Add method to restore a Challenge

pull/17/merge
Richard Körber 2015-12-20 12:18:24 +01:00
parent c97392236d
commit db927300e9
7 changed files with 87 additions and 9 deletions

View File

@ -80,6 +80,19 @@ public interface AcmeClient {
*/
void updateChallenge(Account account, Challenge challenge) throws AcmeException;
/**
* Restore a {@link Challenge} instance if only the challenge URI is known. It
* contains the current state.
*
* @param account
* {@link Account} to be used for conversation
* @param challengeUri
* {@link URI} of the challenge to restore
* @throws ClassCastException
* if the challenge does not match the desired type
*/
<T extends Challenge> T restoreChallenge(Account account, URI challengeUri) throws AcmeException;
/**
* Request a certificate.
*

View File

@ -39,9 +39,9 @@ public interface Challenge {
String getType();
/**
* Returns the {@link URI} of the challenge.
* Returns the location {@link URI} of the challenge.
*/
URI getUri();
URI getLocation();
/**
* Returns the current status of the challenge.

View File

@ -58,7 +58,7 @@ public class GenericChallenge implements Challenge {
}
@Override
public URI getUri() {
public URI getLocation() {
String uri = get(KEY_URI);
if (uri == null) {
return null;

View File

@ -202,7 +202,7 @@ public abstract class AbstractAcmeClient implements AcmeClient {
claims.putResource("challenge");
challenge.marshall(claims);
int rc = conn.sendSignedRequest(challenge.getUri(), claims, session, account);
int rc = conn.sendSignedRequest(challenge.getLocation(), claims, session, account);
if (rc != HttpURLConnection.HTTP_OK && rc != HttpURLConnection.HTTP_ACCEPTED) {
conn.throwAcmeException();
}
@ -215,7 +215,7 @@ public abstract class AbstractAcmeClient implements AcmeClient {
public void updateChallenge(Account account, Challenge challenge) throws AcmeException {
LOG.debug("updateChallenge");
try (Connection conn = createConnection()) {
int rc = conn.sendRequest(challenge.getUri());
int rc = conn.sendRequest(challenge.getLocation());
if (rc != HttpURLConnection.HTTP_ACCEPTED) {
conn.throwAcmeException();
}
@ -224,6 +224,27 @@ public abstract class AbstractAcmeClient implements AcmeClient {
}
}
@Override
@SuppressWarnings("unchecked")
public <T extends Challenge> T restoreChallenge(Account account, URI challengeUri) throws AcmeException {
LOG.debug("restoreChallenge");
try (Connection conn = createConnection()) {
int rc = conn.sendRequest(challengeUri);
if (rc != HttpURLConnection.HTTP_ACCEPTED) {
conn.throwAcmeException();
}
Map<String, Object> json = conn.readJsonResponse();
if (!(json.containsKey("type"))) {
throw new AcmeException("Provided URI is not a challenge URI");
}
T challenge = (T) createChallenge(json.get("type").toString());
challenge.unmarshall(json);
return challenge;
}
}
@Override
public URI requestCertificate(Account account, byte[] csr) throws AcmeException {
LOG.debug("requestCertificate");

View File

@ -49,7 +49,7 @@ public class GenericChallengeTest {
// Test default values
assertThat(challenge.getType(), is(nullValue()));
assertThat(challenge.getStatus(), is(Status.PENDING));
assertThat(challenge.getUri(), is(nullValue()));
assertThat(challenge.getLocation(), is(nullValue()));
assertThat(challenge.getValidated(), is(nullValue()));
// Unmarshall a challenge JSON
@ -58,7 +58,7 @@ public class GenericChallengeTest {
// Test unmarshalled values
assertThat(challenge.getType(), is("generic-01"));
assertThat(challenge.getStatus(), is(Status.VALID));
assertThat(challenge.getUri(), is(new URI("http://example.com/challenge/123")));
assertThat(challenge.getLocation(), is(new URI("http://example.com/challenge/123")));
assertThat(challenge.getValidated(), is("2015-12-12T17:19:36.336785823Z"));
}

View File

@ -224,7 +224,7 @@ public class AbstractAcmeClientTest {
client.triggerChallenge(testAccount, challenge);
assertThat(challenge.getStatus(), is(Status.PENDING));
assertThat(challenge.getUri(), is(locationUri));
assertThat(challenge.getLocation(), is(locationUri));
}
/**
@ -253,7 +253,31 @@ public class AbstractAcmeClientTest {
client.updateChallenge(testAccount, challenge);
assertThat(challenge.getStatus(), is(Status.VALID));
assertThat(challenge.getUri(), is(locationUri));
assertThat(challenge.getLocation(), is(locationUri));
}
@Test
public void testRestoreChallenge() throws AcmeException {
Connection connection = new DummyConnection() {
@Override
public int sendRequest(URI uri) throws AcmeException {
assertThat(uri, is(locationUri));
return HttpURLConnection.HTTP_ACCEPTED;
}
@Override
public Map<String, Object> readJsonResponse() throws AcmeException {
return getJsonAsMap("updateHttpChallengeResponse");
}
};
TestableAbstractAcmeClient client = new TestableAbstractAcmeClient(connection);
client.putTestChallenge(HttpChallenge.TYPE, new HttpChallenge());
Challenge challenge = client.restoreChallenge(testAccount, locationUri);
assertThat(challenge.getStatus(), is(Status.VALID));
assertThat(challenge.getLocation(), is(locationUri));
}
/**

View File

@ -62,3 +62,23 @@ As soon as the challenge is `VALID`, you have successfully associated the domain
If your final certificate contains further domains or subdomains, repeat the authorization run with each of them.
Note that wildcard certificates are not currently supported.
## Restore a Challenge
Validating a challenge can take a considerable amount of time and is a candidate for asynchronous execution. This can be a problem if you need to keep the `Challenge` object for a later time or a different Java environment.
To recreate a `Challenge` object at a later time, all you need is to store the original object's `location` property:
```java
Challenge originalChallenge = ... // some Challenge instance
URI challengeUri = originalChallenge.getLocation();
```
Later, you pass this `challengeUri` to `recreateChallenge()`:
```java
URI challengeUri = ... // challenge URI
Challenge restoredChallenge = client.restoreChallenge(account, challengeUri);
```
The `restoredChallenge` already reflects the current state of the challenge.