mirror of https://github.com/shred/acme4j
Evaluate retry-after header
parent
cef5984f81
commit
5049cd5ffd
|
@ -28,6 +28,7 @@ import org.shredzone.acme4j.connector.Connection;
|
||||||
import org.shredzone.acme4j.exception.AcmeException;
|
import org.shredzone.acme4j.exception.AcmeException;
|
||||||
import org.shredzone.acme4j.exception.AcmeNetworkException;
|
import org.shredzone.acme4j.exception.AcmeNetworkException;
|
||||||
import org.shredzone.acme4j.exception.AcmeProtocolException;
|
import org.shredzone.acme4j.exception.AcmeProtocolException;
|
||||||
|
import org.shredzone.acme4j.exception.AcmeRetryAfterException;
|
||||||
import org.shredzone.acme4j.util.ClaimBuilder;
|
import org.shredzone.acme4j.util.ClaimBuilder;
|
||||||
import org.shredzone.acme4j.util.TimestampParser;
|
import org.shredzone.acme4j.util.TimestampParser;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
|
@ -162,6 +163,13 @@ public class Authorization extends AcmeResource {
|
||||||
/**
|
/**
|
||||||
* Updates the {@link Authorization}. After invocation, the {@link Authorization}
|
* Updates the {@link Authorization}. After invocation, the {@link Authorization}
|
||||||
* reflects the current state at the ACME server.
|
* reflects the current state at the ACME server.
|
||||||
|
*
|
||||||
|
* @throws AcmeRetryAfterException
|
||||||
|
* the auhtorization is still being validated, and the server returned an
|
||||||
|
* estimated date when the validation will be completed. If you are
|
||||||
|
* polling for the authorization to complete, you should wait for the date
|
||||||
|
* given in {@link AcmeRetryAfterException#getRetryAfter()}. Note that the
|
||||||
|
* authorization status is updated even if this exception was thrown.
|
||||||
*/
|
*/
|
||||||
public void update() throws AcmeException {
|
public void update() throws AcmeException {
|
||||||
LOG.debug("update");
|
LOG.debug("update");
|
||||||
|
@ -171,10 +179,15 @@ public class Authorization extends AcmeResource {
|
||||||
conn.throwAcmeException();
|
conn.throwAcmeException();
|
||||||
}
|
}
|
||||||
|
|
||||||
// HTTP_ACCEPTED requires Retry-After header to be set
|
|
||||||
|
|
||||||
Map<String, Object> result = conn.readJsonResponse();
|
Map<String, Object> result = conn.readJsonResponse();
|
||||||
unmarshalAuthorization(result);
|
unmarshalAuthorization(result);
|
||||||
|
|
||||||
|
if (rc == HttpURLConnection.HTTP_ACCEPTED) {
|
||||||
|
Date retryAfter = conn.getRetryAfterHeader();
|
||||||
|
throw new AcmeRetryAfterException(
|
||||||
|
"authorization is not completed yet",
|
||||||
|
retryAfter);
|
||||||
|
}
|
||||||
} catch (IOException ex) {
|
} catch (IOException ex) {
|
||||||
throw new AcmeNetworkException(ex);
|
throw new AcmeNetworkException(ex);
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,6 +19,7 @@ import java.net.URI;
|
||||||
import java.security.cert.CertificateEncodingException;
|
import java.security.cert.CertificateEncodingException;
|
||||||
import java.security.cert.X509Certificate;
|
import java.security.cert.X509Certificate;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Date;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import org.shredzone.acme4j.connector.Connection;
|
import org.shredzone.acme4j.connector.Connection;
|
||||||
|
@ -26,6 +27,7 @@ import org.shredzone.acme4j.connector.Resource;
|
||||||
import org.shredzone.acme4j.exception.AcmeException;
|
import org.shredzone.acme4j.exception.AcmeException;
|
||||||
import org.shredzone.acme4j.exception.AcmeNetworkException;
|
import org.shredzone.acme4j.exception.AcmeNetworkException;
|
||||||
import org.shredzone.acme4j.exception.AcmeProtocolException;
|
import org.shredzone.acme4j.exception.AcmeProtocolException;
|
||||||
|
import org.shredzone.acme4j.exception.AcmeRetryAfterException;
|
||||||
import org.shredzone.acme4j.util.ClaimBuilder;
|
import org.shredzone.acme4j.util.ClaimBuilder;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
@ -80,20 +82,29 @@ public class Certificate extends AcmeResource {
|
||||||
* Downloads the certificate. The result is cached.
|
* Downloads the certificate. The result is cached.
|
||||||
*
|
*
|
||||||
* @return {@link X509Certificate} that was downloaded
|
* @return {@link X509Certificate} that was downloaded
|
||||||
|
* @throws AcmeRetryAfterException
|
||||||
|
* the certificate is still being created, and the server returned an
|
||||||
|
* estimated date when it will be ready for download. You should wait for
|
||||||
|
* the date given in {@link AcmeRetryAfterException#getRetryAfter()}
|
||||||
|
* before trying again.
|
||||||
*/
|
*/
|
||||||
public X509Certificate download() throws AcmeException {
|
public X509Certificate download() throws AcmeException {
|
||||||
if (cert == null) {
|
if (cert == null) {
|
||||||
LOG.debug("download");
|
LOG.debug("download");
|
||||||
try (Connection conn = getSession().provider().connect()) {
|
try (Connection conn = getSession().provider().connect()) {
|
||||||
int rc = conn.sendRequest(getLocation());
|
int rc = conn.sendRequest(getLocation());
|
||||||
|
if (rc == HttpURLConnection.HTTP_ACCEPTED) {
|
||||||
|
Date retryAfter = conn.getRetryAfterHeader();
|
||||||
|
throw new AcmeRetryAfterException(
|
||||||
|
"certificate is not available for download yet",
|
||||||
|
retryAfter);
|
||||||
|
}
|
||||||
|
|
||||||
if (rc != HttpURLConnection.HTTP_OK) {
|
if (rc != HttpURLConnection.HTTP_OK) {
|
||||||
conn.throwAcmeException();
|
conn.throwAcmeException();
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: HTTP_ACCEPTED plus Retry-After header if not yet available
|
|
||||||
|
|
||||||
chainCertUri = conn.getLink("up");
|
chainCertUri = conn.getLink("up");
|
||||||
|
|
||||||
cert = conn.readCertificate();
|
cert = conn.readCertificate();
|
||||||
} catch (IOException ex) {
|
} catch (IOException ex) {
|
||||||
throw new AcmeNetworkException(ex);
|
throw new AcmeNetworkException(ex);
|
||||||
|
@ -106,6 +117,11 @@ public class Certificate extends AcmeResource {
|
||||||
* Downloads the certificate chain. The result is cached.
|
* Downloads the certificate chain. The result is cached.
|
||||||
*
|
*
|
||||||
* @return Chain of {@link X509Certificate}s
|
* @return Chain of {@link X509Certificate}s
|
||||||
|
* @throws AcmeRetryAfterException
|
||||||
|
* the certificate is still being created, and the server returned an
|
||||||
|
* estimated date when it will be ready for download. You should wait for
|
||||||
|
* the date given in {@link AcmeRetryAfterException#getRetryAfter()}
|
||||||
|
* before trying again.
|
||||||
*/
|
*/
|
||||||
public X509Certificate[] downloadChain() throws AcmeException {
|
public X509Certificate[] downloadChain() throws AcmeException {
|
||||||
if (chain == null) {
|
if (chain == null) {
|
||||||
|
|
|
@ -31,6 +31,7 @@ import org.shredzone.acme4j.connector.Connection;
|
||||||
import org.shredzone.acme4j.exception.AcmeException;
|
import org.shredzone.acme4j.exception.AcmeException;
|
||||||
import org.shredzone.acme4j.exception.AcmeNetworkException;
|
import org.shredzone.acme4j.exception.AcmeNetworkException;
|
||||||
import org.shredzone.acme4j.exception.AcmeProtocolException;
|
import org.shredzone.acme4j.exception.AcmeProtocolException;
|
||||||
|
import org.shredzone.acme4j.exception.AcmeRetryAfterException;
|
||||||
import org.shredzone.acme4j.util.ClaimBuilder;
|
import org.shredzone.acme4j.util.ClaimBuilder;
|
||||||
import org.shredzone.acme4j.util.TimestampParser;
|
import org.shredzone.acme4j.util.TimestampParser;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
|
@ -228,16 +229,31 @@ public class Challenge extends AcmeResource {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates the state of this challenge.
|
* Updates the state of this challenge.
|
||||||
|
*
|
||||||
|
* @throws AcmeRetryAfterException
|
||||||
|
* the challenge is still being validated, and the server returned an
|
||||||
|
* estimated date when the challenge will be completed. If you are polling
|
||||||
|
* for the challenge to complete, you should wait for the date given in
|
||||||
|
* {@link AcmeRetryAfterException#getRetryAfter()}. Note that the
|
||||||
|
* challenge status is updated even if this exception was thrown.
|
||||||
*/
|
*/
|
||||||
public void update() throws AcmeException {
|
public void update() throws AcmeException {
|
||||||
LOG.debug("update");
|
LOG.debug("update");
|
||||||
try (Connection conn = getSession().provider().connect()) {
|
try (Connection conn = getSession().provider().connect()) {
|
||||||
int rc = conn.sendRequest(getLocation());
|
int rc = conn.sendRequest(getLocation());
|
||||||
if (rc != HttpURLConnection.HTTP_ACCEPTED) {
|
if (rc != HttpURLConnection.HTTP_OK && rc != HttpURLConnection.HTTP_ACCEPTED) {
|
||||||
conn.throwAcmeException();
|
conn.throwAcmeException();
|
||||||
}
|
}
|
||||||
|
|
||||||
unmarshall(conn.readJsonResponse());
|
unmarshall(conn.readJsonResponse());
|
||||||
|
|
||||||
|
if (rc == HttpURLConnection.HTTP_ACCEPTED) {
|
||||||
|
Date retryAfter = conn.getRetryAfterHeader();
|
||||||
|
if (retryAfter != null) {
|
||||||
|
throw new AcmeRetryAfterException("challenge is not completed yet",
|
||||||
|
retryAfter);
|
||||||
|
}
|
||||||
|
}
|
||||||
} catch (IOException ex) {
|
} catch (IOException ex) {
|
||||||
throw new AcmeNetworkException(ex);
|
throw new AcmeNetworkException(ex);
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,41 @@
|
||||||
|
/*
|
||||||
|
* acme4j - Java ACME client
|
||||||
|
*
|
||||||
|
* Copyright (C) 2016 Richard "Shred" Körber
|
||||||
|
* http://acme4j.shredzone.org
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||||
|
*/
|
||||||
|
package org.shredzone.acme4j.exception;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This exception is thrown when a server side process has not been completed yet, and the
|
||||||
|
* server returned an estimated retry date.
|
||||||
|
*
|
||||||
|
* @author Richard "Shred" Körber
|
||||||
|
*/
|
||||||
|
public class AcmeRetryAfterException extends AcmeException {
|
||||||
|
private static final long serialVersionUID = 4461979121063649905L;
|
||||||
|
|
||||||
|
private final Date retryAfter;
|
||||||
|
|
||||||
|
public AcmeRetryAfterException(String msg, Date retryAfter) {
|
||||||
|
super(msg);
|
||||||
|
this.retryAfter = retryAfter;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the retry-after date returned by the server.
|
||||||
|
*/
|
||||||
|
public Date getRetryAfter() {
|
||||||
|
return (retryAfter != null ? new Date(retryAfter.getTime()) : null);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -14,13 +14,14 @@
|
||||||
package org.shredzone.acme4j;
|
package org.shredzone.acme4j;
|
||||||
|
|
||||||
import static org.hamcrest.Matchers.*;
|
import static org.hamcrest.Matchers.*;
|
||||||
import static org.junit.Assert.assertThat;
|
import static org.junit.Assert.*;
|
||||||
import static org.shredzone.acme4j.util.TestUtils.getJsonAsMap;
|
import static org.shredzone.acme4j.util.TestUtils.getJsonAsMap;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.HttpURLConnection;
|
import java.net.HttpURLConnection;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
import java.util.Date;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
@ -28,6 +29,7 @@ import org.shredzone.acme4j.challenge.Challenge;
|
||||||
import org.shredzone.acme4j.challenge.Dns01Challenge;
|
import org.shredzone.acme4j.challenge.Dns01Challenge;
|
||||||
import org.shredzone.acme4j.challenge.Http01Challenge;
|
import org.shredzone.acme4j.challenge.Http01Challenge;
|
||||||
import org.shredzone.acme4j.challenge.TlsSni02Challenge;
|
import org.shredzone.acme4j.challenge.TlsSni02Challenge;
|
||||||
|
import org.shredzone.acme4j.exception.AcmeRetryAfterException;
|
||||||
import org.shredzone.acme4j.provider.TestableConnectionProvider;
|
import org.shredzone.acme4j.provider.TestableConnectionProvider;
|
||||||
import org.shredzone.acme4j.util.ClaimBuilder;
|
import org.shredzone.acme4j.util.ClaimBuilder;
|
||||||
import org.shredzone.acme4j.util.TimestampParser;
|
import org.shredzone.acme4j.util.TimestampParser;
|
||||||
|
@ -156,6 +158,64 @@ public class AuthorizationTest {
|
||||||
provider.close();
|
provider.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test that authorization is properly updated, with retry-after header set.
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testUpdateRetryAfter() throws Exception {
|
||||||
|
final long retryAfter = System.currentTimeMillis() + 30 * 1000L;
|
||||||
|
|
||||||
|
TestableConnectionProvider provider = new TestableConnectionProvider() {
|
||||||
|
@Override
|
||||||
|
public int sendRequest(URI uri) {
|
||||||
|
assertThat(uri, is(locationUri));
|
||||||
|
return HttpURLConnection.HTTP_ACCEPTED;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<String, Object> readJsonResponse() {
|
||||||
|
return getJsonAsMap("updateAuthorizationResponse");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Date getRetryAfterHeader() {
|
||||||
|
return new Date(retryAfter);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Session session = provider.createSession();
|
||||||
|
|
||||||
|
Http01Challenge httpChallenge = new Http01Challenge(session);
|
||||||
|
Dns01Challenge dnsChallenge = new Dns01Challenge(session);
|
||||||
|
provider.putTestChallenge("http-01", httpChallenge);
|
||||||
|
provider.putTestChallenge("dns-01", dnsChallenge);
|
||||||
|
|
||||||
|
Authorization auth = new Authorization(session, locationUri);
|
||||||
|
|
||||||
|
try {
|
||||||
|
auth.update();
|
||||||
|
fail("Expected AcmeRetryAfterException");
|
||||||
|
} catch (AcmeRetryAfterException ex) {
|
||||||
|
assertThat(ex.getRetryAfter(), is(new Date(retryAfter)));
|
||||||
|
}
|
||||||
|
|
||||||
|
assertThat(auth.getDomain(), is("example.org"));
|
||||||
|
assertThat(auth.getStatus(), is(Status.VALID));
|
||||||
|
assertThat(auth.getExpires(), is(TimestampParser.parse("2016-01-02T17:12:40Z")));
|
||||||
|
assertThat(auth.getLocation(), is(locationUri));
|
||||||
|
|
||||||
|
assertThat(auth.getChallenges(), containsInAnyOrder(
|
||||||
|
(Challenge) httpChallenge, (Challenge) dnsChallenge));
|
||||||
|
|
||||||
|
assertThat(auth.getCombinations(), hasSize(2));
|
||||||
|
assertThat(auth.getCombinations().get(0), containsInAnyOrder(
|
||||||
|
(Challenge) httpChallenge));
|
||||||
|
assertThat(auth.getCombinations().get(1), containsInAnyOrder(
|
||||||
|
(Challenge) httpChallenge, (Challenge) dnsChallenge));
|
||||||
|
|
||||||
|
provider.close();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test that an authorization can be deactivated.
|
* Test that an authorization can be deactivated.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
package org.shredzone.acme4j;
|
package org.shredzone.acme4j;
|
||||||
|
|
||||||
import static org.hamcrest.Matchers.*;
|
import static org.hamcrest.Matchers.*;
|
||||||
import static org.junit.Assert.assertThat;
|
import static org.junit.Assert.*;
|
||||||
import static org.shredzone.acme4j.util.TestUtils.getJson;
|
import static org.shredzone.acme4j.util.TestUtils.getJson;
|
||||||
import static uk.co.datumedge.hamcrest.json.SameJSONAs.sameJSONAs;
|
import static uk.co.datumedge.hamcrest.json.SameJSONAs.sameJSONAs;
|
||||||
|
|
||||||
|
@ -22,10 +22,12 @@ import java.io.IOException;
|
||||||
import java.net.HttpURLConnection;
|
import java.net.HttpURLConnection;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.security.cert.X509Certificate;
|
import java.security.cert.X509Certificate;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.shredzone.acme4j.connector.Resource;
|
import org.shredzone.acme4j.connector.Resource;
|
||||||
import org.shredzone.acme4j.exception.AcmeException;
|
import org.shredzone.acme4j.exception.AcmeException;
|
||||||
|
import org.shredzone.acme4j.exception.AcmeRetryAfterException;
|
||||||
import org.shredzone.acme4j.provider.TestableConnectionProvider;
|
import org.shredzone.acme4j.provider.TestableConnectionProvider;
|
||||||
import org.shredzone.acme4j.util.ClaimBuilder;
|
import org.shredzone.acme4j.util.ClaimBuilder;
|
||||||
import org.shredzone.acme4j.util.TestUtils;
|
import org.shredzone.acme4j.util.TestUtils;
|
||||||
|
@ -84,6 +86,38 @@ public class CertificateTest {
|
||||||
provider.close();
|
provider.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test that a {@link AcmeRetryAfterException} is thrown.
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testRetryAfter() throws AcmeException, IOException {
|
||||||
|
final long retryAfter = System.currentTimeMillis() + 30 * 1000L;
|
||||||
|
|
||||||
|
TestableConnectionProvider provider = new TestableConnectionProvider() {
|
||||||
|
@Override
|
||||||
|
public int sendRequest(URI uri) {
|
||||||
|
assertThat(uri, is(locationUri));
|
||||||
|
return HttpURLConnection.HTTP_ACCEPTED;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Date getRetryAfterHeader() {
|
||||||
|
return new Date(retryAfter);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Certificate cert = new Certificate(provider.createSession(), locationUri);
|
||||||
|
|
||||||
|
try {
|
||||||
|
cert.download();
|
||||||
|
fail("Expected AcmeRetryAfterException");
|
||||||
|
} catch (AcmeRetryAfterException ex) {
|
||||||
|
assertThat(ex.getRetryAfter(), is(new Date(retryAfter)));
|
||||||
|
}
|
||||||
|
|
||||||
|
provider.close();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test that a certificate can be revoked.
|
* Test that a certificate can be revoked.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
package org.shredzone.acme4j.challenge;
|
package org.shredzone.acme4j.challenge;
|
||||||
|
|
||||||
import static org.hamcrest.Matchers.*;
|
import static org.hamcrest.Matchers.*;
|
||||||
import static org.junit.Assert.assertThat;
|
import static org.junit.Assert.*;
|
||||||
import static org.shredzone.acme4j.util.TestUtils.*;
|
import static org.shredzone.acme4j.util.TestUtils.*;
|
||||||
import static uk.co.datumedge.hamcrest.json.SameJSONAs.sameJSONAs;
|
import static uk.co.datumedge.hamcrest.json.SameJSONAs.sameJSONAs;
|
||||||
|
|
||||||
|
@ -27,6 +27,7 @@ import java.net.HttpURLConnection;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.net.URISyntaxException;
|
import java.net.URISyntaxException;
|
||||||
import java.security.KeyPair;
|
import java.security.KeyPair;
|
||||||
|
import java.util.Date;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import org.jose4j.base64url.Base64Url;
|
import org.jose4j.base64url.Base64Url;
|
||||||
|
@ -39,6 +40,7 @@ import org.junit.Test;
|
||||||
import org.shredzone.acme4j.Session;
|
import org.shredzone.acme4j.Session;
|
||||||
import org.shredzone.acme4j.Status;
|
import org.shredzone.acme4j.Status;
|
||||||
import org.shredzone.acme4j.exception.AcmeProtocolException;
|
import org.shredzone.acme4j.exception.AcmeProtocolException;
|
||||||
|
import org.shredzone.acme4j.exception.AcmeRetryAfterException;
|
||||||
import org.shredzone.acme4j.provider.TestableConnectionProvider;
|
import org.shredzone.acme4j.provider.TestableConnectionProvider;
|
||||||
import org.shredzone.acme4j.util.ClaimBuilder;
|
import org.shredzone.acme4j.util.ClaimBuilder;
|
||||||
import org.shredzone.acme4j.util.SignatureUtils;
|
import org.shredzone.acme4j.util.SignatureUtils;
|
||||||
|
@ -208,7 +210,7 @@ public class ChallengeTest {
|
||||||
@Override
|
@Override
|
||||||
public int sendRequest(URI uri) {
|
public int sendRequest(URI uri) {
|
||||||
assertThat(uri, is(locationUri));
|
assertThat(uri, is(locationUri));
|
||||||
return HttpURLConnection.HTTP_ACCEPTED;
|
return HttpURLConnection.HTTP_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -230,6 +232,49 @@ public class ChallengeTest {
|
||||||
provider.close();
|
provider.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test that a challenge is properly updated, with Retry-After header.
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testUpdateRetryAfter() throws Exception {
|
||||||
|
final long retryAfter = System.currentTimeMillis() + 30 * 1000L;
|
||||||
|
|
||||||
|
TestableConnectionProvider provider = new TestableConnectionProvider() {
|
||||||
|
@Override
|
||||||
|
public int sendRequest(URI uri) {
|
||||||
|
assertThat(uri, is(locationUri));
|
||||||
|
return HttpURLConnection.HTTP_ACCEPTED;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<String, Object> readJsonResponse() {
|
||||||
|
return getJsonAsMap("updateHttpChallengeResponse");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Date getRetryAfterHeader() {
|
||||||
|
return new Date(retryAfter);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Session session = provider.createSession();
|
||||||
|
|
||||||
|
Challenge challenge = new Http01Challenge(session);
|
||||||
|
challenge.unmarshall(getJsonAsMap("triggerHttpChallengeResponse"));
|
||||||
|
|
||||||
|
try {
|
||||||
|
challenge.update();
|
||||||
|
fail("Expected AcmeRetryAfterException");
|
||||||
|
} catch (AcmeRetryAfterException ex) {
|
||||||
|
assertThat(ex.getRetryAfter(), is(new Date(retryAfter)));
|
||||||
|
}
|
||||||
|
|
||||||
|
assertThat(challenge.getStatus(), is(Status.VALID));
|
||||||
|
assertThat(challenge.getLocation(), is(locationUri));
|
||||||
|
|
||||||
|
provider.close();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test that challenge serialization works correctly.
|
* Test that challenge serialization works correctly.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -48,6 +48,8 @@ while (challenge.getStatus() != Status.VALID) {
|
||||||
|
|
||||||
This is a very simple example. You should limit the number of loop iterations, and abort the loop when the status should turn to `INVALID`. If you know when the CA server actually requested your response (e.g. when you notice a HTTP request on the response file), you should start polling after that event.
|
This is a very simple example. You should limit the number of loop iterations, and abort the loop when the status should turn to `INVALID`. If you know when the CA server actually requested your response (e.g. when you notice a HTTP request on the response file), you should start polling after that event.
|
||||||
|
|
||||||
|
`update()` may throw an `AcmeRetryAfterException`, giving an estimated time in `getRetryAfter()` for when the challenge is completed. You should then wait until that moment has been reached, before trying again. The challenge state is still updated when this exception is thrown.
|
||||||
|
|
||||||
As soon as all the necessary challenges are `VALID`, you have successfully associated the domain with your account.
|
As soon as all the necessary challenges are `VALID`, you have successfully associated the domain with your account.
|
||||||
|
|
||||||
If your final certificate will contain further domains or subdomains, repeat the authorization run with each of them.
|
If your final certificate will contain further domains or subdomains, repeat the authorization run with each of them.
|
||||||
|
@ -67,6 +69,8 @@ auth.update();
|
||||||
|
|
||||||
After invoking `update()`, the `Authorization` object contains the current server state about your authorization, including the domain name, the overall status, and an expiry date.
|
After invoking `update()`, the `Authorization` object contains the current server state about your authorization, including the domain name, the overall status, and an expiry date.
|
||||||
|
|
||||||
|
`update()` may throw an `AcmeRetryAfterException`, giving an estimated time in `getRetryAfter()` for when all challenges are completed. You should then wait until that moment has been reached, before trying again. The authorization state is still updated when this exception is thrown.
|
||||||
|
|
||||||
## Deactivate an Authorization
|
## Deactivate an Authorization
|
||||||
|
|
||||||
It is possible to deactivate an `Authorization`, for example if you sell the associated domain.
|
It is possible to deactivate an `Authorization`, for example if you sell the associated domain.
|
||||||
|
|
|
@ -45,6 +45,8 @@ X509Certificate[] chain = cert.downloadChain();
|
||||||
|
|
||||||
Congratulations! You have just created your first certificate via _acme4j_.
|
Congratulations! You have just created your first certificate via _acme4j_.
|
||||||
|
|
||||||
|
`download()` may throw an `AcmeRetryAfterException`, giving an estimated time in `getRetryAfter()` for when the certificate is ready for download. You should then wait until that moment has been reached, before trying again.
|
||||||
|
|
||||||
To recreate a `Certificate` object from the location, just bind it:
|
To recreate a `Certificate` object from the location, just bind it:
|
||||||
|
|
||||||
```java
|
```java
|
||||||
|
|
Loading…
Reference in New Issue