From 210b2aa453786c868dcd74a3063e4008ca8925b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Richard=20K=C3=B6rber?= Date: Sun, 28 Apr 2019 17:25:47 +0200 Subject: [PATCH] Return external account binding key identifier --- .../java/org/shredzone/acme4j/Account.java | 25 +++++++++++++++++++ .../org/shredzone/acme4j/toolbox/JSON.java | 16 ++++++++++++ .../shredzone/acme4j/AccountBuilderTest.java | 5 ++-- .../org/shredzone/acme4j/AccountTest.java | 2 ++ .../acme4j/toolbox/JSONBuilderTest.java | 1 - .../shredzone/acme4j/toolbox/JSONTest.java | 19 +++++++++++++- .../src/test/resources/json/datatypes.json | 3 ++- .../resources/json/updateAccountResponse.json | 7 +++++- 8 files changed, 72 insertions(+), 6 deletions(-) diff --git a/acme4j-client/src/main/java/org/shredzone/acme4j/Account.java b/acme4j-client/src/main/java/org/shredzone/acme4j/Account.java index b5307ec2..60c20fb7 100644 --- a/acme4j-client/src/main/java/org/shredzone/acme4j/Account.java +++ b/acme4j-client/src/main/java/org/shredzone/acme4j/Account.java @@ -54,6 +54,7 @@ public class Account extends AcmeJsonResource { private static final String KEY_ORDERS = "orders"; private static final String KEY_CONTACT = "contact"; private static final String KEY_STATUS = "status"; + private static final String KEY_EXTERNAL_ACCOUNT_BINDING = "externalAccountBinding"; protected Account(Login login) { super(login, login.getAccountLocation()); @@ -91,6 +92,30 @@ public class Account extends AcmeJsonResource { return getJSON().get(KEY_STATUS).asStatus(); } + /** + * Returns {@code true} if the account is bound to an external non-ACME account. + * + * @since 2.8 + */ + public boolean hasExternalAccountBinding() { + return getJSON().contains(KEY_EXTERNAL_ACCOUNT_BINDING); + } + + /** + * Returns the key identifier of the external non-ACME account. If this account is + * not bound to an external account, {@code null} is returned instead. + * + * @since 2.8 + */ + @CheckForNull + public String getKeyIdentifier() { + return getJSON().get(KEY_EXTERNAL_ACCOUNT_BINDING) + .optional().map(Value::asObject) + .map(j -> j.get("protected")).map(Value::asEncodedObject) + .map(j -> j.get("kid")).map(Value::asString) + .orElse(null); + } + /** * Returns an {@link Iterator} of all {@link Order} belonging to this {@link Account}. *

diff --git a/acme4j-client/src/main/java/org/shredzone/acme4j/toolbox/JSON.java b/acme4j-client/src/main/java/org/shredzone/acme4j/toolbox/JSON.java index 8e31577d..3dfe47d3 100644 --- a/acme4j-client/src/main/java/org/shredzone/acme4j/toolbox/JSON.java +++ b/acme4j-client/src/main/java/org/shredzone/acme4j/toolbox/JSON.java @@ -27,6 +27,7 @@ import java.net.MalformedURLException; import java.net.URI; import java.net.URISyntaxException; import java.net.URL; +import java.nio.charset.StandardCharsets; import java.time.Duration; import java.time.Instant; import java.util.Collections; @@ -347,6 +348,21 @@ public final class JSON implements Serializable { } } + /** + * Returns the value as JSON object that was Base64 URL encoded. + * + * @since 2.8 + */ + public JSON asEncodedObject() { + required(); + try { + byte[] raw = AcmeUtils.base64UrlDecode(val.toString()); + return new JSON(path, JsonUtil.parseJson(new String(raw, StandardCharsets.UTF_8))); + } catch (IllegalArgumentException | JoseException ex) { + throw new AcmeProtocolException(path + ": expected an encoded object", ex); + } + } + /** * Returns the value as {@link Problem}. * diff --git a/acme4j-client/src/test/java/org/shredzone/acme4j/AccountBuilderTest.java b/acme4j-client/src/test/java/org/shredzone/acme4j/AccountBuilderTest.java index dadbcc7f..7a59346b 100644 --- a/acme4j-client/src/test/java/org/shredzone/acme4j/AccountBuilderTest.java +++ b/acme4j-client/src/test/java/org/shredzone/acme4j/AccountBuilderTest.java @@ -13,8 +13,7 @@ */ package org.shredzone.acme4j; -import static org.hamcrest.Matchers.is; -import static org.hamcrest.Matchers.notNullValue; +import static org.hamcrest.Matchers.*; import static org.junit.Assert.assertThat; import static org.shredzone.acme4j.toolbox.TestUtils.getJSON; import static org.shredzone.acme4j.toolbox.TestUtils.url; @@ -100,6 +99,8 @@ public class AccountBuilderTest { Account account = login.getAccount(); assertThat(account.getTermsOfServiceAgreed(), is(true)); assertThat(account.getLocation(), is(locationUrl)); + assertThat(account.hasExternalAccountBinding(), is(false)); + assertThat(account.getKeyIdentifier(), is(nullValue())); provider.close(); } diff --git a/acme4j-client/src/test/java/org/shredzone/acme4j/AccountTest.java b/acme4j-client/src/test/java/org/shredzone/acme4j/AccountTest.java index b74a229b..56fd1f21 100644 --- a/acme4j-client/src/test/java/org/shredzone/acme4j/AccountTest.java +++ b/acme4j-client/src/test/java/org/shredzone/acme4j/AccountTest.java @@ -115,6 +115,8 @@ public class AccountTest { assertThat(account.getContacts(), hasSize(1)); assertThat(account.getContacts().get(0), is(URI.create("mailto:foo2@example.com"))); assertThat(account.getStatus(), is(Status.VALID)); + assertThat(account.hasExternalAccountBinding(), is(true)); + assertThat(account.getKeyIdentifier(), is("NCC-1701")); Iterator orderIt = account.getOrders(); assertThat(orderIt, not(nullValue())); diff --git a/acme4j-client/src/test/java/org/shredzone/acme4j/toolbox/JSONBuilderTest.java b/acme4j-client/src/test/java/org/shredzone/acme4j/toolbox/JSONBuilderTest.java index b70c6b09..a357c7d0 100644 --- a/acme4j-client/src/test/java/org/shredzone/acme4j/toolbox/JSONBuilderTest.java +++ b/acme4j-client/src/test/java/org/shredzone/acme4j/toolbox/JSONBuilderTest.java @@ -112,7 +112,6 @@ public class JSONBuilderTest { * Test JWK. */ @Test - @SuppressWarnings("unchecked") public void testKey() throws IOException, JoseException { KeyPair keyPair = TestUtils.createKeyPair(); diff --git a/acme4j-client/src/test/java/org/shredzone/acme4j/toolbox/JSONTest.java b/acme4j-client/src/test/java/org/shredzone/acme4j/toolbox/JSONTest.java index 0b65a6f7..4e312971 100644 --- a/acme4j-client/src/test/java/org/shredzone/acme4j/toolbox/JSONTest.java +++ b/acme4j-client/src/test/java/org/shredzone/acme4j/toolbox/JSONTest.java @@ -105,7 +105,7 @@ public class JSONTest { assertThat(json.keySet(), containsInAnyOrder( "text", "number", "boolean", "uri", "url", "date", "array", - "collect", "status", "binary", "duration", "problem")); + "collect", "status", "binary", "duration", "problem", "encoded")); assertThat(json.contains("text"), is(true)); assertThat(json.contains("music"), is(false)); assertThat(json.get("text"), is(notNullValue())); @@ -233,6 +233,9 @@ public class JSONTest { JSON sub = array.get(3).asObject(); assertThat(sub.get("test").asString(), is("ok")); + JSON encodedSub = json.get("encoded").asEncodedObject(); + assertThat(encodedSub.toString(), is(sameJSONAs("{\"key\":\"value\"}"))); + Problem problem = json.get("problem").asProblem(BASE_URL); assertThat(problem, is(notNullValue())); assertThat(problem.getType(), is(URI.create("urn:ietf:params:acme:error:rateLimited"))); @@ -294,6 +297,13 @@ public class JSONTest { // expected } + try { + json.get("none").asEncodedObject(); + fail("asEncodedObject did not fail"); + } catch (AcmeProtocolException ex) { + // expected + } + try { json.get("none").asStatus(); fail("asStatus did not fail"); @@ -344,6 +354,13 @@ public class JSONTest { // expected } + try { + json.get("text").asEncodedObject(); + fail("no exception was thrown"); + } catch (AcmeProtocolException ex) { + // expected + } + try { json.get("text").asArray(); fail("no exception was thrown"); diff --git a/acme4j-client/src/test/resources/json/datatypes.json b/acme4j-client/src/test/resources/json/datatypes.json index 695895ef..8ccaa7e3 100644 --- a/acme4j-client/src/test/resources/json/datatypes.json +++ b/acme4j-client/src/test/resources/json/datatypes.json @@ -14,5 +14,6 @@ "type": "urn:ietf:params:acme:error:rateLimited", "detail": "too many requests", "instance": "/documents/errors.html" - } + }, + "encoded": "eyJrZXkiOiJ2YWx1ZSJ9" } diff --git a/acme4j-client/src/test/resources/json/updateAccountResponse.json b/acme4j-client/src/test/resources/json/updateAccountResponse.json index 97dfbe82..b77a937e 100644 --- a/acme4j-client/src/test/resources/json/updateAccountResponse.json +++ b/acme4j-client/src/test/resources/json/updateAccountResponse.json @@ -4,5 +4,10 @@ "mailto:foo2@example.com" ], "termsOfServiceAgreed": true, - "orders": "https://example.com/acme/acct/1/orders" + "orders": "https://example.com/acme/acct/1/orders", + "externalAccountBinding": { + "protected": "eyJ1cmwiOiJodHRwOi8vZXhhbXBsZS5jb20vYWNtZS9yZXNvdXJjZSIsImtpZCI6Ik5DQy0xNzAxIiwiYWxnIjoiSFMyNTYifQ", + "payload": "eyJrdHkiOiJSU0EiLCJuIjoicFpzVEtZNDF5X0N3Z0owVlg3Qm1tR3NfN1Vwcm1YUU1HUGNuU2JCZUpBalpIQTlTeXlKS2FXdjRmTlVkQklBWDNZMlFvWml4ajUwblFMeUx2Mm5nM3B2RW9STDBzeDlaSGdwNW5kQWpwSWlWUV84VjAxVFRZQ0VEVWM5aWk3YmpWa2dGQWI0VmFsWkdGSlo1NFBjQ25BSHZYaTVnMEVMT1J6R2NUdVJxSFZBVWNrTVYyb3RyMGcwdV81YldNbTZFTUFiQnJHUUNnVUdqYlpRSGphdmExWS01dEhYWmtQQmFoSjJMdktScU1tSlVscjBhbkt1Skp0SlVHMDNESllBeEFCdjhZQWFYRkJuR3c2a0tKUnBVRkFDNTVyeTRzcDRrR3kwTnJLMlRWV21aVzlrU3RuaVJ2NFJhSkdJOWFaR1l3UXkya1V5a2liQk5tV0VRVWxJd0l3IiwiZSI6IkFRQUIifQ", + "signature": "skPdpjTgx8zIGsNRtvv4zNlfp-uidFDgCMY3Z3ONLgw" + } }