001/* 002 * Copyright 2015 The AppAuth for Android Authors. All Rights Reserved. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 005 * in compliance with the License. You may obtain a copy of the License at 006 * 007 * http://www.apache.org/licenses/LICENSE-2.0 008 * 009 * Unless required by applicable law or agreed to in writing, software distributed under the 010 * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 011 * express or implied. See the License for the specific language governing permissions and 012 * limitations under the License. 013 */ 014 015package net.openid.appauth; 016 017import static net.openid.appauth.AuthorizationException.AuthorizationRequestErrors; 018import static net.openid.appauth.Preconditions.checkArgument; 019import static net.openid.appauth.Preconditions.checkNotEmpty; 020import static net.openid.appauth.Preconditions.checkNotNull; 021 022import androidx.annotation.NonNull; 023import androidx.annotation.Nullable; 024import androidx.annotation.VisibleForTesting; 025 026import net.openid.appauth.internal.Logger; 027import org.json.JSONException; 028import org.json.JSONObject; 029 030import java.util.ArrayList; 031import java.util.Collections; 032import java.util.List; 033import java.util.Map; 034import java.util.Set; 035 036/** 037 * Collects authorization state from authorization requests and responses. This facilitates 038 * the creation of subsequent requests based on this state, and allows for this state to be 039 * persisted easily. 040 */ 041public class AuthState { 042 043 /** 044 * Tokens which have less time than this value left before expiry will be considered to be 045 * expired for the purposes of calls to 046 * {@link #performActionWithFreshTokens(AuthorizationService, AuthStateAction) 047 * performActionWithFreshTokens}. 048 */ 049 public static final int EXPIRY_TIME_TOLERANCE_MS = 60000; 050 051 private static final String KEY_CONFIG = "config"; 052 private static final String KEY_REFRESH_TOKEN = "refreshToken"; 053 private static final String KEY_SCOPE = "scope"; 054 private static final String KEY_LAST_AUTHORIZATION_RESPONSE = "lastAuthorizationResponse"; 055 private static final String KEY_LAST_TOKEN_RESPONSE = "mLastTokenResponse"; 056 private static final String KEY_AUTHORIZATION_EXCEPTION = "mAuthorizationException"; 057 private static final String KEY_LAST_REGISTRATION_RESPONSE = "lastRegistrationResponse"; 058 059 @Nullable 060 private String mRefreshToken; 061 062 @Nullable 063 private String mScope; 064 065 @Nullable 066 private AuthorizationServiceConfiguration mConfig; 067 068 @Nullable 069 private AuthorizationResponse mLastAuthorizationResponse; 070 071 @Nullable 072 private TokenResponse mLastTokenResponse; 073 074 @Nullable 075 private RegistrationResponse mLastRegistrationResponse; 076 077 @Nullable 078 private AuthorizationException mAuthorizationException; 079 080 private final Object mPendingActionsSyncObject = new Object(); 081 private List<AuthStateAction> mPendingActions; 082 private boolean mNeedsTokenRefreshOverride; 083 084 /** 085 * Creates an empty, unauthenticated {@link AuthState}. 086 */ 087 public AuthState() {} 088 089 /** 090 * Creates an unauthenticated {@link AuthState}, with the service configuration retained 091 * for convenience. 092 */ 093 public AuthState(@NonNull AuthorizationServiceConfiguration config) { 094 mConfig = config; 095 } 096 097 /** 098 * Creates an {@link AuthState} based on an authorization exchange. 099 */ 100 public AuthState(@Nullable AuthorizationResponse authResponse, 101 @Nullable AuthorizationException authError) { 102 checkArgument(authResponse != null ^ authError != null, 103 "exactly one of authResponse or authError should be non-null"); 104 mPendingActions = null; 105 update(authResponse, authError); 106 } 107 108 /** 109 * Creates an {@link AuthState} based on a dynamic registration client registration request. 110 */ 111 public AuthState(@NonNull RegistrationResponse regResponse) { 112 update(regResponse); 113 } 114 115 /** 116 * Creates an {@link AuthState} based on an authorization exchange and subsequent token 117 * exchange. 118 */ 119 public AuthState( 120 @NonNull AuthorizationResponse authResponse, 121 @Nullable TokenResponse tokenResponse, 122 @Nullable AuthorizationException authException) { 123 this(authResponse, null); 124 update(tokenResponse, authException); 125 } 126 127 /** 128 * The most recent refresh token received from the server, if available. Rather than using 129 * this property directly as part of any request depending on authorization state, it is 130 * recommended to call {@link #performActionWithFreshTokens(AuthorizationService, 131 * AuthStateAction) performActionWithFreshTokens} to ensure that fresh tokens are available. 132 */ 133 @Nullable 134 public String getRefreshToken() { 135 return mRefreshToken; 136 } 137 138 /** 139 * The scope of the current authorization grant. This represents the latest scope returned by 140 * the server and may be a subset of the scope that was initially granted. 141 */ 142 @Nullable 143 public String getScope() { 144 return mScope; 145 } 146 147 /** 148 * A set representation of {@link #getScope()}, for convenience. 149 */ 150 @Nullable 151 public Set<String> getScopeSet() { 152 return AsciiStringListUtil.stringToSet(mScope); 153 } 154 155 /** 156 * The most recent authorization response used to update the authorization state. For the 157 * implicit flow, this will contain the latest access token. It is rarely necessary to 158 * directly use the response; instead convenience methods are provided to retrieve the 159 * {@link #getAccessToken() access token}, 160 * {@link #getAccessTokenExpirationTime() access token expiration}, 161 * {@link #getIdToken() ID token} 162 * and {@link #getScopeSet() scope} regardless of the flow used to retrieve them. 163 */ 164 @Nullable 165 public AuthorizationResponse getLastAuthorizationResponse() { 166 return mLastAuthorizationResponse; 167 } 168 169 /** 170 * The most recent token response used to update this authorization state. For the 171 * authorization code flow, this will contain the latest access token. It is rarely necessary 172 * to directly use the response; instead convenience methods are provided to retrieve the 173 * {@link #getAccessToken() access token}, 174 * {@link #getAccessTokenExpirationTime() access token expiration}, 175 * {@link #getIdToken() ID token} 176 * and {@link #getScopeSet() scope} regardless of the flow used to retrieve them. 177 */ 178 @Nullable 179 public TokenResponse getLastTokenResponse() { 180 return mLastTokenResponse; 181 } 182 183 /** 184 * The most recent client registration response used to update this authorization state. 185 * 186 * <p> 187 * It is rarely necessary to directly use the response; instead convenience methods are provided 188 * to retrieve the {@link #getClientSecret() client secret} and 189 * {@link #getClientSecretExpirationTime() client secret expiration}. 190 * </p> 191 */ 192 @Nullable 193 public RegistrationResponse getLastRegistrationResponse() { 194 return mLastRegistrationResponse; 195 } 196 197 /** 198 * The configuration of the authorization service associated with this authorization state. 199 */ 200 @Nullable 201 public AuthorizationServiceConfiguration getAuthorizationServiceConfiguration() { 202 if (mLastAuthorizationResponse != null) { 203 return mLastAuthorizationResponse.request.configuration; 204 } 205 206 return mConfig; 207 } 208 209 /** 210 * The current access token, if available. Rather than using 211 * this property directly as part of any request depending on authorization state, it s 212 * recommended to call {@link #performActionWithFreshTokens(AuthorizationService, 213 * AuthStateAction) performActionWithFreshTokens} to ensure that fresh tokens are available. 214 */ 215 @Nullable 216 public String getAccessToken() { 217 if (mAuthorizationException != null) { 218 return null; 219 } 220 221 if (mLastTokenResponse != null && mLastTokenResponse.accessToken != null) { 222 return mLastTokenResponse.accessToken; 223 } 224 225 if (mLastAuthorizationResponse != null) { 226 return mLastAuthorizationResponse.accessToken; 227 } 228 229 return null; 230 } 231 232 /** 233 * The expiration time of the current access token (if available), as milliseconds from the 234 * UNIX epoch (consistent with {@link System#currentTimeMillis()}). 235 */ 236 @Nullable 237 public Long getAccessTokenExpirationTime() { 238 if (mAuthorizationException != null) { 239 return null; 240 } 241 242 if (mLastTokenResponse != null && mLastTokenResponse.accessToken != null) { 243 return mLastTokenResponse.accessTokenExpirationTime; 244 } 245 246 if (mLastAuthorizationResponse != null && mLastAuthorizationResponse.accessToken != null) { 247 return mLastAuthorizationResponse.accessTokenExpirationTime; 248 } 249 250 return null; 251 } 252 253 /** 254 * The current ID token, if available. 255 */ 256 @Nullable 257 public String getIdToken() { 258 if (mAuthorizationException != null) { 259 return null; 260 } 261 262 if (mLastTokenResponse != null && mLastTokenResponse.idToken != null) { 263 return mLastTokenResponse.idToken; 264 } 265 266 if (mLastAuthorizationResponse != null) { 267 return mLastAuthorizationResponse.idToken; 268 } 269 270 return null; 271 } 272 273 /** 274 * The current parsed ID token, if available. 275 */ 276 @Nullable 277 public IdToken getParsedIdToken() { 278 String stringToken = getIdToken(); 279 IdToken token; 280 281 if (stringToken != null) { 282 try { 283 token = IdToken.from(stringToken); 284 } catch (JSONException | IdToken.IdTokenException ex) { 285 token = null; 286 } 287 } else { 288 token = null; 289 } 290 291 return token; 292 } 293 294 /** 295 * The current client secret, if available. 296 */ 297 public String getClientSecret() { 298 if (mLastRegistrationResponse != null) { 299 return mLastRegistrationResponse.clientSecret; 300 } 301 302 return null; 303 } 304 305 /** 306 * The expiration time of the current client credentials (if available), as milliseconds from 307 * the UNIX epoch (consistent with {@link System#currentTimeMillis()}). If the value is 0, the 308 * client credentials will not expire. 309 */ 310 @Nullable 311 public Long getClientSecretExpirationTime() { 312 if (mLastRegistrationResponse != null) { 313 return mLastRegistrationResponse.clientSecretExpiresAt; 314 } 315 316 return null; 317 } 318 319 /** 320 * Determines whether the current state represents a successful authorization, 321 * from which at least either an access token or an ID token have been retrieved. 322 */ 323 public boolean isAuthorized() { 324 return mAuthorizationException == null 325 && (getAccessToken() != null || getIdToken() != null); 326 } 327 328 /** 329 * If the last response was an OAuth related failure, this returns the exception describing 330 * the failure. 331 */ 332 @Nullable 333 public AuthorizationException getAuthorizationException() { 334 return mAuthorizationException; 335 } 336 337 /** 338 * Determines whether the access token is considered to have expired. If no refresh token 339 * has been acquired, then this method will always return `false`. A token refresh 340 * can be forced, regardless of the validity of any currently acquired access token, by 341 * calling {@link #setNeedsTokenRefresh(boolean) setNeedsTokenRefresh(true)}. 342 */ 343 public boolean getNeedsTokenRefresh() { 344 return getNeedsTokenRefresh(SystemClock.INSTANCE); 345 } 346 347 @VisibleForTesting 348 boolean getNeedsTokenRefresh(Clock clock) { 349 if (mNeedsTokenRefreshOverride) { 350 return true; 351 } 352 353 if (getAccessTokenExpirationTime() == null) { 354 // if there is no expiration but we have an access token, it is assumed 355 // to never expire. 356 return getAccessToken() == null; 357 } 358 359 return getAccessTokenExpirationTime() 360 <= clock.getCurrentTimeMillis() + EXPIRY_TIME_TOLERANCE_MS; 361 } 362 363 /** 364 * Sets whether to force an access token refresh, regardless of the current access token's 365 * expiration time. 366 */ 367 public void setNeedsTokenRefresh(boolean needsTokenRefresh) { 368 mNeedsTokenRefreshOverride = needsTokenRefresh; 369 } 370 371 /** 372 * Determines whether the client credentials is considered to have expired. If no client 373 * credentials have been acquired, then this method will always return `false` 374 */ 375 public boolean hasClientSecretExpired() { 376 return hasClientSecretExpired(SystemClock.INSTANCE); 377 } 378 379 @VisibleForTesting 380 boolean hasClientSecretExpired(Clock clock) { 381 if (getClientSecretExpirationTime() == null || getClientSecretExpirationTime() == 0) { 382 // no explicit expiration time, and 0 means it will not expire 383 return false; 384 } 385 386 return getClientSecretExpirationTime() <= clock.getCurrentTimeMillis(); 387 } 388 389 /** 390 * Updates the authorization state based on a new authorization response. 391 */ 392 public void update( 393 @Nullable AuthorizationResponse authResponse, 394 @Nullable AuthorizationException authException) { 395 checkArgument(authResponse != null ^ authException != null, 396 "exactly one of authResponse or authException should be non-null"); 397 if (authException != null) { 398 if (authException.type == AuthorizationException.TYPE_OAUTH_AUTHORIZATION_ERROR) { 399 mAuthorizationException = authException; 400 } 401 return; 402 } 403 404 // the last token response and refresh token are now stale, as they are associated with 405 // any previous authorization response 406 mLastAuthorizationResponse = authResponse; 407 mConfig = null; 408 mLastTokenResponse = null; 409 mRefreshToken = null; 410 mAuthorizationException = null; 411 412 // if the response's mScope is null, it means that it equals that of the request 413 // see: https://tools.ietf.org/html/rfc6749#section-5.1 414 mScope = (authResponse.scope != null) ? authResponse.scope : authResponse.request.scope; 415 } 416 417 /** 418 * Updates the authorization state based on a new token response. 419 */ 420 public void update( 421 @Nullable TokenResponse tokenResponse, 422 @Nullable AuthorizationException authException) { 423 checkArgument(tokenResponse != null ^ authException != null, 424 "exactly one of tokenResponse or authException should be non-null"); 425 426 if (mAuthorizationException != null) { 427 // Calling updateFromTokenResponse while in an error state probably means the developer 428 // obtained a new token and did the exchange without also calling 429 // updateFromAuthorizationResponse. Attempt to handle this gracefully, but warn the 430 // developer that this is unexpected. 431 Logger.warn( 432 "AuthState.update should not be called in an error state (%s), call update" 433 + "with the result of the fresh authorization response first", 434 mAuthorizationException); 435 mAuthorizationException = null; 436 } 437 438 if (authException != null) { 439 if (authException.type == AuthorizationException.TYPE_OAUTH_TOKEN_ERROR) { 440 mAuthorizationException = authException; 441 } 442 return; 443 } 444 445 mLastTokenResponse = tokenResponse; 446 if (tokenResponse.scope != null) { 447 mScope = tokenResponse.scope; 448 } 449 if (tokenResponse.refreshToken != null) { 450 mRefreshToken = tokenResponse.refreshToken; 451 } 452 } 453 454 /** 455 * Updates the authorization state based on a new client registration response. 456 */ 457 public void update(@Nullable RegistrationResponse regResponse) { 458 mLastRegistrationResponse = regResponse; 459 460 // a new client registration will have a new client id, so invalidate the current session. 461 // Note however that we do not discard the configuration; this is likely still applicable. 462 mConfig = getAuthorizationServiceConfiguration(); 463 464 mRefreshToken = null; 465 mScope = null; 466 mLastAuthorizationResponse = null; 467 mLastTokenResponse = null; 468 mAuthorizationException = null; 469 } 470 471 /** 472 * Ensures that a non-expired access token is available before invoking the provided action. 473 */ 474 public void performActionWithFreshTokens( 475 @NonNull AuthorizationService service, 476 @NonNull AuthStateAction action) { 477 performActionWithFreshTokens( 478 service, 479 NoClientAuthentication.INSTANCE, 480 Collections.<String, String>emptyMap(), 481 SystemClock.INSTANCE, 482 action); 483 } 484 485 /** 486 * Ensures that a non-expired access token is available before invoking the provided action. 487 */ 488 public void performActionWithFreshTokens( 489 @NonNull AuthorizationService service, 490 @NonNull ClientAuthentication clientAuth, 491 @NonNull AuthStateAction action) { 492 performActionWithFreshTokens( 493 service, 494 clientAuth, 495 Collections.<String, String>emptyMap(), 496 SystemClock.INSTANCE, 497 action); 498 } 499 500 /** 501 * Ensures that a non-expired access token is available before invoking the provided action. 502 * If a token refresh is required, the provided additional parameters will be included in this 503 * refresh request. 504 */ 505 public void performActionWithFreshTokens( 506 @NonNull AuthorizationService service, 507 @NonNull Map<String, String> refreshTokenAdditionalParams, 508 @NonNull AuthStateAction action) { 509 try { 510 performActionWithFreshTokens( 511 service, 512 getClientAuthentication(), 513 refreshTokenAdditionalParams, 514 SystemClock.INSTANCE, 515 action); 516 } catch (ClientAuthentication.UnsupportedAuthenticationMethod ex) { 517 action.execute(null, null, 518 AuthorizationException.fromTemplate( 519 AuthorizationException.TokenRequestErrors.CLIENT_ERROR, ex)); 520 } 521 } 522 523 /** 524 * Ensures that a non-expired access token is available before invoking the provided action. 525 * If a token refresh is required, the provided additional parameters will be included in this 526 * refresh request. 527 */ 528 public void performActionWithFreshTokens( 529 @NonNull AuthorizationService service, 530 @NonNull ClientAuthentication clientAuth, 531 @NonNull Map<String, String> refreshTokenAdditionalParams, 532 @NonNull AuthStateAction action) { 533 performActionWithFreshTokens( 534 service, 535 clientAuth, 536 refreshTokenAdditionalParams, 537 SystemClock.INSTANCE, 538 action); 539 } 540 541 @VisibleForTesting 542 void performActionWithFreshTokens( 543 @NonNull final AuthorizationService service, 544 @NonNull final ClientAuthentication clientAuth, 545 @NonNull final Map<String, String> refreshTokenAdditionalParams, 546 @NonNull final Clock clock, 547 @NonNull final AuthStateAction action) { 548 checkNotNull(service, "service cannot be null"); 549 checkNotNull(clientAuth, "client authentication cannot be null"); 550 checkNotNull(refreshTokenAdditionalParams, 551 "additional params cannot be null"); 552 checkNotNull(clock, "clock cannot be null"); 553 checkNotNull(action, "action cannot be null"); 554 555 if (!getNeedsTokenRefresh(clock)) { 556 action.execute(getAccessToken(), getIdToken(), null); 557 return; 558 } 559 560 if (mRefreshToken == null) { 561 AuthorizationException ex = AuthorizationException.fromTemplate( 562 AuthorizationRequestErrors.CLIENT_ERROR, 563 new IllegalStateException("No refresh token available and token have expired")); 564 action.execute(null, null, ex); 565 return; 566 } 567 568 checkNotNull(mPendingActionsSyncObject, "pending actions sync object cannot be null"); 569 synchronized (mPendingActionsSyncObject) { 570 //if a token request is currently executing, queue the actions instead 571 if (mPendingActions != null) { 572 mPendingActions.add(action); 573 return; 574 } 575 576 //creates a list of pending actions, starting with the current action 577 mPendingActions = new ArrayList<>(); 578 mPendingActions.add(action); 579 } 580 581 service.performTokenRequest( 582 createTokenRefreshRequest(refreshTokenAdditionalParams), 583 clientAuth, 584 new AuthorizationService.TokenResponseCallback() { 585 @Override 586 public void onTokenRequestCompleted( 587 @Nullable TokenResponse response, 588 @Nullable AuthorizationException ex) { 589 update(response, ex); 590 591 String accessToken = null; 592 String idToken = null; 593 AuthorizationException exception = null; 594 595 if (ex == null) { 596 mNeedsTokenRefreshOverride = false; 597 accessToken = getAccessToken(); 598 idToken = getIdToken(); 599 } else { 600 exception = ex; 601 } 602 603 //sets pending queue to null and processes all actions in the queue 604 List<AuthStateAction> actionsToProcess; 605 synchronized (mPendingActionsSyncObject) { 606 actionsToProcess = mPendingActions; 607 mPendingActions = null; 608 } 609 for (AuthStateAction action : actionsToProcess) { 610 action.execute(accessToken, idToken, exception); 611 } 612 } 613 }); 614 } 615 616 /** 617 * Creates a token request for new tokens using the current refresh token. 618 */ 619 @NonNull 620 public TokenRequest createTokenRefreshRequest() { 621 return createTokenRefreshRequest(Collections.<String, String>emptyMap()); 622 } 623 624 /** 625 * Creates a token request for new tokens using the current refresh token, adding the 626 * specified additional parameters. 627 */ 628 @NonNull 629 public TokenRequest createTokenRefreshRequest( 630 @NonNull Map<String, String> additionalParameters) { 631 if (mRefreshToken == null) { 632 throw new IllegalStateException("No refresh token available for refresh request"); 633 } 634 if (mLastAuthorizationResponse == null) { 635 throw new IllegalStateException( 636 "No authorization configuration available for refresh request"); 637 } 638 639 return new TokenRequest.Builder( 640 mLastAuthorizationResponse.request.configuration, 641 mLastAuthorizationResponse.request.clientId) 642 .setGrantType(GrantTypeValues.REFRESH_TOKEN) 643 .setScope(null) 644 .setRefreshToken(mRefreshToken) 645 .setAdditionalParameters(additionalParameters) 646 .build(); 647 } 648 649 /** 650 * Produces a JSON representation of the authorization state for persistent storage or local 651 * transmission (e.g. between activities). 652 */ 653 public JSONObject jsonSerialize() { 654 JSONObject json = new JSONObject(); 655 JsonUtil.putIfNotNull(json, KEY_REFRESH_TOKEN, mRefreshToken); 656 JsonUtil.putIfNotNull(json, KEY_SCOPE, mScope); 657 658 if (mConfig != null) { 659 JsonUtil.put(json, KEY_CONFIG, mConfig.toJson()); 660 } 661 662 if (mAuthorizationException != null) { 663 JsonUtil.put(json, KEY_AUTHORIZATION_EXCEPTION, mAuthorizationException.toJson()); 664 } 665 666 if (mLastAuthorizationResponse != null) { 667 JsonUtil.put( 668 json, 669 KEY_LAST_AUTHORIZATION_RESPONSE, 670 mLastAuthorizationResponse.jsonSerialize()); 671 } 672 673 if (mLastTokenResponse != null) { 674 JsonUtil.put( 675 json, 676 KEY_LAST_TOKEN_RESPONSE, 677 mLastTokenResponse.jsonSerialize()); 678 } 679 680 if (mLastRegistrationResponse != null) { 681 JsonUtil.put( 682 json, 683 KEY_LAST_REGISTRATION_RESPONSE, 684 mLastRegistrationResponse.jsonSerialize()); 685 } 686 687 return json; 688 } 689 690 /** 691 * Produces a JSON string representation of the authorization state for persistent storage or 692 * local transmission (e.g. between activities). This method is just a convenience wrapper 693 * for {@link #jsonSerialize()}, converting the JSON object to its string form. 694 */ 695 public String jsonSerializeString() { 696 return jsonSerialize().toString(); 697 } 698 699 /** 700 * Reads an authorization state instance from a JSON string representation produced by 701 * {@link #jsonSerialize()}. 702 * @throws JSONException if the provided JSON does not match the expected structure. 703 */ 704 public static AuthState jsonDeserialize(@NonNull JSONObject json) throws JSONException { 705 checkNotNull(json, "json cannot be null"); 706 707 AuthState state = new AuthState(); 708 state.mRefreshToken = JsonUtil.getStringIfDefined(json, KEY_REFRESH_TOKEN); 709 state.mScope = JsonUtil.getStringIfDefined(json, KEY_SCOPE); 710 711 if (json.has(KEY_CONFIG)) { 712 state.mConfig = AuthorizationServiceConfiguration.fromJson( 713 json.getJSONObject(KEY_CONFIG)); 714 } 715 716 if (json.has(KEY_AUTHORIZATION_EXCEPTION)) { 717 state.mAuthorizationException = AuthorizationException.fromJson( 718 json.getJSONObject(KEY_AUTHORIZATION_EXCEPTION)); 719 } 720 721 if (json.has(KEY_LAST_AUTHORIZATION_RESPONSE)) { 722 state.mLastAuthorizationResponse = AuthorizationResponse.jsonDeserialize( 723 json.getJSONObject(KEY_LAST_AUTHORIZATION_RESPONSE)); 724 } 725 726 if (json.has(KEY_LAST_TOKEN_RESPONSE)) { 727 state.mLastTokenResponse = TokenResponse.jsonDeserialize( 728 json.getJSONObject(KEY_LAST_TOKEN_RESPONSE)); 729 } 730 731 if (json.has(KEY_LAST_REGISTRATION_RESPONSE)) { 732 state.mLastRegistrationResponse = RegistrationResponse.jsonDeserialize( 733 json.getJSONObject(KEY_LAST_REGISTRATION_RESPONSE)); 734 } 735 736 return state; 737 } 738 739 /** 740 * Reads an authorization state instance from a JSON string representation produced by 741 * {@link #jsonSerializeString()}. This method is just a convenience wrapper for 742 * {@link #jsonDeserialize(JSONObject)}, converting the JSON string to its JSON object form. 743 * @throws JSONException if the provided JSON does not match the expected structure. 744 */ 745 public static AuthState jsonDeserialize(@NonNull String jsonStr) throws JSONException { 746 checkNotEmpty(jsonStr, "jsonStr cannot be null or empty"); 747 return jsonDeserialize(new JSONObject(jsonStr)); 748 } 749 750 /** 751 * Interface for actions executed in the context of fresh (non-expired) tokens. 752 * @see #performActionWithFreshTokens(AuthorizationService, AuthStateAction) 753 */ 754 public interface AuthStateAction { 755 /** 756 * Executed in the context of fresh (non-expired) tokens. If new tokens were 757 * required to execute the action and could not be acquired, an authorization 758 * exception is provided instead. One or both of the access token and ID token will be 759 * provided, dependent upon the token types previously negotiated. 760 */ 761 void execute( 762 @Nullable String accessToken, 763 @Nullable String idToken, 764 @Nullable AuthorizationException ex); 765 } 766 767 /** 768 * Creates the required client authentication for the token endpoint based on information 769 * in the most recent registration response (if it is set). 770 * 771 * @throws ClientAuthentication.UnsupportedAuthenticationMethod if the expected client 772 * authentication method is unsupported by this client library. 773 */ 774 public ClientAuthentication getClientAuthentication() throws 775 ClientAuthentication.UnsupportedAuthenticationMethod { 776 if (getClientSecret() == null) { 777 /* Without client credentials, or unspecified 'token_endpoint_auth_method', 778 * we can never authenticate */ 779 return NoClientAuthentication.INSTANCE; 780 } else if (mLastRegistrationResponse.tokenEndpointAuthMethod == null) { 781 /* 'token_endpoint_auth_method': "If omitted, the default is client_secret_basic", 782 * "OpenID Connect Dynamic Client Registration 1.0", Section 2 */ 783 return new ClientSecretBasic(getClientSecret()); 784 } 785 786 switch (mLastRegistrationResponse.tokenEndpointAuthMethod) { 787 case ClientSecretBasic.NAME: 788 return new ClientSecretBasic(getClientSecret()); 789 case ClientSecretPost.NAME: 790 return new ClientSecretPost(getClientSecret()); 791 case "none": 792 return NoClientAuthentication.INSTANCE; 793 default: 794 throw new ClientAuthentication.UnsupportedAuthenticationMethod( 795 mLastRegistrationResponse.tokenEndpointAuthMethod); 796 797 } 798 } 799}