001/*
002 * oauth2-oidc-sdk
003 *
004 * Copyright 2012-2016, Connect2id Ltd and contributors.
005 *
006 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use
007 * this file except in compliance with the License. You may obtain a copy of the
008 * License at
009 *
010 *    http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software distributed
013 * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
014 * CONDITIONS OF ANY KIND, either express or implied. See the License for the
015 * specific language governing permissions and limitations under the License.
016 */
017
018package com.nimbusds.openid.connect.sdk.op;
019
020
021import java.io.IOException;
022import java.net.MalformedURLException;
023import java.net.URI;
024import java.net.URL;
025import java.util.*;
026
027import com.nimbusds.jose.EncryptionMethod;
028import com.nimbusds.jose.JWEAlgorithm;
029import com.nimbusds.jose.JWSAlgorithm;
030import com.nimbusds.langtag.LangTag;
031import com.nimbusds.langtag.LangTagException;
032import com.nimbusds.oauth2.sdk.GeneralException;
033import com.nimbusds.oauth2.sdk.ParseException;
034import com.nimbusds.oauth2.sdk.as.AuthorizationServerMetadata;
035import com.nimbusds.oauth2.sdk.http.HTTPRequest;
036import com.nimbusds.oauth2.sdk.http.HTTPResponse;
037import com.nimbusds.oauth2.sdk.id.Issuer;
038import com.nimbusds.oauth2.sdk.util.JSONObjectUtils;
039import com.nimbusds.openid.connect.sdk.Display;
040import com.nimbusds.openid.connect.sdk.SubjectType;
041import com.nimbusds.openid.connect.sdk.claims.ACR;
042import com.nimbusds.openid.connect.sdk.claims.ClaimType;
043import net.minidev.json.JSONObject;
044
045
046/**
047 * OpenID Provider (OP) metadata.
048 *
049 * <p>Related specifications:
050 *
051 * <ul>
052 *     <li>OpenID Connect Discovery 1.0, section 3.
053 *     <li>OpenID Connect Session Management 1.0, section 2.1 (draft 28).
054 *     <li>OpenID Connect Front-Channel Logout 1.0, section 3 (draft 02).
055 *     <li>OpenID Connect Back-Channel Logout 1.0, section 2.1 (draft 04).
056 *     <li>OAuth 2.0 Authorization Server Metadata (RFC 8414)
057 *     <li>OAuth 2.0 Mutual TLS Client Authentication and Certificate Bound
058 *         Access Tokens (draft-ietf-oauth-mtls-08)
059 * </ul>
060 */
061public class OIDCProviderMetadata extends AuthorizationServerMetadata {
062
063
064        /**
065         * The registered parameter names.
066         */
067        private static final Set<String> REGISTERED_PARAMETER_NAMES;
068
069
070        static {
071                Set<String> p = new HashSet<>(AuthorizationServerMetadata.getRegisteredParameterNames());
072                p.add("userinfo_endpoint");
073                p.add("check_session_iframe");
074                p.add("end_session_endpoint");
075                p.add("acr_values_supported");
076                p.add("subject_types_supported");
077                p.add("id_token_signing_alg_values_supported");
078                p.add("id_token_encryption_alg_values_supported");
079                p.add("id_token_encryption_enc_values_supported");
080                p.add("userinfo_signing_alg_values_supported");
081                p.add("userinfo_encryption_alg_values_supported");
082                p.add("userinfo_encryption_enc_values_supported");
083                p.add("display_values_supported");
084                p.add("claim_types_supported");
085                p.add("claims_supported");
086                p.add("claims_locales_supported");
087                p.add("claims_parameter_supported");
088                p.add("request_parameter_supported");
089                p.add("request_uri_parameter_supported");
090                p.add("require_request_uri_registration");
091                p.add("backchannel_logout_supported");
092                p.add("backchannel_logout_session_supported");
093                p.add("frontchannel_logout_supported");
094                p.add("frontchannel_logout_session_supported");
095                REGISTERED_PARAMETER_NAMES = Collections.unmodifiableSet(p);
096        }
097
098
099        /**
100         * The UserInfo endpoint.
101         */
102        private URI userInfoEndpoint;
103        
104        
105        /**
106         * The cross-origin check session iframe.
107         */
108        private URI checkSessionIframe;
109        
110        
111        /**
112         * The logout endpoint.
113         */
114        private URI endSessionEndpoint;
115
116
117        /**
118         * The supported ACRs.
119         */
120        private List<ACR> acrValues;
121
122
123        /**
124         * The supported subject types.
125         */
126        private final List<SubjectType> subjectTypes;
127
128
129        /**
130         * The supported ID token JWS algorithms.
131         */
132        private List<JWSAlgorithm> idTokenJWSAlgs;
133
134
135        /**
136         * The supported ID token JWE algorithms.
137         */
138        private List<JWEAlgorithm> idTokenJWEAlgs;
139
140
141        /**
142         * The supported ID token encryption methods.
143         */
144        private List<EncryptionMethod> idTokenJWEEncs;
145
146
147        /**
148         * The supported UserInfo JWS algorithms.
149         */
150        private List<JWSAlgorithm> userInfoJWSAlgs;
151
152
153        /**
154         * The supported UserInfo JWE algorithms.
155         */
156        private List<JWEAlgorithm> userInfoJWEAlgs;
157
158
159        /**
160         * The supported UserInfo encryption methods.
161         */
162        private List<EncryptionMethod> userInfoJWEEncs;
163
164
165        /**
166         * The supported displays.
167         */
168        private List<Display> displays;
169        
170        
171        /**
172         * The supported claim types.
173         */
174        private List<ClaimType> claimTypes;
175
176
177        /**
178         * The supported claims names.
179         */
180        private List<String> claims;
181        
182        
183        /**
184         * The supported claims locales.
185         */
186        private List<LangTag> claimsLocales;
187        
188        
189        /**
190         * If {@code true} the {@code claims} parameter is supported, else not.
191         */
192        private boolean claimsParamSupported = false;
193        
194        
195        /**
196         * If {@code true} the {@code frontchannel_logout_supported} parameter
197         * is set, else not.
198         */
199        private boolean frontChannelLogoutSupported = false;
200        
201        
202        /**
203         * If {@code true} the {@code frontchannel_logout_session_supported}
204         * parameter is set, else not.
205         */
206        private boolean frontChannelLogoutSessionSupported = false;
207        
208        
209        /**
210         * If {@code true} the {@code backchannel_logout_supported} parameter
211         * is set, else not.
212         */
213        private boolean backChannelLogoutSupported = false;
214        
215        
216        /**
217         * If {@code true} the {@code backchannel_logout_session_supported}
218         * parameter is set, else not.
219         */
220        private boolean backChannelLogoutSessionSupported = false;
221
222
223        /**
224         * Creates a new OpenID Connect provider metadata instance.
225         * 
226         * @param issuer       The issuer identifier. Must be an URI using the
227         *                     https scheme with no query or fragment 
228         *                     component. Must not be {@code null}.
229         * @param subjectTypes The supported subject types. At least one must
230         *                     be specified. Must not be {@code null}.
231         * @param jwkSetURI    The JWK set URI. Must not be {@code null}.
232         */
233        public OIDCProviderMetadata(final Issuer issuer,
234                                    final List<SubjectType> subjectTypes,
235                                    final URI jwkSetURI) {
236        
237                super(issuer);
238                
239                if (subjectTypes.size() < 1)
240                        throw new IllegalArgumentException("At least one supported subject type must be specified");
241                
242                this.subjectTypes = subjectTypes;
243
244                if (jwkSetURI == null)
245                        throw new IllegalArgumentException("The public JWK set URI must not be null");
246
247                setJWKSetURI(jwkSetURI);
248        }
249
250
251        /**
252         * Gets the registered OpenID Connect provider metadata parameter
253         * names.
254         *
255         * @return The registered OpenID Connect provider metadata parameter
256         *         names, as an unmodifiable set.
257         */
258        public static Set<String> getRegisteredParameterNames() {
259
260                return REGISTERED_PARAMETER_NAMES;
261        }
262
263
264        /**
265         * Gets the UserInfo endpoint URI. Corresponds the
266         * {@code userinfo_endpoint} metadata field.
267         *
268         * @return The UserInfo endpoint URI, {@code null} if not specified.
269         */
270        public URI getUserInfoEndpointURI() {
271
272                return userInfoEndpoint;
273        }
274
275
276        /**
277         * Sets the UserInfo endpoint URI. Corresponds the
278         * {@code userinfo_endpoint} metadata field.
279         *
280         * @param userInfoEndpoint The UserInfo endpoint URI, {@code null} if
281         *                         not specified.
282         */
283        public void setUserInfoEndpointURI(final URI userInfoEndpoint) {
284
285                this.userInfoEndpoint = userInfoEndpoint;
286        }
287        
288        
289        /**
290         * Gets the cross-origin check session iframe URI. Corresponds to the
291         * {@code check_session_iframe} metadata field.
292         * 
293         * @return The check session iframe URI, {@code null} if not specified.
294         */
295        public URI getCheckSessionIframeURI() {
296                
297                return checkSessionIframe;
298        }
299
300
301        /**
302         * Sets the cross-origin check session iframe URI. Corresponds to the
303         * {@code check_session_iframe} metadata field.
304         *
305         * @param checkSessionIframe The check session iframe URI, {@code null}
306         *                           if not specified.
307         */
308        public void setCheckSessionIframeURI(final URI checkSessionIframe) {
309
310                this.checkSessionIframe = checkSessionIframe;
311        }
312        
313        
314        /**
315         * Gets the logout endpoint URI. Corresponds to the
316         * {@code end_session_endpoint} metadata field.
317         * 
318         * @return The logoout endpoint URI, {@code null} if not specified.
319         */
320        public URI getEndSessionEndpointURI() {
321                
322                return endSessionEndpoint;
323        }
324
325
326        /**
327         * Sets the logout endpoint URI. Corresponds to the
328         * {@code end_session_endpoint} metadata field.
329         *
330         * @param endSessionEndpoint The logoout endpoint URI, {@code null} if
331         *                           not specified.
332         */
333        public void setEndSessionEndpointURI(final URI endSessionEndpoint) {
334
335                this.endSessionEndpoint = endSessionEndpoint;
336        }
337
338        /**
339         * Gets the supported Authentication Context Class References (ACRs).
340         * Corresponds to the {@code acr_values_supported} metadata field.
341         *
342         * @return The supported ACRs, {@code null} if not specified.
343         */
344        public List<ACR> getACRs() {
345
346                return acrValues;
347        }
348
349
350        /**
351         * Sets the supported Authentication Context Class References (ACRs).
352         * Corresponds to the {@code acr_values_supported} metadata field.
353         *
354         * @param acrValues The supported ACRs, {@code null} if not specified.
355         */
356        public void setACRs(final List<ACR> acrValues) {
357
358                this.acrValues = acrValues;
359        }
360
361
362        /**
363         * Gets the supported subject types. Corresponds to the
364         * {@code subject_types_supported} metadata field.
365         *
366         * @return The supported subject types.
367         */
368        public List<SubjectType> getSubjectTypes() {
369
370                return subjectTypes;
371        }
372
373
374        /**
375         * Gets the supported JWS algorithms for ID tokens. Corresponds to the 
376         * {@code id_token_signing_alg_values_supported} metadata field.
377         *
378         * @return The supported JWS algorithms, {@code null} if not specified.
379         */
380        public List<JWSAlgorithm> getIDTokenJWSAlgs() {
381
382                return idTokenJWSAlgs;
383        }
384
385
386        /**
387         * Sets the supported JWS algorithms for ID tokens. Corresponds to the
388         * {@code id_token_signing_alg_values_supported} metadata field.
389         *
390         * @param idTokenJWSAlgs The supported JWS algorithms, {@code null} if
391         *                       not specified.
392         */
393        public void setIDTokenJWSAlgs(final List<JWSAlgorithm> idTokenJWSAlgs) {
394
395                this.idTokenJWSAlgs = idTokenJWSAlgs;
396        }
397
398
399        /**
400         * Gets the supported JWE algorithms for ID tokens. Corresponds to the 
401         * {@code id_token_encryption_alg_values_supported} metadata field.
402         *
403         * @return The supported JWE algorithms, {@code null} if not specified.
404         */
405        public List<JWEAlgorithm> getIDTokenJWEAlgs() {
406
407                return idTokenJWEAlgs;
408        }
409
410
411        /**
412         * Sets the supported JWE algorithms for ID tokens. Corresponds to the
413         * {@code id_token_encryption_alg_values_supported} metadata field.
414         *
415         * @param idTokenJWEAlgs The supported JWE algorithms, {@code null} if
416         *                       not specified.
417         */
418        public void setIDTokenJWEAlgs(final List<JWEAlgorithm> idTokenJWEAlgs) {
419
420                this.idTokenJWEAlgs = idTokenJWEAlgs;
421        }
422
423
424        /**
425         * Gets the supported encryption methods for ID tokens. Corresponds to 
426         * the {@code id_token_encryption_enc_values_supported} metadata field.
427         *
428         * @return The supported encryption methods, {@code null} if not 
429         *         specified.
430         */
431        public List<EncryptionMethod> getIDTokenJWEEncs() {
432
433                return idTokenJWEEncs;
434        }
435
436
437        /**
438         * Sets the supported encryption methods for ID tokens. Corresponds to
439         * the {@code id_token_encryption_enc_values_supported} metadata field.
440         *
441         * @param idTokenJWEEncs The supported encryption methods, {@code null}
442         *                       if not specified.
443         */
444        public void setIDTokenJWEEncs(final List<EncryptionMethod> idTokenJWEEncs) {
445
446                this.idTokenJWEEncs = idTokenJWEEncs;
447        }
448
449
450        /**
451         * Gets the supported JWS algorithms for UserInfo JWTs. Corresponds to 
452         * the {@code userinfo_signing_alg_values_supported} metadata field.
453         *
454         * @return The supported JWS algorithms, {@code null} if not specified.
455         */
456        public List<JWSAlgorithm> getUserInfoJWSAlgs() {
457
458                return userInfoJWSAlgs;
459        }
460
461
462        /**
463         * Sets the supported JWS algorithms for UserInfo JWTs. Corresponds to
464         * the {@code userinfo_signing_alg_values_supported} metadata field.
465         *
466         * @param userInfoJWSAlgs The supported JWS algorithms, {@code null} if
467         *                        not specified.
468         */
469        public void setUserInfoJWSAlgs(final List<JWSAlgorithm> userInfoJWSAlgs) {
470
471                this.userInfoJWSAlgs = userInfoJWSAlgs;
472        }
473
474
475        /**
476         * Gets the supported JWE algorithms for UserInfo JWTs. Corresponds to 
477         * the {@code userinfo_encryption_alg_values_supported} metadata field.
478         *
479         * @return The supported JWE algorithms, {@code null} if not specified.
480         */
481        public List<JWEAlgorithm> getUserInfoJWEAlgs() {
482
483                return userInfoJWEAlgs;
484        }
485
486
487        /**
488         * Sets the supported JWE algorithms for UserInfo JWTs. Corresponds to
489         * the {@code userinfo_encryption_alg_values_supported} metadata field.
490         *
491         * @param userInfoJWEAlgs The supported JWE algorithms, {@code null} if
492         *                        not specified.
493         */
494        public void setUserInfoJWEAlgs(final List<JWEAlgorithm> userInfoJWEAlgs) {
495
496                this.userInfoJWEAlgs = userInfoJWEAlgs;
497        }
498
499
500        /**
501         * Gets the supported encryption methods for UserInfo JWTs. Corresponds 
502         * to the {@code userinfo_encryption_enc_values_supported} metadata 
503         * field.
504         *
505         * @return The supported encryption methods, {@code null} if not 
506         *         specified.
507         */
508        public List<EncryptionMethod> getUserInfoJWEEncs() {
509
510                return userInfoJWEEncs;
511        }
512
513
514        /**
515         * Sets the supported encryption methods for UserInfo JWTs. Corresponds
516         * to the {@code userinfo_encryption_enc_values_supported} metadata
517         * field.
518         *
519         * @param userInfoJWEEncs The supported encryption methods,
520         *                        {@code null} if not specified.
521         */
522        public void setUserInfoJWEEncs(final List<EncryptionMethod> userInfoJWEEncs) {
523
524                this.userInfoJWEEncs = userInfoJWEEncs;
525        }
526
527
528        /**
529         * Gets the supported displays. Corresponds to the 
530         * {@code display_values_supported} metadata field.
531         *
532         * @return The supported displays, {@code null} if not specified.
533         */
534        public List<Display> getDisplays() {
535
536                return displays;
537        }
538
539
540        /**
541         * Sets the supported displays. Corresponds to the
542         * {@code display_values_supported} metadata field.
543         *
544         * @param displays The supported displays, {@code null} if not
545         *                 specified.
546         */
547        public void setDisplays(final List<Display> displays) {
548
549                this.displays = displays;
550        }
551        
552        
553        /**
554         * Gets the supported claim types. Corresponds to the 
555         * {@code claim_types_supported} metadata field.
556         * 
557         * @return The supported claim types, {@code null} if not specified.
558         */
559        public List<ClaimType> getClaimTypes() {
560                
561                return claimTypes;
562        }
563
564
565        /**
566         * Sets the supported claim types. Corresponds to the
567         * {@code claim_types_supported} metadata field.
568         *
569         * @param claimTypes The supported claim types, {@code null} if not
570         *                   specified.
571         */
572        public void setClaimTypes(final List<ClaimType> claimTypes) {
573
574                this.claimTypes = claimTypes;
575        }
576
577
578        /**
579         * Gets the supported claims names. Corresponds to the 
580         * {@code claims_supported} metadata field.
581         *
582         * @return The supported claims names, {@code null} if not specified.
583         */
584        public List<String> getClaims() {
585
586                return claims;
587        }
588
589
590        /**
591         * Sets the supported claims names. Corresponds to the
592         * {@code claims_supported} metadata field.
593         *
594         * @param claims The supported claims names, {@code null} if not
595         *               specified.
596         */
597        public void setClaims(final List<String> claims) {
598
599                this.claims = claims;
600        }
601        
602        
603        /**
604         * Gets the supported claims locales. Corresponds to the
605         * {@code claims_locales_supported} metadata field.
606         * 
607         * @return The supported claims locales, {@code null} if not specified.
608         */
609        public List<LangTag> getClaimsLocales() {
610                
611                return claimsLocales;
612        }
613
614
615        /**
616         * Sets the supported claims locales. Corresponds to the
617         * {@code claims_locales_supported} metadata field.
618         *
619         * @param claimsLocales The supported claims locales, {@code null} if
620         *                      not specified.
621         */
622        public void setClaimLocales(final List<LangTag> claimsLocales) {
623
624                this.claimsLocales = claimsLocales;
625        }
626        
627        
628        /**
629         * Gets the support for the {@code claims} authorisation request
630         * parameter. Corresponds to the {@code claims_parameter_supported} 
631         * metadata field.
632         * 
633         * @return {@code true} if the {@code claim} parameter is supported,
634         *         else {@code false}.
635         */
636        public boolean supportsClaimsParam() {
637                
638                return claimsParamSupported;
639        }
640
641
642        /**
643         * Sets the support for the {@code claims} authorisation request
644         * parameter. Corresponds to the {@code claims_parameter_supported}
645         * metadata field.
646         *
647         * @param claimsParamSupported {@code true} if the {@code claim}
648         *                             parameter is supported, else
649         *                             {@code false}.
650         */
651        public void setSupportsClaimsParams(final boolean claimsParamSupported) {
652
653                this.claimsParamSupported = claimsParamSupported;
654        }
655        
656        
657        /**
658         * Gets the support for front-channel logout. Corresponds to the
659         * {@code frontchannel_logout_supported} metadata field.
660         *
661         * @return {@code true} if front-channel logout is supported, else
662         *         {@code false}.
663         */
664        public boolean supportsFrontChannelLogout() {
665                
666                return frontChannelLogoutSupported;
667        }
668        
669        
670        /**
671         * Sets the support for front-channel logout. Corresponds to the
672         * {@code frontchannel_logout_supported} metadata field.
673         *
674         * @param frontChannelLogoutSupported {@code true} if front-channel
675         *                                    logout is supported, else
676         *                                    {@code false}.
677         */
678        public void setSupportsFrontChannelLogout(final boolean frontChannelLogoutSupported) {
679        
680                this.frontChannelLogoutSupported = frontChannelLogoutSupported;
681        }
682        
683        
684        /**
685         * Gets the support for front-channel logout with a session ID.
686         * Corresponds to the {@code frontchannel_logout_session_supported}
687         * metadata field.
688         *
689         * @return {@code true} if front-channel logout with a session ID is
690         *         supported, else {@code false}.
691         */
692        public boolean supportsFrontChannelLogoutSession() {
693                
694                return frontChannelLogoutSessionSupported;
695        }
696        
697        
698        /**
699         * Sets the support for front-channel logout with a session ID.
700         * Corresponds to the {@code frontchannel_logout_session_supported}
701         * metadata field.
702         *
703         * @param frontChannelLogoutSessionSupported {@code true} if
704         *                                           front-channel logout with
705         *                                           a session ID is supported,
706         *                                           else {@code false}.
707         */
708        public void setSupportsFrontChannelLogoutSession(final boolean frontChannelLogoutSessionSupported) {
709        
710                this.frontChannelLogoutSessionSupported = frontChannelLogoutSessionSupported;
711        }
712        
713        
714        /**
715         * Gets the support for back-channel logout. Corresponds to the
716         * {@code backchannel_logout_supported} metadata field.
717         *
718         * @return {@code true} if back-channel logout is supported, else
719         *         {@code false}.
720         */
721        public boolean supportsBackChannelLogout() {
722                
723                return backChannelLogoutSupported;
724        }
725        
726        
727        /**
728         * Sets the support for back-channel logout. Corresponds to the
729         * {@code backchannel_logout_supported} metadata field.
730         *
731         * @param backChannelLogoutSupported {@code true} if back-channel
732         *                                   logout is supported, else
733         *                                   {@code false}.
734         */
735        public void setSupportsBackChannelLogout(final boolean backChannelLogoutSupported) {
736        
737                this.backChannelLogoutSupported = backChannelLogoutSupported;
738        }
739        
740        
741        /**
742         * Gets the support for back-channel logout with a session ID.
743         * Corresponds to the {@code backchannel_logout_session_supported}
744         * metadata field.
745         *
746         * @return {@code true} if back-channel logout with a session ID is
747         *         supported, else {@code false}.
748         */
749        public boolean supportsBackChannelLogoutSession() {
750                
751                return backChannelLogoutSessionSupported;
752        }
753        
754        
755        /**
756         * Sets the support for back-channel logout with a session ID.
757         * Corresponds to the {@code backchannel_logout_session_supported}
758         * metadata field.
759         *
760         * @param backChannelLogoutSessionSupported {@code true} if
761         *                                          back-channel logout with a
762         *                                          session ID is supported,
763         *                                          else {@code false}.
764         */
765        public void setSupportsBackChannelLogoutSession(final boolean backChannelLogoutSessionSupported) {
766                
767                this.backChannelLogoutSessionSupported = backChannelLogoutSessionSupported;
768        }
769
770
771        /**
772         * Applies the OpenID Provider metadata defaults where no values have
773         * been specified.
774         *
775         * <ul>
776         *     <li>The response modes default to {@code ["query", "fragment"]}.
777         *     <li>The grant types default to {@code ["authorization_code",
778         *         "implicit"]}.
779         *     <li>The token endpoint authentication methods default to
780         *         {@code ["client_secret_basic"]}.
781         *     <li>The claim types default to {@code ["normal]}.
782         * </ul>
783         */
784        public void applyDefaults() {
785
786                super.applyDefaults();
787
788                if (claimTypes == null) {
789                        claimTypes = new ArrayList<>(1);
790                        claimTypes.add(ClaimType.NORMAL);
791                }
792        }
793
794
795        /**
796         * Returns the JSON object representation of this OpenID Connect
797         * provider metadata.
798         *
799         * @return The JSON object representation.
800         */
801        public JSONObject toJSONObject() {
802
803                JSONObject o = super.toJSONObject();
804
805                // Mandatory fields
806
807                List<String> stringList = new ArrayList<>(subjectTypes.size());
808
809                for (SubjectType st: subjectTypes)
810                        stringList.add(st.toString());
811
812                o.put("subject_types_supported", stringList);
813
814                // Optional fields
815
816                if (userInfoEndpoint != null)
817                        o.put("userinfo_endpoint", userInfoEndpoint.toString());
818
819                if (checkSessionIframe != null)
820                        o.put("check_session_iframe", checkSessionIframe.toString());
821
822                if (endSessionEndpoint != null)
823                        o.put("end_session_endpoint", endSessionEndpoint.toString());
824
825                if (acrValues != null) {
826
827                        stringList = new ArrayList<>(acrValues.size());
828
829                        for (ACR acr: acrValues)
830                                stringList.add(acr.getValue());
831
832                        o.put("acr_values_supported", stringList);
833                }
834
835                if (idTokenJWSAlgs != null) {
836
837                        stringList = new ArrayList<>(idTokenJWSAlgs.size());
838
839                        for (JWSAlgorithm alg: idTokenJWSAlgs)
840                                stringList.add(alg.getName());
841
842                        o.put("id_token_signing_alg_values_supported", stringList);
843                }
844
845                if (idTokenJWEAlgs != null) {
846
847                        stringList = new ArrayList<>(idTokenJWEAlgs.size());
848
849                        for (JWEAlgorithm alg: idTokenJWEAlgs)
850                                stringList.add(alg.getName());
851
852                        o.put("id_token_encryption_alg_values_supported", stringList);
853                }
854
855                if (idTokenJWEEncs != null) {
856
857                        stringList = new ArrayList<>(idTokenJWEEncs.size());
858
859                        for (EncryptionMethod m: idTokenJWEEncs)
860                                stringList.add(m.getName());
861
862                        o.put("id_token_encryption_enc_values_supported", stringList);
863                }
864
865                if (userInfoJWSAlgs != null) {
866
867                        stringList = new ArrayList<>(userInfoJWSAlgs.size());
868
869                        for (JWSAlgorithm alg: userInfoJWSAlgs)
870                                stringList.add(alg.getName());
871
872                        o.put("userinfo_signing_alg_values_supported", stringList);
873                }
874
875                if (userInfoJWEAlgs != null) {
876
877                        stringList = new ArrayList<>(userInfoJWEAlgs.size());
878
879                        for (JWEAlgorithm alg: userInfoJWEAlgs)
880                                stringList.add(alg.getName());
881
882                        o.put("userinfo_encryption_alg_values_supported", stringList);
883                }
884
885                if (userInfoJWEEncs != null) {
886
887                        stringList = new ArrayList<>(userInfoJWEEncs.size());
888
889                        for (EncryptionMethod m: userInfoJWEEncs)
890                                stringList.add(m.getName());
891
892                        o.put("userinfo_encryption_enc_values_supported", stringList);
893                }
894
895                if (displays != null) {
896
897                        stringList = new ArrayList<>(displays.size());
898
899                        for (Display d: displays)
900                                stringList.add(d.toString());
901
902                        o.put("display_values_supported", stringList);
903                }
904
905                if (claimTypes != null) {
906
907                        stringList = new ArrayList<>(claimTypes.size());
908
909                        for (ClaimType ct: claimTypes)
910                                stringList.add(ct.toString());
911
912                        o.put("claim_types_supported", stringList);
913                }
914
915                if (claims != null)
916                        o.put("claims_supported", claims);
917
918                if (claimsLocales != null) {
919
920                        stringList = new ArrayList<>(claimsLocales.size());
921
922                        for (LangTag l: claimsLocales)
923                                stringList.add(l.toString());
924
925                        o.put("claims_locales_supported", stringList);
926                }
927
928                o.put("claims_parameter_supported", claimsParamSupported);
929                
930                // optional front and back-channel logout
931                o.put("frontchannel_logout_supported", frontChannelLogoutSupported);
932                
933                if (frontChannelLogoutSupported) {
934                        o.put("frontchannel_logout_session_supported", frontChannelLogoutSessionSupported);
935                }
936                
937                o.put("backchannel_logout_supported", backChannelLogoutSupported);
938                
939                if (backChannelLogoutSupported) {
940                        o.put("backchannel_logout_session_supported", backChannelLogoutSessionSupported);
941                }
942                
943                return o;
944        }
945        
946        
947        /**
948         * Parses an OpenID Provider metadata from the specified JSON object.
949         *
950         * @param jsonObject The JSON object to parse. Must not be 
951         *                   {@code null}.
952         *
953         * @return The OpenID Provider metadata.
954         *
955         * @throws ParseException If the JSON object couldn't be parsed to an
956         *                        OpenID Provider metadata.
957         */
958        public static OIDCProviderMetadata parse(final JSONObject jsonObject)
959                throws ParseException {
960                
961                AuthorizationServerMetadata as = AuthorizationServerMetadata.parse(jsonObject);
962
963                List<SubjectType> subjectTypes = new ArrayList<>();
964                for (String v: JSONObjectUtils.getStringArray(jsonObject, "subject_types_supported")) {
965                        subjectTypes.add(SubjectType.parse(v));
966                }
967                
968                OIDCProviderMetadata op = new OIDCProviderMetadata(
969                        as.getIssuer(),
970                        Collections.unmodifiableList(subjectTypes),
971                        as.getJWKSetURI());
972
973                // Endpoints
974                op.setAuthorizationEndpointURI(as.getAuthorizationEndpointURI());
975                op.setTokenEndpointURI(as.getTokenEndpointURI());
976                op.setRegistrationEndpointURI(as.getRegistrationEndpointURI());
977                op.setIntrospectionEndpointURI(as.getIntrospectionEndpointURI());
978                op.setRevocationEndpointURI(as.getRevocationEndpointURI());
979                
980                if (jsonObject.get("userinfo_endpoint") != null)
981                        op.userInfoEndpoint = JSONObjectUtils.getURI(jsonObject, "userinfo_endpoint");
982                
983                if (jsonObject.get("check_session_iframe") != null)
984                        op.checkSessionIframe = JSONObjectUtils.getURI(jsonObject, "check_session_iframe");
985                
986                if (jsonObject.get("end_session_endpoint") != null)
987                        op.endSessionEndpoint = JSONObjectUtils.getURI(jsonObject, "end_session_endpoint");
988
989                // Capabilities
990                op.setScopes(as.getScopes());
991                op.setResponseTypes(as.getResponseTypes());
992                op.setResponseModes(as.getResponseModes());
993                op.setGrantTypes(as.getGrantTypes());
994                
995                op.setTokenEndpointAuthMethods(as.getTokenEndpointAuthMethods());
996                op.setTokenEndpointJWSAlgs(as.getTokenEndpointJWSAlgs());
997                
998                op.setIntrospectionEndpointAuthMethods(as.getIntrospectionEndpointAuthMethods());
999                op.setIntrospectionEndpointJWSAlgs(as.getIntrospectionEndpointJWSAlgs());
1000                
1001                op.setRevocationEndpointAuthMethods(as.getRevocationEndpointAuthMethods());
1002                op.setRevocationEndpointJWSAlgs(as.getRevocationEndpointJWSAlgs());
1003                
1004                op.setRequestObjectJWSAlgs(as.getRequestObjectJWSAlgs());
1005                op.setRequestObjectJWEAlgs(as.getRequestObjectJWEAlgs());
1006                op.setRequestObjectJWEEncs(as.getRequestObjectJWEEncs());
1007                
1008                op.setSupportsRequestParam(as.supportsRequestParam());
1009                op.setSupportsRequestURIParam(as.supportsRequestURIParam());
1010                op.setRequiresRequestURIRegistration(as.requiresRequestURIRegistration());
1011                
1012                op.setCodeChallengeMethods(as.getCodeChallengeMethods());
1013
1014                if (jsonObject.get("acr_values_supported") != null) {
1015
1016                        op.acrValues = new ArrayList<>();
1017
1018                        for (String v: JSONObjectUtils.getStringArray(jsonObject, "acr_values_supported")) {
1019
1020                                if (v != null)
1021                                        op.acrValues.add(new ACR(v));
1022                        }
1023                }
1024                
1025                // ID token
1026
1027                if (jsonObject.get("id_token_signing_alg_values_supported") != null) {
1028
1029                        op.idTokenJWSAlgs = new ArrayList<>();
1030
1031                        for (String v: JSONObjectUtils.getStringArray(jsonObject, "id_token_signing_alg_values_supported")) {
1032
1033                                if (v != null)
1034                                        op.idTokenJWSAlgs.add(JWSAlgorithm.parse(v));
1035                        }
1036                }
1037
1038
1039                if (jsonObject.get("id_token_encryption_alg_values_supported") != null) {
1040
1041                        op.idTokenJWEAlgs = new ArrayList<>();
1042
1043                        for (String v: JSONObjectUtils.getStringArray(jsonObject, "id_token_encryption_alg_values_supported")) {
1044
1045                                if (v != null)
1046                                        op.idTokenJWEAlgs.add(JWEAlgorithm.parse(v));
1047                        }
1048                }
1049
1050
1051                if (jsonObject.get("id_token_encryption_enc_values_supported") != null) {
1052
1053                        op.idTokenJWEEncs = new ArrayList<>();
1054
1055                        for (String v: JSONObjectUtils.getStringArray(jsonObject, "id_token_encryption_enc_values_supported")) {
1056
1057                                if (v != null)
1058                                        op.idTokenJWEEncs.add(EncryptionMethod.parse(v));
1059                        }
1060                }
1061
1062                // UserInfo
1063
1064                if (jsonObject.get("userinfo_signing_alg_values_supported") != null) {
1065
1066                        op.userInfoJWSAlgs = new ArrayList<>();
1067
1068                        for (String v: JSONObjectUtils.getStringArray(jsonObject, "userinfo_signing_alg_values_supported")) {
1069
1070                                if (v != null)
1071                                        op.userInfoJWSAlgs.add(JWSAlgorithm.parse(v));
1072                        }
1073                }
1074
1075
1076                if (jsonObject.get("userinfo_encryption_alg_values_supported") != null) {
1077
1078                        op.userInfoJWEAlgs = new ArrayList<>();
1079
1080                        for (String v: JSONObjectUtils.getStringArray(jsonObject, "userinfo_encryption_alg_values_supported")) {
1081
1082                                if (v != null)
1083                                        op.userInfoJWEAlgs.add(JWEAlgorithm.parse(v));
1084                        }
1085                }
1086
1087
1088                if (jsonObject.get("userinfo_encryption_enc_values_supported") != null) {
1089
1090                        op.userInfoJWEEncs = new ArrayList<>();
1091
1092                        for (String v: JSONObjectUtils.getStringArray(jsonObject, "userinfo_encryption_enc_values_supported")) {
1093
1094                                        if (v != null)
1095                                                op.userInfoJWEEncs.add(EncryptionMethod.parse(v));
1096                        }
1097                }
1098
1099                
1100                // Misc
1101
1102                if (jsonObject.get("display_values_supported") != null) {
1103
1104                        op.displays = new ArrayList<>();
1105
1106                        for (String v: JSONObjectUtils.getStringArray(jsonObject, "display_values_supported")) {
1107
1108                                if (v != null)
1109                                        op.displays.add(Display.parse(v));
1110                        }
1111                }
1112                
1113                if (jsonObject.get("claim_types_supported") != null) {
1114                        
1115                        op.claimTypes = new ArrayList<>();
1116                        
1117                        for (String v: JSONObjectUtils.getStringArray(jsonObject, "claim_types_supported")) {
1118                                
1119                                if (v != null)
1120                                        op.claimTypes.add(ClaimType.parse(v));
1121                        }
1122                }
1123
1124
1125                if (jsonObject.get("claims_supported") != null) {
1126
1127                        op.claims = new ArrayList<>();
1128
1129                        for (String v: JSONObjectUtils.getStringArray(jsonObject, "claims_supported")) {
1130
1131                                if (v != null)
1132                                        op.claims.add(v);
1133                        }
1134                }
1135                
1136                if (jsonObject.get("claims_locales_supported") != null) {
1137                        
1138                        op.claimsLocales = new ArrayList<>();
1139                        
1140                        for (String v : JSONObjectUtils.getStringArray(jsonObject, "claims_locales_supported")) {
1141                                
1142                                if (v != null) {
1143                                        
1144                                        try {
1145                                                op.claimsLocales.add(LangTag.parse(v));
1146                                        
1147                                        } catch (LangTagException e) {
1148                                                
1149                                                throw new ParseException("Invalid claims_locales_supported field: " + e.getMessage(), e);
1150                                        }
1151                                }
1152                        }
1153                }
1154                
1155                op.setUILocales(as.getUILocales());
1156                op.setServiceDocsURI(as.getServiceDocsURI());
1157                op.setPolicyURI(as.getPolicyURI());
1158                op.setTermsOfServiceURI(as.getTermsOfServiceURI());
1159                
1160                if (jsonObject.get("claims_parameter_supported") != null)
1161                        op.claimsParamSupported = JSONObjectUtils.getBoolean(jsonObject, "claims_parameter_supported");
1162                
1163                // Optional front and back-channel logout
1164                if (jsonObject.get("frontchannel_logout_supported") != null)
1165                        op.frontChannelLogoutSupported = JSONObjectUtils.getBoolean(jsonObject, "frontchannel_logout_supported");
1166                
1167                if (op.frontChannelLogoutSupported && jsonObject.get("frontchannel_logout_session_supported") != null)
1168                        op.frontChannelLogoutSessionSupported = JSONObjectUtils.getBoolean(jsonObject, "frontchannel_logout_session_supported");
1169                
1170                if (jsonObject.get("backchannel_logout_supported") != null)
1171                        op.backChannelLogoutSupported = JSONObjectUtils.getBoolean(jsonObject, "backchannel_logout_supported");
1172                
1173                if (op.frontChannelLogoutSupported && jsonObject.get("backchannel_logout_session_supported") != null)
1174                        op.backChannelLogoutSessionSupported = JSONObjectUtils.getBoolean(jsonObject, "backchannel_logout_session_supported");
1175                
1176                op.setSupportsTLSClientCertificateBoundAccessTokens(as.supportsTLSClientCertificateBoundAccessTokens());
1177                
1178                // Parse custom (not registered) parameters
1179                for (Map.Entry<String,?> entry: as.getCustomParameters().entrySet()) {
1180                        if (REGISTERED_PARAMETER_NAMES.contains(entry.getKey()))
1181                                continue; // skip
1182                        op.setCustomParameter(entry.getKey(), entry.getValue());
1183                }
1184
1185                return op;
1186        }
1187
1188
1189        /**
1190         * Parses an OpenID Provider metadata from the specified JSON object
1191         * string.
1192         *
1193         * @param s The JSON object sting to parse. Must not be {@code null}.
1194         *
1195         * @return The OpenID Provider metadata.
1196         *
1197         * @throws ParseException If the JSON object string couldn't be parsed
1198         *                        to an OpenID Provider metadata.
1199         */
1200        public static OIDCProviderMetadata parse(final String s)
1201                throws ParseException {
1202
1203                return parse(JSONObjectUtils.parse(s));
1204        }
1205        
1206        
1207        /**
1208         * Resolves OpenID Provider metadata from the specified issuer
1209         * identifier. The metadata is downloaded by HTTP GET from
1210         * {@code [issuer-url]/.well-known/openid-configuration}.
1211         *
1212         * @param issuer The OpenID Provider issuer identifier. Must represent
1213         *               a valid HTTPS or HTTP URL. Must not be {@code null}.
1214         *
1215         * @return The OpenID Provider metadata.
1216         *
1217         * @throws GeneralException If the issuer identifier or the downloaded
1218         *                          metadata are invalid.
1219         * @throws IOException      On a HTTP exception.
1220         */
1221        public static OIDCProviderMetadata resolve(final Issuer issuer)
1222                throws GeneralException, IOException {
1223                
1224                return resolve(issuer, 0, 0);
1225        }
1226        
1227        
1228        /**
1229         * Resolves OpenID Provider metadata from the specified issuer
1230         * identifier. The metadata is downloaded by HTTP GET from
1231         * {@code [issuer-url]/.well-known/openid-configuration}, using the
1232         * specified HTTP timeouts.
1233         *
1234         * @param issuer         The issuer identifier. Must represent a valid
1235         *                       HTTPS or HTTP URL. Must not be {@code null}.
1236         * @param connectTimeout The HTTP connect timeout, in milliseconds.
1237         *                       Zero implies no timeout. Must not be negative.
1238         * @param readTimeout    The HTTP response read timeout, in
1239         *                       milliseconds. Zero implies no timeout. Must
1240         *                       not be negative.
1241         *
1242         * @return The OpenID Provider metadata.
1243         *
1244         * @throws GeneralException If the issuer identifier or the downloaded
1245         *                          metadata are invalid.
1246         * @throws IOException      On a HTTP exception.
1247         */
1248        public static OIDCProviderMetadata resolve(final Issuer issuer,
1249                                                   final int connectTimeout,
1250                                                   final int readTimeout)
1251                throws GeneralException, IOException {
1252                
1253                URL configURL;
1254                
1255                try {
1256                        URL issuerURL = new URL(issuer.getValue());
1257                        
1258                        // Validate but don't insist on HTTPS, see
1259                        // http://openid.net/specs/openid-connect-core-1_0.html#Terminology
1260                        if (issuerURL.getQuery() != null && ! issuerURL.getQuery().trim().isEmpty()) {
1261                                throw new GeneralException("The issuer identifier must not contain a query component");
1262                        }
1263                        
1264                        if (issuerURL.getPath() != null && issuerURL.getPath().endsWith("/")) {
1265                                configURL = new URL(issuerURL + ".well-known/openid-configuration");
1266                        } else {
1267                                configURL = new URL(issuerURL + "/.well-known/openid-configuration");
1268                        }
1269                        
1270                } catch (MalformedURLException e) {
1271                        throw new GeneralException("The issuer identifier doesn't represent a valid URL: " + e.getMessage(), e);
1272                }
1273                
1274                HTTPRequest httpRequest = new HTTPRequest(HTTPRequest.Method.GET, configURL);
1275                httpRequest.setConnectTimeout(connectTimeout);
1276                httpRequest.setReadTimeout(readTimeout);
1277                
1278                HTTPResponse httpResponse = httpRequest.send();
1279                
1280                if (httpResponse.getStatusCode() != 200) {
1281                        throw new IOException("Couldn't download OpenID Provider metadata from " + configURL +
1282                                ": Status code " + httpResponse.getStatusCode());
1283                }
1284                
1285                JSONObject jsonObject = httpResponse.getContentAsJSONObject();
1286                
1287                OIDCProviderMetadata op = OIDCProviderMetadata.parse(jsonObject);
1288                
1289                if (! issuer.equals(op.getIssuer())) {
1290                        throw new GeneralException("The returned issuer doesn't match the expected: " + op.getIssuer());
1291                }
1292                
1293                return op;
1294        }
1295}