Return external account binding key identifier

pull/81/head
Richard Körber 2019-04-28 17:25:47 +02:00
parent d02746156d
commit 210b2aa453
No known key found for this signature in database
GPG Key ID: AAB9FD19C78AA3E0
8 changed files with 72 additions and 6 deletions

View File

@ -54,6 +54,7 @@ public class Account extends AcmeJsonResource {
private static final String KEY_ORDERS = "orders"; private static final String KEY_ORDERS = "orders";
private static final String KEY_CONTACT = "contact"; private static final String KEY_CONTACT = "contact";
private static final String KEY_STATUS = "status"; private static final String KEY_STATUS = "status";
private static final String KEY_EXTERNAL_ACCOUNT_BINDING = "externalAccountBinding";
protected Account(Login login) { protected Account(Login login) {
super(login, login.getAccountLocation()); super(login, login.getAccountLocation());
@ -91,6 +92,30 @@ public class Account extends AcmeJsonResource {
return getJSON().get(KEY_STATUS).asStatus(); 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}. * Returns an {@link Iterator} of all {@link Order} belonging to this {@link Account}.
* <p> * <p>

View File

@ -27,6 +27,7 @@ import java.net.MalformedURLException;
import java.net.URI; import java.net.URI;
import java.net.URISyntaxException; import java.net.URISyntaxException;
import java.net.URL; import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.time.Duration; import java.time.Duration;
import java.time.Instant; import java.time.Instant;
import java.util.Collections; 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}. * Returns the value as {@link Problem}.
* *

View File

@ -13,8 +13,7 @@
*/ */
package org.shredzone.acme4j; package org.shredzone.acme4j;
import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.*;
import static org.hamcrest.Matchers.notNullValue;
import static org.junit.Assert.assertThat; import static org.junit.Assert.assertThat;
import static org.shredzone.acme4j.toolbox.TestUtils.getJSON; import static org.shredzone.acme4j.toolbox.TestUtils.getJSON;
import static org.shredzone.acme4j.toolbox.TestUtils.url; import static org.shredzone.acme4j.toolbox.TestUtils.url;
@ -100,6 +99,8 @@ public class AccountBuilderTest {
Account account = login.getAccount(); Account account = login.getAccount();
assertThat(account.getTermsOfServiceAgreed(), is(true)); assertThat(account.getTermsOfServiceAgreed(), is(true));
assertThat(account.getLocation(), is(locationUrl)); assertThat(account.getLocation(), is(locationUrl));
assertThat(account.hasExternalAccountBinding(), is(false));
assertThat(account.getKeyIdentifier(), is(nullValue()));
provider.close(); provider.close();
} }

View File

@ -115,6 +115,8 @@ public class AccountTest {
assertThat(account.getContacts(), hasSize(1)); assertThat(account.getContacts(), hasSize(1));
assertThat(account.getContacts().get(0), is(URI.create("mailto:foo2@example.com"))); assertThat(account.getContacts().get(0), is(URI.create("mailto:foo2@example.com")));
assertThat(account.getStatus(), is(Status.VALID)); assertThat(account.getStatus(), is(Status.VALID));
assertThat(account.hasExternalAccountBinding(), is(true));
assertThat(account.getKeyIdentifier(), is("NCC-1701"));
Iterator<Order> orderIt = account.getOrders(); Iterator<Order> orderIt = account.getOrders();
assertThat(orderIt, not(nullValue())); assertThat(orderIt, not(nullValue()));

View File

@ -112,7 +112,6 @@ public class JSONBuilderTest {
* Test JWK. * Test JWK.
*/ */
@Test @Test
@SuppressWarnings("unchecked")
public void testKey() throws IOException, JoseException { public void testKey() throws IOException, JoseException {
KeyPair keyPair = TestUtils.createKeyPair(); KeyPair keyPair = TestUtils.createKeyPair();

View File

@ -105,7 +105,7 @@ public class JSONTest {
assertThat(json.keySet(), containsInAnyOrder( assertThat(json.keySet(), containsInAnyOrder(
"text", "number", "boolean", "uri", "url", "date", "array", "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("text"), is(true));
assertThat(json.contains("music"), is(false)); assertThat(json.contains("music"), is(false));
assertThat(json.get("text"), is(notNullValue())); assertThat(json.get("text"), is(notNullValue()));
@ -233,6 +233,9 @@ public class JSONTest {
JSON sub = array.get(3).asObject(); JSON sub = array.get(3).asObject();
assertThat(sub.get("test").asString(), is("ok")); 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); Problem problem = json.get("problem").asProblem(BASE_URL);
assertThat(problem, is(notNullValue())); assertThat(problem, is(notNullValue()));
assertThat(problem.getType(), is(URI.create("urn:ietf:params:acme:error:rateLimited"))); assertThat(problem.getType(), is(URI.create("urn:ietf:params:acme:error:rateLimited")));
@ -294,6 +297,13 @@ public class JSONTest {
// expected // expected
} }
try {
json.get("none").asEncodedObject();
fail("asEncodedObject did not fail");
} catch (AcmeProtocolException ex) {
// expected
}
try { try {
json.get("none").asStatus(); json.get("none").asStatus();
fail("asStatus did not fail"); fail("asStatus did not fail");
@ -344,6 +354,13 @@ public class JSONTest {
// expected // expected
} }
try {
json.get("text").asEncodedObject();
fail("no exception was thrown");
} catch (AcmeProtocolException ex) {
// expected
}
try { try {
json.get("text").asArray(); json.get("text").asArray();
fail("no exception was thrown"); fail("no exception was thrown");

View File

@ -14,5 +14,6 @@
"type": "urn:ietf:params:acme:error:rateLimited", "type": "urn:ietf:params:acme:error:rateLimited",
"detail": "too many requests", "detail": "too many requests",
"instance": "/documents/errors.html" "instance": "/documents/errors.html"
} },
"encoded": "eyJrZXkiOiJ2YWx1ZSJ9"
} }

View File

@ -4,5 +4,10 @@
"mailto:foo2@example.com" "mailto:foo2@example.com"
], ],
"termsOfServiceAgreed": true, "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"
}
} }