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.Preconditions.checkNotNull; 018 019import android.net.Uri; 020import androidx.annotation.NonNull; 021import androidx.annotation.Nullable; 022import androidx.annotation.VisibleForTesting; 023 024import net.openid.appauth.JsonUtil.BooleanField; 025import net.openid.appauth.JsonUtil.Field; 026import net.openid.appauth.JsonUtil.StringField; 027import net.openid.appauth.JsonUtil.StringListField; 028import net.openid.appauth.JsonUtil.UriField; 029import org.json.JSONException; 030import org.json.JSONObject; 031 032import java.util.Arrays; 033import java.util.Collections; 034import java.util.List; 035 036/** 037 * An OpenID Connect 1.0 Discovery Document. 038 * 039 * @see "OpenID Connect discovery 1.0, Section 3 040 * <https://openid.net/specs/openid-connect-discovery-1_0.html#rfc.section.3>" 041 */ 042public class AuthorizationServiceDiscovery { 043 044 @VisibleForTesting 045 static final StringField ISSUER = str("issuer"); 046 047 @VisibleForTesting 048 static final UriField AUTHORIZATION_ENDPOINT = uri("authorization_endpoint"); 049 050 @VisibleForTesting 051 static final UriField TOKEN_ENDPOINT = uri("token_endpoint"); 052 053 @VisibleForTesting 054 static final UriField END_SESSION_ENDPOINT = uri("end_session_endpoint"); 055 056 @VisibleForTesting 057 static final UriField USERINFO_ENDPOINT = uri("userinfo_endpoint"); 058 059 @VisibleForTesting 060 static final UriField JWKS_URI = uri("jwks_uri"); 061 062 @VisibleForTesting 063 static final UriField REGISTRATION_ENDPOINT = uri("registration_endpoint"); 064 065 @VisibleForTesting 066 static final StringListField SCOPES_SUPPORTED = strList("scopes_supported"); 067 068 @VisibleForTesting 069 static final StringListField RESPONSE_TYPES_SUPPORTED = strList("response_types_supported"); 070 071 @VisibleForTesting 072 static final StringListField RESPONSE_MODES_SUPPORTED = strList("response_modes_supported"); 073 074 @VisibleForTesting 075 static final StringListField GRANT_TYPES_SUPPORTED = 076 strList("grant_types_supported", Arrays.asList("authorization_code", "implicit")); 077 078 @VisibleForTesting 079 static final StringListField ACR_VALUES_SUPPORTED = strList("acr_values_supported"); 080 081 @VisibleForTesting 082 static final StringListField SUBJECT_TYPES_SUPPORTED = strList("subject_types_supported"); 083 084 @VisibleForTesting 085 static final StringListField ID_TOKEN_SIGNING_ALG_VALUES_SUPPORTED = 086 strList("id_token_signing_alg_values_supported"); 087 088 @VisibleForTesting 089 static final StringListField ID_TOKEN_ENCRYPTION_ALG_VALUES_SUPPORTED = 090 strList("id_token_encryption_enc_values_supported"); 091 092 @VisibleForTesting 093 static final StringListField ID_TOKEN_ENCRYPTION_ENC_VALUES_SUPPORTED = 094 strList("id_token_encryption_enc_values_supported"); 095 096 @VisibleForTesting 097 static final StringListField USERINFO_SIGNING_ALG_VALUES_SUPPORTED = 098 strList("userinfo_signing_alg_values_supported"); 099 100 @VisibleForTesting 101 static final StringListField USERINFO_ENCRYPTION_ALG_VALUES_SUPPORTED = 102 strList("userinfo_encryption_alg_values_supported"); 103 104 @VisibleForTesting 105 static final StringListField USERINFO_ENCRYPTION_ENC_VALUES_SUPPORTED = 106 strList("userinfo_encryption_enc_values_supported"); 107 108 @VisibleForTesting 109 static final StringListField REQUEST_OBJECT_SIGNING_ALG_VALUES_SUPPORTED = 110 strList("request_object_signing_alg_values_supported"); 111 112 @VisibleForTesting 113 static final StringListField REQUEST_OBJECT_ENCRYPTION_ALG_VALUES_SUPPORTED = 114 strList("request_object_encryption_alg_values_supported"); 115 116 @VisibleForTesting 117 static final StringListField REQUEST_OBJECT_ENCRYPTION_ENC_VALUES_SUPPORTED = 118 strList("request_object_encryption_enc_values_supported"); 119 120 @VisibleForTesting 121 static final StringListField TOKEN_ENDPOINT_AUTH_METHODS_SUPPORTED = 122 strList("token_endpoint_auth_methods_supported", 123 Collections.singletonList("client_secret_basic")); 124 125 @VisibleForTesting 126 static final StringListField TOKEN_ENDPOINT_AUTH_SIGNING_ALG_VALUES_SUPPORTED = 127 strList("token_endpoint_auth_signing_alg_values_supported"); 128 129 @VisibleForTesting 130 static final StringListField DISPLAY_VALUES_SUPPORTED = strList("display_values_supported"); 131 132 @VisibleForTesting 133 static final StringListField CLAIM_TYPES_SUPPORTED = 134 strList("claim_types_supported", Collections.singletonList("normal")); 135 136 @VisibleForTesting 137 static final StringListField CLAIMS_SUPPORTED = strList("claims_supported"); 138 139 @VisibleForTesting 140 static final UriField SERVICE_DOCUMENTATION = uri("service_documentation"); 141 142 @VisibleForTesting 143 static final StringListField CLAIMS_LOCALES_SUPPORTED = strList("claims_locales_supported"); 144 145 @VisibleForTesting 146 static final StringListField UI_LOCALES_SUPPORTED = strList("ui_locales_supported"); 147 148 @VisibleForTesting 149 static final BooleanField CLAIMS_PARAMETER_SUPPORTED = 150 bool("claims_parameter_supported", false); 151 152 @VisibleForTesting 153 static final BooleanField REQUEST_PARAMETER_SUPPORTED = 154 bool("request_parameter_supported", false); 155 156 @VisibleForTesting 157 static final BooleanField REQUEST_URI_PARAMETER_SUPPORTED = 158 bool("request_uri_parameter_supported", true); 159 160 @VisibleForTesting 161 static final BooleanField REQUIRE_REQUEST_URI_REGISTRATION = 162 bool("require_request_uri_registration", false); 163 164 @VisibleForTesting 165 static final UriField OP_POLICY_URI = uri("op_policy_uri"); 166 167 @VisibleForTesting 168 static final UriField OP_TOS_URI = uri("op_tos_uri"); 169 170 /** 171 * The fields which are marked as mandatory in the OpenID discovery spec. 172 */ 173 private static final List<String> MANDATORY_METADATA = Arrays.asList( 174 ISSUER.key, 175 AUTHORIZATION_ENDPOINT.key, 176 JWKS_URI.key, 177 RESPONSE_TYPES_SUPPORTED.key, 178 SUBJECT_TYPES_SUPPORTED.key, 179 ID_TOKEN_SIGNING_ALG_VALUES_SUPPORTED.key); 180 181 /** 182 * The JSON representation of the discovery document. 183 */ 184 @NonNull 185 public final JSONObject docJson; 186 187 /** 188 * Extracts a discovery document from its standard JSON representation. 189 * @throws JSONException if the provided JSON does not match the expected structure. 190 * @throws MissingArgumentException if a mandatory property is missing from the discovery 191 * document. 192 */ 193 public AuthorizationServiceDiscovery(@NonNull JSONObject discoveryDoc) 194 throws JSONException, MissingArgumentException { 195 this.docJson = checkNotNull(discoveryDoc); 196 for (String mandatory : MANDATORY_METADATA) { 197 if (!this.docJson.has(mandatory) || this.docJson.get(mandatory) == null) { 198 throw new MissingArgumentException(mandatory); 199 } 200 } 201 } 202 203 /** 204 * Thrown when a mandatory property is missing from the discovery document. 205 */ 206 public static class MissingArgumentException extends Exception { 207 private String mMissingField; 208 209 /** 210 * Indicates that the specified mandatory field is missing from the discovery document. 211 */ 212 public MissingArgumentException(String field) { 213 super("Missing mandatory configuration field: " + field); 214 mMissingField = field; 215 } 216 217 public String getMissingField() { 218 return mMissingField; 219 } 220 } 221 222 /** 223 * Retrieves a metadata value from the discovery document. This need only be used 224 * for the retrieval of a non-standard metadata value. Convenience methods are defined on this 225 * class for all standard metadata values. 226 */ 227 private <T> T get(Field<T> field) { 228 return JsonUtil.get(docJson, field); 229 } 230 231 /** 232 * Retrieves a metadata value from the discovery document. This need only be used 233 * for the retrieval of a non-standard metadata value. Convenience methods are defined on this 234 * class for all standard metadata values. 235 */ 236 private <T> List<T> get(JsonUtil.ListField<T> field) { 237 return JsonUtil.get(docJson, field); 238 } 239 240 /** 241 * The asserted issuer identifier. 242 */ 243 @NonNull 244 public String getIssuer() { 245 return get(ISSUER); 246 } 247 248 /** 249 * The OAuth 2 authorization endpoint URI. 250 */ 251 @NonNull 252 public Uri getAuthorizationEndpoint() { 253 return get(AUTHORIZATION_ENDPOINT); 254 } 255 256 /** 257 * The OAuth 2 token endpoint URI. Not specified if only the implicit flow is used. 258 */ 259 @Nullable 260 public Uri getTokenEndpoint() { 261 return get(TOKEN_ENDPOINT); 262 } 263 264 /** 265 * The OAuth 2 emd session endpoint URI. Not specified test OAuth implementation 266 */ 267 public Uri getEndSessionEndpoint() { 268 return get(END_SESSION_ENDPOINT); 269 } 270 271 /** 272 * The OpenID Connect UserInfo endpoint URI. 273 */ 274 @Nullable 275 public Uri getUserinfoEndpoint() { 276 return get(USERINFO_ENDPOINT); 277 } 278 279 /** 280 * The JSON web key set document URI. 281 * 282 * @see "JSON Web Key (RFC 7517) <http://tools.ietf.org/html/rfc7517>" 283 */ 284 @NonNull 285 public Uri getJwksUri() { 286 return get(JWKS_URI); 287 } 288 289 /** 290 * The dynamic client registration endpoint URI. 291 */ 292 @Nullable 293 public Uri getRegistrationEndpoint() { 294 return get(REGISTRATION_ENDPOINT); 295 } 296 297 /** 298 * The OAuth 2 `scope` values supported. 299 * 300 * @see "OpenID Connect Dynamic Client Registration 1.0 301 * <https://openid.net/specs/openid-connect-discovery-1_0.html>" 302 */ 303 public List<String> getScopesSupported() { 304 return get(SCOPES_SUPPORTED); 305 } 306 307 /** 308 * The OAuth 2 `response_type` values supported. 309 */ 310 @NonNull 311 public List<String> getResponseTypesSupported() { 312 return get(RESPONSE_TYPES_SUPPORTED); 313 } 314 315 /** 316 * The OAuth 2 `response_mode` values supported. 317 * 318 * @see "OAuth 2.0 Multiple Response Type Encoding Practices <http://openid.net/specs/oauth-v2-multiple-response-types-1_0.html>" 319 */ 320 @Nullable 321 public List<String> getResponseModesSupported() { 322 return get(RESPONSE_MODES_SUPPORTED); 323 } 324 325 /** 326 * The OAuth 2 `grant_type` values supported. Defaults to `authorization_code` and `implicit` 327 * if not specified in the discovery document, as suggested by the discovery specification. 328 */ 329 @NonNull 330 public List<String> getGrantTypesSupported() { 331 return get(GRANT_TYPES_SUPPORTED); 332 } 333 334 /** 335 * The authentication context class references supported. 336 */ 337 public List<String> getAcrValuesSupported() { 338 return get(ACR_VALUES_SUPPORTED); 339 } 340 341 /** 342 * The subject identifier types supported. 343 */ 344 @NonNull 345 public List<String> getSubjectTypesSupported() { 346 return get(SUBJECT_TYPES_SUPPORTED); 347 } 348 349 /** 350 * The JWS signing algorithms (alg values) supported for encoding ID token claims. 351 * 352 * @see "JSON Web Token (RFC 7519) <https://tools.ietf.org/html/rfc7519>" 353 */ 354 @NonNull 355 public List<String> getIdTokenSigningAlgorithmValuesSupported() { 356 return get(ID_TOKEN_SIGNING_ALG_VALUES_SUPPORTED); 357 } 358 359 /** 360 * The JWE encryption algorithms (alg values) supported for encoding ID token claims. 361 * 362 * @see "JSON Web Token (RFC 7519) <https://tools.ietf.org/html/rfc7519>" 363 */ 364 @Nullable 365 public List<String> getIdTokenEncryptionAlgorithmValuesSupported() { 366 return get(ID_TOKEN_ENCRYPTION_ALG_VALUES_SUPPORTED); 367 } 368 369 /** 370 * The JWE encryption encodings (enc values) supported for encoding ID token claims. 371 * 372 * @see "JSON Web Token (RFC 7519) <https://tools.ietf.org/html/rfc7519>" 373 */ 374 @Nullable 375 public List<String> getIdTokenEncryptionEncodingValuesSupported() { 376 return get(ID_TOKEN_ENCRYPTION_ENC_VALUES_SUPPORTED); 377 } 378 379 /** 380 * The JWS signing algorithms (alg values) supported by the UserInfo Endpoint 381 * for encoding ID token claims. 382 * 383 * @see "JSON Web Signature (RFC 7515) <https://tools.ietf.org/html/rfc7515>" 384 * @see "JSON Web Algorithms (RFC 7518) <https://tools.ietf.org/html/rfc7518>" 385 * @see "JSON Web Token (RFC 7519) <https://tools.ietf.org/html/rfc7519>" 386 */ 387 @Nullable 388 public List<String> getUserinfoSigningAlgorithmValuesSupported() { 389 return get(USERINFO_SIGNING_ALG_VALUES_SUPPORTED); 390 } 391 392 /** 393 * The JWE encryption algorithms (alg values) supported by the UserInfo Endpoint 394 * for encoding ID token claims. 395 * 396 * @see "JSON Web Signature (RFC 7515) <https://tools.ietf.org/html/rfc7515>" 397 * @see "JSON Web Algorithms (RFC 7518) <https://tools.ietf.org/html/rfc7518>" 398 * @see "JSON Web Token (RFC 7519) <https://tools.ietf.org/html/rfc7519>" 399 */ 400 @Nullable 401 public List<String> getUserinfoEncryptionAlgorithmValuesSupported() { 402 return get(USERINFO_ENCRYPTION_ALG_VALUES_SUPPORTED); 403 } 404 405 /** 406 * The JWE encryption encodings (enc values) supported by the UserInfo Endpoint 407 * for encoding ID token claims. 408 * 409 * @see "JSON Web Token (RFC 7519) <https://tools.ietf.org/html/rfc7519>" 410 */ 411 @Nullable 412 public List<String> getUserinfoEncryptionEncodingValuesSupported() { 413 return get(USERINFO_ENCRYPTION_ENC_VALUES_SUPPORTED); 414 } 415 416 /** 417 * The JWS signing algorithms (alg values) supported for Request Objects. 418 * 419 * @see "OpenID Connect Core 1.0, Section 6.1 420 * <https://openid.net/specs/openid-connect-core-1_0.html#rfc.section.6.1>" 421 */ 422 public List<String> getRequestObjectSigningAlgorithmValuesSupported() { 423 return get(REQUEST_OBJECT_SIGNING_ALG_VALUES_SUPPORTED); 424 } 425 426 /** 427 * The JWE encryption algorithms (alg values) supported for Request Objects. 428 */ 429 @Nullable 430 public List<String> getRequestObjectEncryptionAlgorithmValuesSupported() { 431 return get(REQUEST_OBJECT_ENCRYPTION_ALG_VALUES_SUPPORTED); 432 } 433 434 /** 435 * The JWE encryption encodings (enc values) supported for Request Objects. 436 */ 437 @Nullable 438 public List<String> getRequestObjectEncryptionEncodingValuesSupported() { 439 return get(REQUEST_OBJECT_ENCRYPTION_ENC_VALUES_SUPPORTED); 440 } 441 442 /** 443 * The client authentication methods supported by the token endpoint. Defaults to 444 * `client_secret_basic` if the discovery document does not specify a value, as suggested 445 * by the discovery specification. 446 * 447 * @see "OpenID Connect Core 1.0, Section 9 448 * <https://openid.net/specs/openid-connect-core-1_0.html#rfc.section.9>" 449 * @see "The OAuth 2.0 Authorization Framework (RFC 6749), Section 2.3.1 450 * <https://tools.ietf.org/html/rfc6749#section-2.3.1>" 451 */ 452 @NonNull 453 public List<String> getTokenEndpointAuthMethodsSupported() { 454 return get(TOKEN_ENDPOINT_AUTH_METHODS_SUPPORTED); 455 } 456 457 /** 458 * The JWS signing algorithms (alg values) supported by the token endpoint for the signature on 459 * the JWT used to authenticate the client for the `private_key_jwt` and 460 * `client_secret_jwt` authentication methods. 461 * 462 * @see "JSON Web Token (RFC 7519) <https://tools.ietf.org/html/rfc7519>" 463 */ 464 @Nullable 465 public List<String> getTokenEndpointAuthSigningAlgorithmValuesSupported() { 466 return get(TOKEN_ENDPOINT_AUTH_SIGNING_ALG_VALUES_SUPPORTED); 467 } 468 469 /** 470 * The `display` parameter values supported. 471 * 472 * @see "OpenID Connect Core 1.0, Section 3.1.2.1 473 * <https://openid.net/specs/openid-connect-core-1_0.html#rfc.section.3.1.2.1>" 474 */ 475 @Nullable 476 public List<String> getDisplayValuesSupported() { 477 return get(DISPLAY_VALUES_SUPPORTED); 478 } 479 480 /** 481 * The claim types supported. Defaults to `normal` if not specified by the discovery 482 * document JSON, as suggested by the discovery specification. 483 * 484 * @see "OpenID Connect Core 1.0, Section 5.6 485 * <https://openid.net/specs/openid-connect-core-1_0.html#rfc.section.5.6>" 486 */ 487 public List<String> getClaimTypesSupported() { 488 return get(CLAIM_TYPES_SUPPORTED); 489 } 490 491 /** 492 * The claim names of the claims that the provider _may_ be able to supply values for. 493 */ 494 @Nullable 495 public List<String> getClaimsSupported() { 496 return get(CLAIMS_SUPPORTED); 497 } 498 499 /** 500 * A page containing human-readable information that developers might want or need to know when 501 * using this provider. 502 */ 503 @Nullable 504 public Uri getServiceDocumentation() { 505 return get(SERVICE_DOCUMENTATION); 506 } 507 508 /** 509 * Languages and scripts supported for values in claims being returned. 510 * Represented as a list of BCP47 language tag values. 511 * 512 * @see "Tags for Identifying Languages (RFC 5646) <http://tools.ietf.org/html/rfc5646>" 513 */ 514 @Nullable 515 public List<String> getClaimsLocalesSupported() { 516 return get(CLAIMS_LOCALES_SUPPORTED); 517 } 518 519 /** 520 * Languages and scripts supported for the user interface. 521 * Represented as a list of BCP47 language tag values. 522 * 523 * @see "Tags for Identifying Languages (RFC 5646) <http://tools.ietf.org/html/rfc5646>" 524 */ 525 @Nullable 526 public List<String> getUiLocalesSupported() { 527 return get(UI_LOCALES_SUPPORTED); 528 } 529 530 /** 531 * Specifies whether the `claims` parameter is supported for authorization requests. 532 * 533 * @see "OpenID Connect Core 1.0, Section 5.5 534 * <https://openid.net/specs/openid-connect-core-1_0.html#rfc.section.5.5>" 535 */ 536 public boolean isClaimsParameterSupported() { 537 return get(CLAIMS_PARAMETER_SUPPORTED); 538 } 539 540 /** 541 * Specifies whether the `request` parameter is supported for authorization requests. 542 * 543 * @see "OpenID Connect Core 1.0, Section 6.1 544 * <https://openid.net/specs/openid-connect-core-1_0.html#rfc.section.6.1>" 545 */ 546 public boolean isRequestParameterSupported() { 547 return get(REQUEST_PARAMETER_SUPPORTED); 548 } 549 550 /** 551 * Specifies whether the `request_uri` parameter is supported for authorization requests. 552 * 553 * @see "OpenID Connect Core 1.0, Section 6.2 554 * <https://openid.net/specs/openid-connect-core-1_0.html#rfc.section.6.2>" 555 */ 556 public boolean isRequestUriParameterSupported() { 557 return get(REQUEST_URI_PARAMETER_SUPPORTED); 558 } 559 560 /** 561 * Specifies whether `request_uri` values are required to be pre-registered before use. 562 * 563 * @see "OpenID Connect Core 1.0, Section 6.2 564 * <https://openid.net/specs/openid-connect-core-1_0.html#rfc.section.6.2>" 565 */ 566 public boolean requireRequestUriRegistration() { 567 return get(REQUIRE_REQUEST_URI_REGISTRATION); 568 } 569 570 /** 571 * A page articulating the policy regarding the use of data provided by the provider. 572 */ 573 @Nullable 574 public Uri getOpPolicyUri() { 575 return get(OP_POLICY_URI); 576 } 577 578 /** 579 * A page articulating the terms of service for the provider. 580 */ 581 @Nullable 582 public Uri getOpTosUri() { 583 return get(OP_TOS_URI); 584 } 585 586 /** 587 * Shorthand method for creating a string metadata extractor. 588 */ 589 private static StringField str(String key) { 590 return new StringField(key); 591 } 592 593 /** 594 * Shorthand method for creating a URI metadata extractor. 595 */ 596 private static UriField uri(String key) { 597 return new UriField(key); 598 } 599 600 /** 601 * Shorthand method for creating a string list metadata extractor. 602 */ 603 private static StringListField strList(String key) { 604 return new StringListField(key); 605 } 606 607 /** 608 * Shorthand method for creating a string list metadata extractor, with a default value. 609 */ 610 private static StringListField strList(String key, List<String> defaults) { 611 return new StringListField(key, defaults); 612 } 613 614 /** 615 * Shorthand method for creating a boolean metadata extractor. 616 */ 617 private static BooleanField bool(String key, boolean defaultValue) { 618 return new BooleanField(key, defaultValue); 619 } 620}