Add support for sub-problems

pull/55/head
Richard Körber 2017-11-29 01:28:14 +01:00
parent 1eedc755ea
commit d6fb218a27
6 changed files with 105 additions and 28 deletions

View File

@ -13,9 +13,16 @@
*/
package org.shredzone.acme4j;
import static java.util.Collections.unmodifiableList;
import static java.util.stream.Collectors.toList;
import java.io.Serializable;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.List;
import org.shredzone.acme4j.exception.AcmeProtocolException;
import org.shredzone.acme4j.toolbox.JSON;
/**
@ -26,7 +33,7 @@ import org.shredzone.acme4j.toolbox.JSON;
public class Problem implements Serializable {
private static final long serialVersionUID = -8418248862966754214L;
private final URI baseUri;
private final URL baseUrl;
private final JSON problemJson;
/**
@ -34,26 +41,34 @@ public class Problem implements Serializable {
*
* @param problem
* Problem as JSON structure
* @param baseUri
* Document's base {@link URI} to resolve relative URIs against
* @param baseUrl
* Document's base {@link URL} to resolve relative URIs against
*/
public Problem(JSON problem, URI baseUri) {
public Problem(JSON problem, URL baseUrl) {
this.problemJson = problem;
this.baseUri = baseUri;
this.baseUrl = baseUrl;
}
/**
* Returns the problem type. It is always an absolute URI.
*/
public URI getType() {
try {
String type = problemJson.get("type").asString();
return type != null ? baseUri.resolve(type) : null;
return type != null ? baseUrl.toURI().resolve(type) : null;
} catch (URISyntaxException ex) {
throw new IllegalArgumentException("Bad base URL", ex);
}
}
/**
* Returns a human-readable description of the problem.
*/
public String getDetail() {
String value = problemJson.get("value").asString();
if (value != null) {
return value;
}
return problemJson.get("detail").asString();
}
@ -62,8 +77,41 @@ public class Problem implements Serializable {
* an absolute URI.
*/
public URI getInstance() {
try {
String instance = problemJson.get("instance").asString();
return instance != null ? baseUri.resolve(instance) : null;
return instance != null ? baseUrl.toURI().resolve(instance) : null;
} catch (URISyntaxException ex) {
throw new IllegalArgumentException("Bad base URL", ex);
}
}
/**
* Returns the domain this problem relates to. May be {@code null}.
*/
public String getDomain() {
JSON identifier = problemJson.get("identifier").asObject();
if (identifier == null) {
return null;
}
String type = identifier.get("type").asString();
if (!"dns".equals(type)) {
throw new AcmeProtocolException("Cannot process a " + type + " identifier");
}
return identifier.get("value").asString();
}
/**
* Returns a list of sub-problems. May be empty, but is never {@code null}.
*/
public List<Problem> getSubProblems() {
return unmodifiableList(
problemJson.get("sub-problems")
.asArray().stream()
.map(o -> o.asProblem(baseUrl))
.collect(toList())
);
}
/**

View File

@ -238,10 +238,8 @@ public class DefaultConnection implements Connection {
throw new AcmeException("HTTP " + rc + ": " + conn.getResponseMessage());
}
Problem problem = new Problem(readJsonResponse(), conn.getURL().toURI());
Problem problem = new Problem(readJsonResponse(), conn.getURL());
throw createAcmeException(problem);
} catch (URISyntaxException ex) {
throw new AcmeProtocolException("Bad request URL: " + conn.getURL(), ex);
} catch (IOException ex) {
throw new AcmeNetworkException(ex);
}

View File

@ -315,11 +315,7 @@ public final class JSON implements Serializable {
return null;
}
try {
return new Problem(asObject(), baseUrl.toURI());
} catch (URISyntaxException ex) {
throw new IllegalArgumentException("Bad base URL", ex);
}
return new Problem(asObject(), baseUrl);
}
/**

View File

@ -13,11 +13,15 @@
*/
package org.shredzone.acme4j;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.CoreMatchers.nullValue;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.assertThat;
import static org.shredzone.acme4j.toolbox.TestUtils.url;
import static uk.co.datumedge.hamcrest.json.SameJSONAs.sameJSONAs;
import java.net.URI;
import java.net.URL;
import java.util.List;
import org.junit.Test;
import org.shredzone.acme4j.toolbox.JSON;
@ -30,16 +34,31 @@ public class ProblemTest {
@Test
public void testProblem() {
URI baseUri = URI.create("https://example.com/acme/1");
URL baseUrl = url("https://example.com/acme/1");
JSON original = TestUtils.getJSON("problem");
Problem problem = new Problem(original, baseUri);
Problem problem = new Problem(original, baseUrl);
assertThat(problem.getType(), is(URI.create("urn:ietf:params:acme:error:connection")));
assertThat(problem.getDetail(), is("connection refused"));
assertThat(problem.getType(), is(URI.create("urn:ietf:params:acme:error:malformed")));
assertThat(problem.getDetail(), is("Some of the identifiers requested were rejected"));
assertThat(problem.getInstance(), is(URI.create("https://example.com/documents/error.html")));
assertThat(problem.getDomain(), is(nullValue()));
assertThat(problem.asJSON().toString(), is(sameJSONAs(original.toString())));
assertThat(problem.toString(), is(sameJSONAs(original.toString())));
List<Problem> subs = problem.getSubProblems();
assertThat(subs, not(nullValue()));
assertThat(subs, hasSize(2));
Problem p1 = subs.get(0);
assertThat(p1.getType(), is(URI.create("urn:ietf:params:acme:error:malformed")));
assertThat(p1.getDetail(), is("Invalid underscore in DNS name \"_example.com\""));
assertThat(p1.getDomain(), is("_example.com"));
Problem p2 = subs.get(1);
assertThat(p2.getType(), is(URI.create("urn:ietf:params:acme:error:rejectedIdentifier")));
assertThat(p2.getDetail(), is("This CA will not issue for \"example.net\""));
assertThat(p2.getDomain(), is("example.net"));
}
}

View File

@ -302,8 +302,6 @@ public final class TestUtils {
* @return Created {@link Problem} object
*/
public static Problem createProblem(URI type, String detail, URL instance) {
URI baseUri = URI.create("https://example.com/acme/1");
JSONBuilder jb = new JSONBuilder();
jb.put("type", type);
jb.put("detail", detail);
@ -311,7 +309,7 @@ public final class TestUtils {
jb.put("instance", instance);
}
return new Problem(jb.toJSON(), baseUri);
return new Problem(jb.toJSON(), url("https://example.com/acme/1"));
}
/**

View File

@ -1,5 +1,23 @@
{
"type": "urn:ietf:params:acme:error:connection",
"detail": "connection refused",
"instance": "/documents/error.html"
"type": "urn:ietf:params:acme:error:malformed",
"detail": "Some of the identifiers requested were rejected",
"instance": "/documents/error.html",
"sub-problems": [
{
"type": "urn:ietf:params:acme:error:malformed",
"value": "Invalid underscore in DNS name \"_example.com\"",
"identifier": {
"type": "dns",
"value": "_example.com"
}
},
{
"type": "urn:ietf:params:acme:error:rejectedIdentifier",
"value": "This CA will not issue for \"example.net\"",
"identifier": {
"type": "dns",
"value": "example.net"
}
}
]
}