From dfb40edc12150fd186f541b813c4743c96c85cb2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Richard=20K=C3=B6rber?= Date: Sun, 30 Jul 2017 15:57:58 +0200 Subject: [PATCH] Challenge can have multiple errors --- .../shredzone/acme4j/challenge/Challenge.java | 30 ++++++++++++++++--- .../acme4j/challenge/ChallengeTest.java | 22 +++++++++++--- .../test/resources/json/genericChallenge.json | 17 +++++++---- 3 files changed, 56 insertions(+), 13 deletions(-) diff --git a/acme4j-client/src/main/java/org/shredzone/acme4j/challenge/Challenge.java b/acme4j-client/src/main/java/org/shredzone/acme4j/challenge/Challenge.java index a168cd59..bf2ff070 100644 --- a/acme4j-client/src/main/java/org/shredzone/acme4j/challenge/Challenge.java +++ b/acme4j-client/src/main/java/org/shredzone/acme4j/challenge/Challenge.java @@ -13,9 +13,13 @@ */ package org.shredzone.acme4j.challenge; +import static java.util.stream.Collectors.toList; + import java.net.HttpURLConnection; import java.net.URL; import java.time.Instant; +import java.util.Collections; +import java.util.List; import java.util.Objects; import org.shredzone.acme4j.AcmeResource; @@ -27,6 +31,7 @@ import org.shredzone.acme4j.exception.AcmeException; import org.shredzone.acme4j.exception.AcmeProtocolException; import org.shredzone.acme4j.exception.AcmeRetryAfterException; import org.shredzone.acme4j.util.JSON; +import org.shredzone.acme4j.util.JSON.Array; import org.shredzone.acme4j.util.JSONBuilder; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -48,7 +53,7 @@ public class Challenge extends AcmeResource { protected static final String KEY_URL = "url"; protected static final String KEY_STATUS = "status"; protected static final String KEY_VALIDATED = "validated"; - protected static final String KEY_ERROR = "error"; + protected static final String KEY_ERRORS = "errors"; private JSON data = JSON.empty(); @@ -120,10 +125,27 @@ public class Challenge extends AcmeResource { } /** - * Returns the reason why the challenge failed, if returned by the server. + * Returns a list of reasons why the challenge has failed in the past, if returned by + * the server. New errors are always appended to the end of the list. */ - public Problem getError() { - return data.get(KEY_ERROR).asProblem(getLocation()); + public List getErrors() { + URL location = getLocation(); + return Collections.unmodifiableList(data.get(KEY_ERRORS).asArray().stream() + .map(it -> it.asProblem(location)) + .collect(toList())); + } + + /** + * Returns the last reason why the challenge has failed, if returned by the server. + * {@code null} if there are no errors. + */ + public Problem getLastError() { + Array errors = data.get(KEY_ERRORS).asArray(); + if (!errors.isEmpty()) { + return errors.get(errors.size() - 1).asProblem(getLocation()); + } else { + return null; + } } /** diff --git a/acme4j-client/src/test/java/org/shredzone/acme4j/challenge/ChallengeTest.java b/acme4j-client/src/test/java/org/shredzone/acme4j/challenge/ChallengeTest.java index 5337aa0a..70e3f478 100644 --- a/acme4j-client/src/test/java/org/shredzone/acme4j/challenge/ChallengeTest.java +++ b/acme4j-client/src/test/java/org/shredzone/acme4j/challenge/ChallengeTest.java @@ -26,10 +26,12 @@ import java.net.URISyntaxException; import java.net.URL; import java.time.Duration; import java.time.Instant; +import java.util.List; import org.jose4j.lang.JoseException; import org.junit.Before; import org.junit.Test; +import org.shredzone.acme4j.Problem; import org.shredzone.acme4j.Session; import org.shredzone.acme4j.Status; import org.shredzone.acme4j.exception.AcmeException; @@ -111,14 +113,26 @@ public class ChallengeTest { assertThat(challenge.getStatus(), is(Status.INVALID)); assertThat(challenge.getLocation(), is(url("http://example.com/challenge/123"))); assertThat(challenge.getValidated(), is(parseTimestamp("2015-12-12T17:19:36.336785823Z"))); - assertThat(challenge.getError(), is(notNullValue())); - assertThat(challenge.getError().getType(), is(URI.create("urn:ietf:params:acme:error:connection"))); - assertThat(challenge.getError().getDetail(), is("connection refused")); - assertThat(challenge.getError().getInstance(), is(URI.create("http://example.com/documents/error.html"))); assertThat(challenge.getJSON().get("type").asString(), is("generic-01")); assertThat(challenge.getJSON().get("url").asURL(), is(url("http://example.com/challenge/123"))); assertThat(challenge.getJSON().get("not-present").asString(), is(nullValue())); assertThat(challenge.getJSON().get("not-present-url").asURL(), is(nullValue())); + + List errors = challenge.getErrors(); + assertThat(errors, is(notNullValue())); + assertThat(errors, hasSize(2)); + assertThat(errors.get(0).getType(), is(URI.create("urn:ietf:params:acme:error:connection"))); + assertThat(errors.get(0).getDetail(), is("connection refused")); + assertThat(errors.get(0).getInstance(), is(URI.create("http://example.com/documents/error.html"))); + assertThat(errors.get(1).getType(), is(URI.create("urn:ietf:params:acme:error:incorrectResponse"))); + assertThat(errors.get(1).getDetail(), is("bad token")); + assertThat(errors.get(1).getInstance(), is(URI.create("http://example.com/documents/faq.html"))); + + Problem lastError = challenge.getLastError(); + assertThat(lastError, is(notNullValue())); + assertThat(lastError.getType(), is(URI.create("urn:ietf:params:acme:error:incorrectResponse"))); + assertThat(lastError.getDetail(), is("bad token")); + assertThat(lastError.getInstance(), is(URI.create("http://example.com/documents/faq.html"))); } /** diff --git a/acme4j-client/src/test/resources/json/genericChallenge.json b/acme4j-client/src/test/resources/json/genericChallenge.json index 4e4572e0..055143b4 100644 --- a/acme4j-client/src/test/resources/json/genericChallenge.json +++ b/acme4j-client/src/test/resources/json/genericChallenge.json @@ -3,9 +3,16 @@ "status": "invalid", "url": "http://example.com/challenge/123", "validated": "2015-12-12T17:19:36.336785823Z", - "error": { - "type": "urn:ietf:params:acme:error:connection", - "detail": "connection refused", - "instance": "/documents/error.html" - } + "errors": [ + { + "type": "urn:ietf:params:acme:error:connection", + "detail": "connection refused", + "instance": "/documents/error.html" + }, + { + "type": "urn:ietf:params:acme:error:incorrectResponse", + "detail": "bad token", + "instance": "/documents/faq.html" + } + ] }