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;
|
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
|
* Triggers a {@link Challenge}. The ACME server is requested to validate the
|
||||||
* response. Note that the validation is performed asynchronously.
|
* response. Note that the validation is performed asynchronously.
|
||||||
|
|
|
@ -14,6 +14,7 @@
|
||||||
package org.shredzone.acme4j;
|
package org.shredzone.acme4j;
|
||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
|
import java.net.URI;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
@ -29,12 +30,41 @@ import org.shredzone.acme4j.challenge.Challenge;
|
||||||
public class Authorization implements Serializable {
|
public class Authorization implements Serializable {
|
||||||
private static final long serialVersionUID = -3116928998379417741L;
|
private static final long serialVersionUID = -3116928998379417741L;
|
||||||
|
|
||||||
|
private URI location;
|
||||||
private String domain;
|
private String domain;
|
||||||
private String status;
|
private String status;
|
||||||
private String expires;
|
private String expires;
|
||||||
private List<Challenge> challenges;
|
private List<Challenge> challenges;
|
||||||
private List<List<Challenge>> combinations;
|
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.
|
* Gets the domain name to be authorized.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -186,41 +186,33 @@ public abstract class AbstractAcmeClient implements AcmeClient {
|
||||||
conn.throwAcmeException();
|
conn.throwAcmeException();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auth.setLocation(conn.getLocation());
|
||||||
|
|
||||||
Map<String, Object> result = conn.readJsonResponse();
|
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")
|
LOG.debug("updateAuthorization");
|
||||||
Collection<Map<String, Object>> challenges =
|
try (Connection conn = createConnection()) {
|
||||||
(Collection<Map<String, Object>>) result.get("challenges");
|
int rc = conn.sendRequest(auth.getLocation());
|
||||||
List<Challenge> cr = new ArrayList<>();
|
if (rc != HttpURLConnection.HTTP_OK && rc != HttpURLConnection.HTTP_ACCEPTED) {
|
||||||
for (Map<String, Object> c : challenges) {
|
conn.throwAcmeException();
|
||||||
Challenge ch = createChallenge((String) c.get("type"));
|
|
||||||
if (ch != null) {
|
|
||||||
ch.unmarshall(c);
|
|
||||||
cr.add(ch);
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
auth.setChallenges(cr);
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
// HTTP_ACCEPTED requires Retry-After header to be set
|
||||||
Collection<List<Number>> combinations =
|
|
||||||
(Collection<List<Number>>) result.get("combinations");
|
Map<String, Object> result = conn.readJsonResponse();
|
||||||
if (combinations != null) {
|
unmarshalAuthorization(result, auth);
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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.hamcrest.Matchers.*;
|
||||||
import static org.junit.Assert.assertThat;
|
import static org.junit.Assert.assertThat;
|
||||||
|
|
||||||
|
import java.net.URI;
|
||||||
|
import java.net.URISyntaxException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
@ -154,6 +156,18 @@ public class AuthorizationTest {
|
||||||
assertThat(c7, is(nullValue()));
|
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}.
|
* Sets up a {@link Challenge}.
|
||||||
*
|
*
|
||||||
|
|
|
@ -53,7 +53,7 @@ public class RegistrationTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test constructors;
|
* Test constructors.
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void testConstructor() throws URISyntaxException {
|
public void testConstructor() throws URISyntaxException {
|
||||||
|
|
|
@ -168,6 +168,11 @@ public class AbstractAcmeClientTest {
|
||||||
public Map<String, Object> readJsonResponse() throws AcmeException {
|
public Map<String, Object> readJsonResponse() throws AcmeException {
|
||||||
return getJsonAsMap("newAuthorizationResponse");
|
return getJsonAsMap("newAuthorizationResponse");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public URI getLocation() throws AcmeException {
|
||||||
|
return locationUri;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
HttpChallenge httpChallenge = new HttpChallenge();
|
HttpChallenge httpChallenge = new HttpChallenge();
|
||||||
|
@ -183,6 +188,51 @@ public class AbstractAcmeClientTest {
|
||||||
assertThat(auth.getDomain(), is("example.org"));
|
assertThat(auth.getDomain(), is("example.org"));
|
||||||
assertThat(auth.getStatus(), is("pending"));
|
assertThat(auth.getStatus(), is("pending"));
|
||||||
assertThat(auth.getExpires(), is(nullValue()));
|
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(
|
assertThat(auth.getChallenges(), containsInAnyOrder(
|
||||||
(Challenge) httpChallenge, (Challenge) dnsChallenge));
|
(Challenge) httpChallenge, (Challenge) dnsChallenge));
|
||||||
|
|
|
@ -49,6 +49,31 @@ newAuthorizationResponse = \
|
||||||
"combinations": [[0], [0,1]]\
|
"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 = \
|
triggerHttpChallenge = \
|
||||||
{\
|
{\
|
||||||
"type": "http-01",\
|
"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.
|
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
|
## 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.
|
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