Add updateAuthorization() method to get the current authorization state

pull/17/merge
Richard Körber 2015-12-21 00:10:03 +01:00
parent 8b0f266455
commit 41dabd0cfd
8 changed files with 213 additions and 31 deletions

View File

@ -59,6 +59,14 @@ public interface AcmeClient {
*/
void newAuthorization(Account account, Authorization auth) throws AcmeException;
/**
* Updates an {@link Authorization} to the current server state.
*
* @param auth
* {@link Authorization} to update
*/
void updateAuthorization(Authorization auth) throws AcmeException;
/**
* Triggers a {@link Challenge}. The ACME server is requested to validate the
* response. Note that the validation is performed asynchronously.

View File

@ -14,6 +14,7 @@
package org.shredzone.acme4j;
import java.io.Serializable;
import java.net.URI;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
@ -29,12 +30,41 @@ import org.shredzone.acme4j.challenge.Challenge;
public class Authorization implements Serializable {
private static final long serialVersionUID = -3116928998379417741L;
private URI location;
private String domain;
private String status;
private String expires;
private List<Challenge> challenges;
private List<List<Challenge>> combinations;
/**
* Create an empty {@link Authorization}.
*/
public Authorization() {
// default constructor
}
/**
* Create an {@link Authorization} for the given location URI.
*/
public Authorization(URI location) {
this.location = location;
}
/**
* Gets the server URI for the authorization.
*/
public URI getLocation() {
return location;
}
/**
* Sets the server URI for the authorization.
*/
public void setLocation(URI location) {
this.location = location;
}
/**
* Gets the domain name to be authorized.
*/

View File

@ -186,41 +186,33 @@ public abstract class AbstractAcmeClient implements AcmeClient {
conn.throwAcmeException();
}
auth.setLocation(conn.getLocation());
Map<String, Object> result = conn.readJsonResponse();
unmarshalAuthorization(result, auth);
}
}
auth.setStatus((String) result.get("status"));
@Override
public void updateAuthorization(Authorization auth) throws AcmeException {
if (auth == null) {
throw new NullPointerException("auth must not be null");
}
if (auth.getLocation() == null) {
throw new IllegalArgumentException("auth location must not be null. Use newAuthorization() if not known.");
}
@SuppressWarnings("unchecked")
Collection<Map<String, Object>> challenges =
(Collection<Map<String, Object>>) result.get("challenges");
List<Challenge> cr = new ArrayList<>();
for (Map<String, Object> c : challenges) {
Challenge ch = createChallenge((String) c.get("type"));
if (ch != null) {
ch.unmarshall(c);
cr.add(ch);
}
LOG.debug("updateAuthorization");
try (Connection conn = createConnection()) {
int rc = conn.sendRequest(auth.getLocation());
if (rc != HttpURLConnection.HTTP_OK && rc != HttpURLConnection.HTTP_ACCEPTED) {
conn.throwAcmeException();
}
auth.setChallenges(cr);
@SuppressWarnings("unchecked")
Collection<List<Number>> combinations =
(Collection<List<Number>>) result.get("combinations");
if (combinations != null) {
List<List<Challenge>> cmb = new ArrayList<>(combinations.size());
for (List<Number> c : combinations) {
List<Challenge> clist = new ArrayList<>(c.size());
for (Number n : c) {
clist.add(cr.get(n.intValue()));
}
cmb.add(clist);
}
auth.setCombinations(cmb);
} else {
List<List<Challenge>> cmb = new ArrayList<>(1);
cmb.add(cr);
auth.setCombinations(cmb);
}
// HTTP_ACCEPTED requires Retry-After header to be set
Map<String, Object> result = conn.readJsonResponse();
unmarshalAuthorization(result, auth);
}
}
@ -371,4 +363,53 @@ public abstract class AbstractAcmeClient implements AcmeClient {
}
}
/**
* Sets {@link Authorization} properties according to the given JSON data.
*
* @param json
* JSON data
* @param auth
* {@link Authorization} to update
*/
@SuppressWarnings("unchecked")
private void unmarshalAuthorization(Map<String, Object> json, Authorization auth) {
auth.setStatus((String) json.get("status"));
auth.setExpires((String) json.get("expires"));
Map<String, Object> identifier = (Map<String, Object>) json.get("identifier");
if (identifier != null) {
auth.setDomain((String) identifier.get("value"));
}
Collection<Map<String, Object>> challenges =
(Collection<Map<String, Object>>) json.get("challenges");
List<Challenge> cr = new ArrayList<>();
for (Map<String, Object> c : challenges) {
Challenge ch = createChallenge((String) c.get("type"));
if (ch != null) {
ch.unmarshall(c);
cr.add(ch);
}
}
auth.setChallenges(cr);
Collection<List<Number>> combinations =
(Collection<List<Number>>) json.get("combinations");
if (combinations != null) {
List<List<Challenge>> cmb = new ArrayList<>(combinations.size());
for (List<Number> c : combinations) {
List<Challenge> clist = new ArrayList<>(c.size());
for (Number n : c) {
clist.add(cr.get(n.intValue()));
}
cmb.add(clist);
}
auth.setCombinations(cmb);
} else {
List<List<Challenge>> cmb = new ArrayList<>(1);
cmb.add(cr);
auth.setCombinations(cmb);
}
}
}

View File

@ -16,6 +16,8 @@ package org.shredzone.acme4j;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.assertThat;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
@ -154,6 +156,18 @@ public class AuthorizationTest {
assertThat(c7, is(nullValue()));
}
/**
* Test constructors.
*/
@Test
public void testConstructor() throws URISyntaxException {
Authorization auth1 = new Authorization();
assertThat(auth1.getLocation(), is(nullValue()));
Authorization auth2 = new Authorization(new URI("http://example.com/acme/12345"));
assertThat(auth2.getLocation(), is(new URI("http://example.com/acme/12345")));
}
/**
* Sets up a {@link Challenge}.
*

View File

@ -53,7 +53,7 @@ public class RegistrationTest {
}
/**
* Test constructors;
* Test constructors.
*/
@Test
public void testConstructor() throws URISyntaxException {

View File

@ -168,6 +168,11 @@ public class AbstractAcmeClientTest {
public Map<String, Object> readJsonResponse() throws AcmeException {
return getJsonAsMap("newAuthorizationResponse");
}
@Override
public URI getLocation() throws AcmeException {
return locationUri;
}
};
HttpChallenge httpChallenge = new HttpChallenge();
@ -183,6 +188,51 @@ public class AbstractAcmeClientTest {
assertThat(auth.getDomain(), is("example.org"));
assertThat(auth.getStatus(), is("pending"));
assertThat(auth.getExpires(), is(nullValue()));
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));
}
/**
* Test that {@link Authorization} are properly updated.
*/
@Test
public void testUpdateAuthorization() throws AcmeException {
Authorization auth = new Authorization(locationUri);
Connection connection = new DummyConnection() {
@Override
public int sendRequest(URI uri) throws AcmeException {
assertThat(uri, is(locationUri));
return HttpURLConnection.HTTP_OK;
}
@Override
public Map<String, Object> readJsonResponse() throws AcmeException {
return getJsonAsMap("updateAuthorizationResponse");
}
};
HttpChallenge httpChallenge = new HttpChallenge();
DnsChallenge dnsChallenge = new DnsChallenge();
TestableAbstractAcmeClient client = new TestableAbstractAcmeClient(connection);
client.putTestChallenge("http-01", httpChallenge);
client.putTestChallenge("dns-01", dnsChallenge);
client.updateAuthorization(auth);
assertThat(auth.getDomain(), is("example.org"));
assertThat(auth.getStatus(), is("valid"));
assertThat(auth.getExpires(), is("2015-03-01"));
assertThat(auth.getLocation(), is(locationUri));
assertThat(auth.getChallenges(), containsInAnyOrder(
(Challenge) httpChallenge, (Challenge) dnsChallenge));

View File

@ -49,6 +49,31 @@ newAuthorizationResponse = \
"combinations": [[0], [0,1]]\
}
updateAuthorizationResponse = \
{\
"status": "valid",\
"expires": "2015-03-01",\
"identifier": {\
"type": "dns",\
"value": "example.org"\
},\
"challenges": [\
{\
"type": "http-01",\
"status":"pending",\
"uri": "https://example.com/authz/asdf/0",\
"token": "IlirfxKKXAsHtmzK29Pj8A"\
},\
{\
"type": "dns-01",\
"status":"pending",\
"uri": "https://example.com/authz/asdf/1",\
"token": "DGyRejmCefe7v4NfDGDKfA"\
}\
],\
"combinations": [[0], [0,1]]\
}
triggerHttpChallenge = \
{\
"type": "http-01",\

View File

@ -63,6 +63,20 @@ If your final certificate contains further domains or subdomains, repeat the aut
Note that wildcard certificates are not currently supported.
## Update an Authorization
For each authorization, the server provides an URI where the status of the authorization can be queried. It can be retrieved from `Authorization.getLocation()` after `newAuthorization()` returned.
To get a status overview of your authorization and all challenges, create a new `Authorization` object and pass the location URI to the constructor:
```java
URI authUri = ... // Authorization URI
Authorization auth = new Authorization(authUri);
client.updateAuthorization(auth);
```
After that call, the `Authorization` object contains the current server state about your authorization, including the domain name, the overall status, and an expiry date.
## 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.