From c72a41c7cfdde251de769b9e06031b68e960501b Mon Sep 17 00:00:00 2001 From: Luca Figoli Date: Wed, 2 Dec 2020 18:09:50 +0100 Subject: [PATCH] Add methods to request access token synchronous --- .../java/net/openid/appauth/AuthState.java | 145 ++++++++++++++++++ .../openid/appauth/AuthorizationService.java | 73 ++++++--- 2 files changed, 193 insertions(+), 25 deletions(-) diff --git a/library/java/net/openid/appauth/AuthState.java b/library/java/net/openid/appauth/AuthState.java index 6ef874bb..d14dce27 100644 --- a/library/java/net/openid/appauth/AuthState.java +++ b/library/java/net/openid/appauth/AuthState.java @@ -447,6 +447,128 @@ public void update(@Nullable RegistrationResponse regResponse) { mAuthorizationException = null; } + /** + * Ensures that a non-expired access token is available before invoking the provided action. + * @return + */ + @NonNull + public Tokens getSynchronousFreshToken( + @NonNull AuthorizationService service) throws AuthorizationException { + return getSynchronousFreshToken( + service, + NoClientAuthentication.INSTANCE, + Collections.emptyMap(), + SystemClock.INSTANCE); + } + + /** + * Ensures that a non-expired access token is available before invoking the provided action. + * @return + */ + @NonNull + public Tokens getSynchronousFreshToken( + @NonNull AuthorizationService service, + @NonNull ClientAuthentication clientAuth) throws AuthorizationException { + return getSynchronousFreshToken( + service, + clientAuth, + Collections.emptyMap(), + SystemClock.INSTANCE); + } + + /** + * Ensures that a non-expired access token is available before invoking the provided action. + * If a token refresh is required, the provided additional parameters will be included in this + * refresh request. + * @return + */ + @NonNull + public Tokens getSynchronousFreshToken( + @NonNull AuthorizationService service, + @NonNull Map refreshTokenAdditionalParams) throws ClientAuthentication.UnsupportedAuthenticationMethod, AuthorizationException { + return getSynchronousFreshToken( + service, + getClientAuthentication(), + refreshTokenAdditionalParams, + SystemClock.INSTANCE); + } + + /** + * Ensures that a non-expired access token is available before invoking the provided action. + * If a token refresh is required, the provided additional parameters will be included in this + * refresh request. + * @return + */ + @NonNull + public Tokens getSynchronousFreshToken( + @NonNull AuthorizationService service, + @NonNull ClientAuthentication clientAuth, + @NonNull Map refreshTokenAdditionalParams) throws AuthorizationException { + return getSynchronousFreshToken( + service, + clientAuth, + refreshTokenAdditionalParams, + SystemClock.INSTANCE); + } + + @VisibleForTesting + @NonNull + Tokens getSynchronousFreshToken( + @NonNull final AuthorizationService service, + @NonNull final ClientAuthentication clientAuth, + @NonNull final Map refreshTokenAdditionalParams, + @NonNull final Clock clock) throws AuthorizationException { + checkNotNull(service, "service cannot be null"); + checkNotNull(clientAuth, "client authentication cannot be null"); + checkNotNull(refreshTokenAdditionalParams, + "additional params cannot be null"); + checkNotNull(clock, "clock cannot be null"); + + if (!getNeedsTokenRefresh(clock)) { + String accessToken = getAccessToken(); + String idToken = getIdToken(); + if (accessToken != null && idToken != null) { + return new Tokens(accessToken, idToken); + } + } + + if (mRefreshToken == null) { + throw AuthorizationException.fromTemplate( + AuthorizationRequestErrors.CLIENT_ERROR, + new IllegalStateException("No refresh token available and token have expired")); + } + + TokenResponse response = null; + AuthorizationException exception = null; + try { + response = service.performSynchronousTokenRequest(createTokenRefreshRequest(refreshTokenAdditionalParams), clientAuth); + } catch (AuthorizationException e) { + exception = e; + } + + update(response, exception); + + if (response != null) { + String accessToken = getAccessToken(); + String idToken = getIdToken(); + if (accessToken != null && idToken != null) { + return new Tokens(accessToken, idToken); + } else { + exception = AuthorizationException.fromTemplate( + AuthorizationException.GeneralErrors.JSON_DESERIALIZATION_ERROR, + new Exception("")); + } + } + + if (exception != null) { + throw exception; + } else { + throw AuthorizationException.fromTemplate( + AuthorizationException.GeneralErrors.JSON_DESERIALIZATION_ERROR, + new Exception("")); + } + } + /** * Ensures that a non-expired access token is available before invoking the provided action. */ @@ -743,6 +865,29 @@ void execute( @Nullable AuthorizationException ex); } + public static class Tokens { + /** + * Result of the synchronous function that return accessToken + */ + private @NonNull final String accessToken; + private @NonNull final String idToken; + + public Tokens(@NonNull String accessToken, @NonNull String idToken) { + this.accessToken = accessToken; + this.idToken = idToken; + } + + @NonNull + public String getAccessToken() { + return accessToken; + } + + @NonNull + public String getIdToken() { + return idToken; + } + } + /** * Creates the required client authentication for the token endpoint based on information * in the most recent registration response (if it is set). diff --git a/library/java/net/openid/appauth/AuthorizationService.java b/library/java/net/openid/appauth/AuthorizationService.java index 2d5ae480..25b5b666 100644 --- a/library/java/net/openid/appauth/AuthorizationService.java +++ b/library/java/net/openid/appauth/AuthorizationService.java @@ -328,6 +328,27 @@ public void performTokenRequest( .execute(); } + /** + * Sends a request to the authorization service to exchange a code granted as part of an + * authorization request for a token. The result of this request will be sent to the provided + * callback handler. + * @return TokenResponse + */ + public TokenResponse performSynchronousTokenRequest( + @NonNull TokenRequest request, + @NonNull ClientAuthentication clientAuthentication) throws AuthorizationException { + checkNotDisposed(); + Logger.debug("Initiating code exchange request to %s", + request.configuration.tokenEndpoint); + TokenRequestTask tokenRequest = new TokenRequestTask( + request, + clientAuthentication, + mClientConfiguration.getConnectionBuilder(), + SystemClock.INSTANCE, null); + JSONObject json = tokenRequest.doInBackground(); + return tokenRequest.parseJson(json); + } + /** * Sends a request to the authorization service to dynamically register a client. * The result of this request will be sent to the provided callback handler. @@ -473,9 +494,17 @@ protected JSONObject doInBackground(Void... voids) { @Override protected void onPostExecute(JSONObject json) { + try { + TokenResponse tokenResponse = parseJson(json); + mCallback.onTokenRequestCompleted(tokenResponse, null); + } catch (AuthorizationException authorizationException) { + mCallback.onTokenRequestCompleted(null, authorizationException); + } + } + + public TokenResponse parseJson(JSONObject json) throws AuthorizationException { if (mException != null) { - mCallback.onTokenRequestCompleted(null, mException); - return; + throw mException; } if (json.has(AuthorizationException.PARAM_ERROR)) { @@ -483,29 +512,26 @@ protected void onPostExecute(JSONObject json) { try { String error = json.getString(AuthorizationException.PARAM_ERROR); ex = AuthorizationException.fromOAuthTemplate( - TokenRequestErrors.byString(error), - error, - json.optString(AuthorizationException.PARAM_ERROR_DESCRIPTION, null), - UriUtil.parseUriIfAvailable( - json.optString(AuthorizationException.PARAM_ERROR_URI))); + TokenRequestErrors.byString(error), + error, + json.optString(AuthorizationException.PARAM_ERROR_DESCRIPTION, null), + UriUtil.parseUriIfAvailable( + json.optString(AuthorizationException.PARAM_ERROR_URI))); } catch (JSONException jsonEx) { ex = AuthorizationException.fromTemplate( - GeneralErrors.JSON_DESERIALIZATION_ERROR, - jsonEx); + GeneralErrors.JSON_DESERIALIZATION_ERROR, + jsonEx); } - mCallback.onTokenRequestCompleted(null, ex); - return; + throw ex; } TokenResponse response; try { response = new TokenResponse.Builder(mRequest).fromResponseJson(json).build(); } catch (JSONException jsonEx) { - mCallback.onTokenRequestCompleted(null, - AuthorizationException.fromTemplate( - GeneralErrors.JSON_DESERIALIZATION_ERROR, - jsonEx)); - return; + throw AuthorizationException.fromTemplate( + GeneralErrors.JSON_DESERIALIZATION_ERROR, + jsonEx); } if (response.idToken != null) { @@ -513,23 +539,20 @@ protected void onPostExecute(JSONObject json) { try { idToken = IdToken.from(response.idToken); } catch (IdTokenException | JSONException ex) { - mCallback.onTokenRequestCompleted(null, - AuthorizationException.fromTemplate( - GeneralErrors.ID_TOKEN_PARSING_ERROR, - ex)); - return; + throw AuthorizationException.fromTemplate( + GeneralErrors.ID_TOKEN_PARSING_ERROR, + ex); } try { idToken.validate(mRequest, mClock); } catch (AuthorizationException ex) { - mCallback.onTokenRequestCompleted(null, ex); - return; + throw ex; } } Logger.debug("Token exchange with %s completed", - mRequest.configuration.tokenEndpoint); - mCallback.onTokenRequestCompleted(response, null); + mRequest.configuration.tokenEndpoint); + return response; } /**