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}