diff --git a/library/java/net/openid/appauth/AuthorizationService.java b/library/java/net/openid/appauth/AuthorizationService.java index 3fd5c054..12edb63e 100644 --- a/library/java/net/openid/appauth/AuthorizationService.java +++ b/library/java/net/openid/appauth/AuthorizationService.java @@ -346,6 +346,27 @@ public void performRegistrationRequest( .execute(); } + /** + * Sends a request to the authorization service to dynamically register a client. + * The request is authorized by the provided initial access token, + * for OAuth 2.0 protected dynamic client registration endpoints. + * The result of this request will be sent to the provided callback handler. + */ + public void performRegistrationRequest( + @NonNull RegistrationRequest request, + @NonNull RegistrationResponseCallback callback, + @NonNull String initialAccessToken) { + checkNotDisposed(); + Logger.debug("Initiating dynamic client registration %s", + request.configuration.registrationEndpoint.toString()); + new RegistrationRequestTask( + request, + mClientConfiguration.getConnectionBuilder(), + initialAccessToken, + callback) + .execute(); + } + /** * Disposes state that will not normally be handled by garbage collection. This should be * called when the authorization service is no longer required, including when any owning @@ -573,14 +594,23 @@ private static class RegistrationRequestTask private RegistrationRequest mRequest; private final ConnectionBuilder mConnectionBuilder; private RegistrationResponseCallback mCallback; + @Nullable private String mInitialAccessToken; private AuthorizationException mException; RegistrationRequestTask(RegistrationRequest request, ConnectionBuilder connectionBuilder, RegistrationResponseCallback callback) { + this(request, connectionBuilder, null, callback); + } + + RegistrationRequestTask(RegistrationRequest request, + ConnectionBuilder connectionBuilder, + @Nullable String initialAccessToken, + RegistrationResponseCallback callback) { mRequest = request; mConnectionBuilder = connectionBuilder; + mInitialAccessToken = initialAccessToken; mCallback = callback; } @@ -595,6 +625,9 @@ protected JSONObject doInBackground(Void... voids) { conn.setRequestProperty("Content-Type", "application/json"); conn.setDoOutput(true); conn.setRequestProperty("Content-Length", String.valueOf(postData.length())); + if (mInitialAccessToken != null) { + conn.setRequestProperty("Authorization", "Bearer " + mInitialAccessToken); + } OutputStreamWriter wr = new OutputStreamWriter(conn.getOutputStream()); wr.write(postData); wr.flush(); diff --git a/library/javatests/net/openid/appauth/AuthorizationServiceTest.java b/library/javatests/net/openid/appauth/AuthorizationServiceTest.java index 078c5046..c05b3163 100644 --- a/library/javatests/net/openid/appauth/AuthorizationServiceTest.java +++ b/library/javatests/net/openid/appauth/AuthorizationServiceTest.java @@ -93,6 +93,13 @@ public class AuthorizationServiceTest { private static final int TEST_EXPIRES_IN = 3600; private static final String TEST_BROWSER_PACKAGE = "com.browser.test"; + private static final String INITIAL_ACCESS_TOKEN = "initial-access-token"; + private static final String UNAUTHORIZED_REQUEST = "{\n" + + " \"error\": \"invalid_token\",\n" + + " \"error_uri\": \"https://human-readable-error-description\",\n" + + " \"error_description\": \"token-unknown\"\n" + + "}"; + private static final String REGISTRATION_RESPONSE_JSON = "{\n" + " \"client_id\": \"" + TEST_CLIENT_ID + "\",\n" + " \"client_secret\": \"" + TEST_CLIENT_SECRET + "\",\n" @@ -410,6 +417,40 @@ public void testRegistrationRequest_IoException() throws Exception { assertEquals(GeneralErrors.NETWORK_ERROR, mRegistrationCallback.error); } + @Test + public void testRegistrationRequestWithInitialAccessToken() throws Exception { + InputStream is = new ByteArrayInputStream(REGISTRATION_RESPONSE_JSON.getBytes()); + when(mHttpConnection.getInputStream()).thenReturn(is); + RegistrationRequest request = getTestRegistrationRequest(); + mService.performRegistrationRequest(request, mRegistrationCallback, INITIAL_ACCESS_TOKEN); + mRegistrationCallback.waitForCallback(); + assertRegistrationResponse(mRegistrationCallback.response, request); + String postBody = mOutputStream.toString(); + assertThat(postBody).isEqualTo(request.toJsonString()); + } + + @Test + public void testRegistrationRequestWithInitialAccessToken_Unauthorized() throws Exception { + InputStream is = new ByteArrayInputStream(UNAUTHORIZED_REQUEST.getBytes()); + when(mHttpConnection.getInputStream()).thenReturn(is); + RegistrationRequest request = getTestRegistrationRequest(); + mService.performRegistrationRequest(request, mRegistrationCallback, ""); + mRegistrationCallback.waitForCallback(); + assertNotNull(mRegistrationCallback.error); + String postBody = mOutputStream.toString(); + assertThat(postBody).isEqualTo(request.toJsonString()); + } + + @Test + public void testRegistrationRequestWithInitialAccessToken_IoException() throws Exception { + Exception ex = new IOException(); + when(mHttpConnection.getInputStream()).thenThrow(ex); + mService.performRegistrationRequest(getTestRegistrationRequest(), mRegistrationCallback, INITIAL_ACCESS_TOKEN); + mRegistrationCallback.waitForCallback(); + assertNotNull(mRegistrationCallback.error); + assertEquals(GeneralErrors.NETWORK_ERROR, mRegistrationCallback.error); + } + @Test(expected = IllegalStateException.class) public void testTokenRequest_afterDispose() throws Exception { mService.dispose();