mirror of https://github.com/shred/acme4j
Add updateAuthorization() method to get the current authorization state
parent
8b0f266455
commit
41dabd0cfd
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
*/
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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}.
|
||||
*
|
||||
|
|
|
@ -53,7 +53,7 @@ public class RegistrationTest {
|
|||
}
|
||||
|
||||
/**
|
||||
* Test constructors;
|
||||
* Test constructors.
|
||||
*/
|
||||
@Test
|
||||
public void testConstructor() throws URISyntaxException {
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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",\
|
||||
|
|
|
@ -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.
|
||||
|
|
Loading…
Reference in New Issue