001/*
002 * oauth2-oidc-sdk
003 *
004 * Copyright 2012-2020, 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;
019
020
021import java.util.*;
022
023import net.jcip.annotations.Immutable;
024import net.minidev.json.JSONArray;
025import net.minidev.json.JSONAware;
026import net.minidev.json.JSONObject;
027
028import com.nimbusds.oauth2.sdk.ParseException;
029import com.nimbusds.oauth2.sdk.ResponseType;
030import com.nimbusds.oauth2.sdk.Scope;
031import com.nimbusds.oauth2.sdk.util.CollectionUtils;
032import com.nimbusds.oauth2.sdk.util.JSONArrayUtils;
033import com.nimbusds.oauth2.sdk.util.JSONObjectUtils;
034import com.nimbusds.openid.connect.sdk.assurance.request.VerifiedClaimsSetRequest;
035import com.nimbusds.openid.connect.sdk.claims.ClaimRequirement;
036import com.nimbusds.openid.connect.sdk.claims.ClaimsSetRequest;
037
038
039/**
040 * Specifies individual OpenID claims to return from the UserInfo endpoint and
041 * / or in the ID Token. Replaces the deprecated {@link ClaimsRequest} class.
042 *
043 * <p>Example:
044 *
045 * <pre>
046 * {
047 *   "userinfo":
048 *    {
049 *     "given_name": {"essential": true},
050 *     "nickname": null,
051 *     "email": {"essential": true},
052 *     "email_verified": {"essential": true},
053 *     "picture": null,
054 *     "http://example.info/claims/groups": null
055 *    },
056 *   "id_token":
057 *    {
058 *     "auth_time": {"essential": true},
059 *     "acr": {"values": ["urn:mace:incommon:iap:silver"] }
060 *    }
061 * }
062 * </pre>
063 *
064 * <p>Related specifications:
065 *
066 * <ul>
067 *     <li>OpenID Connect Core 1.0
068 *     <li>OpenID Connect for Identity Assurance 1.0
069 * </ul>
070 */
071@Immutable
072public class OIDCClaimsRequest implements JSONAware {
073        
074        
075        /**
076         * Claims requested in the ID token, {@code null} if not specified.
077         */
078        private final ClaimsSetRequest idToken;
079        
080        
081        /**
082         * Claims requested at the UserInfo endpoint, {@code null} if not
083         * specified.
084         */
085        private final ClaimsSetRequest userInfo;
086        
087        
088        /**
089         * Verified claims requested in the ID token, empty list if none.
090         */
091        private final List<VerifiedClaimsSetRequest> idTokenVerified;
092        
093        
094        /**
095         * Verified claims requested at the UserInfo endpoint, empty list if
096         * none.
097         */
098        private final List<VerifiedClaimsSetRequest> userInfoVerified;
099        
100        
101        /**
102         * Creates a new empty OpenID claims request.
103         */
104        public OIDCClaimsRequest() {
105                this(null, null, Collections.<VerifiedClaimsSetRequest>emptyList(), Collections.<VerifiedClaimsSetRequest>emptyList());
106        }
107        
108        
109        private OIDCClaimsRequest(final ClaimsSetRequest idToken,
110                                  final ClaimsSetRequest userInfo,
111                                  final List<VerifiedClaimsSetRequest> idTokenVerified,
112                                  final List<VerifiedClaimsSetRequest> userInfoVerified) {
113                
114                this.idToken = idToken;
115                this.userInfo = userInfo;
116                this.idTokenVerified = Collections.unmodifiableList(idTokenVerified);
117                this.userInfoVerified = Collections.unmodifiableList(userInfoVerified);
118        }
119        
120        
121        /**
122         * Adds the entries from the specified other OpenID claims request.
123         *
124         * @param other The other OpenID claims request. If {@code null} no
125         *              claims request entries will be added to this claims
126         *              request.
127         *
128         * @return The updated OpenID claims request.
129         */
130        public OIDCClaimsRequest add(final OIDCClaimsRequest other) {
131                
132                if (other == null)
133                        return this;
134                
135                // Regular id_token
136                Collection<ClaimsSetRequest.Entry> idTokenEntries = new LinkedList<>();
137                if (idToken != null) {
138                        idTokenEntries.addAll(idToken.getEntries());
139                }
140                if (other.getIDTokenClaimsRequest() != null) {
141                        idTokenEntries.addAll(other.getIDTokenClaimsRequest().getEntries());
142                }
143                
144                // Regular userinfo
145                Collection<ClaimsSetRequest.Entry> userInfoEntries = new LinkedList<>();
146                if (userInfo != null) {
147                        userInfoEntries.addAll(userInfo.getEntries());
148                }
149                if (other.getUserInfoClaimsRequest() != null) {
150                        userInfoEntries.addAll(other.getUserInfoClaimsRequest().getEntries());
151                }
152                
153                // Verified id_token
154                List<VerifiedClaimsSetRequest> idTokenVerifiedList = new LinkedList<>(idTokenVerified);
155                idTokenVerifiedList.addAll(other.getIDTokenVerifiedClaimsRequests());
156                
157                // Verified userinfo
158                List<VerifiedClaimsSetRequest> userInfoVerifiedList = new LinkedList<>(userInfoVerified);
159                userInfoVerifiedList.addAll(other.getUserInfoVerifiedClaimsRequests());
160                
161                return new OIDCClaimsRequest(
162                        idTokenEntries.isEmpty() ? null : new ClaimsSetRequest(idTokenEntries),
163                        userInfoEntries.isEmpty() ? null : new ClaimsSetRequest(userInfoEntries),
164                        idTokenVerifiedList,
165                        userInfoVerifiedList
166                );
167        }
168        
169        
170        /**
171         * Returns the claims requested in the ID token.
172         *
173         * @return The ID token claims request, {@code null} if not specified.
174         */
175        public ClaimsSetRequest getIDTokenClaimsRequest() {
176                return idToken;
177        }
178        
179        
180        /**
181         * Sets the claims requested in the ID token.
182         *
183         * @param idToken The ID token claims request, {@code null} if not
184         *                specified.
185         *
186         * @return The updated OpenID claims request.
187         */
188        public OIDCClaimsRequest withIDTokenClaimsRequest(final ClaimsSetRequest idToken) {
189                return new OIDCClaimsRequest(
190                        idToken,
191                        getUserInfoClaimsRequest(),
192                        getIDTokenVerifiedClaimsRequests(),
193                        getUserInfoVerifiedClaimsRequests());
194        }
195        
196        
197        /**
198         * Returns the claims requested at the UserInfo endpoint.
199         *
200         * @return The UserInfo claims request, {@code null} if not specified.
201         */
202        public ClaimsSetRequest getUserInfoClaimsRequest() {
203                return userInfo;
204        }
205        
206        
207        /**
208         * Sets the claims requested at the UserInfo endpoint.
209         *
210         * @param userInfo The UserInfo claims request, {@code null} if not
211         *                 specified.
212         *
213         * @return The updated OpenID claims request.
214         */
215        public OIDCClaimsRequest withUserInfoClaimsRequest(final ClaimsSetRequest userInfo) {
216                return new OIDCClaimsRequest(
217                        getIDTokenClaimsRequest(),
218                        userInfo,
219                        getIDTokenVerifiedClaimsRequests(),
220                        getUserInfoVerifiedClaimsRequests());
221        }
222        
223        
224        private static List<VerifiedClaimsSetRequest> toCurrent(final List<com.nimbusds.openid.connect.sdk.assurance.claims.VerifiedClaimsSetRequest> list) {
225                
226                if (CollectionUtils.isEmpty(list)) {
227                        return Collections.emptyList();
228                }
229                
230                List<VerifiedClaimsSetRequest> out = new LinkedList<>();
231                for (com.nimbusds.openid.connect.sdk.assurance.claims.VerifiedClaimsSetRequest r: list) {
232                        if (r == null) {
233                                continue;
234                        }
235                        try {
236                                out.add(VerifiedClaimsSetRequest.parse(r.toJSONObject()));
237                        } catch (ParseException e) {
238                                // should never happen
239                        }       
240                }
241                return out;
242        }
243        
244        
245        private static List<com.nimbusds.openid.connect.sdk.assurance.claims.VerifiedClaimsSetRequest> toDeprecated(final List<VerifiedClaimsSetRequest> list) {
246                
247                if (CollectionUtils.isEmpty(list)) {
248                        return Collections.emptyList();
249                }
250                
251                List<com.nimbusds.openid.connect.sdk.assurance.claims.VerifiedClaimsSetRequest> out = new LinkedList<>();
252                for (VerifiedClaimsSetRequest r: list) {
253                        if (r == null) {
254                                continue;
255                        }
256                        try {
257                                out.add(com.nimbusds.openid.connect.sdk.assurance.claims.VerifiedClaimsSetRequest.parse(r.toJSONObject()));
258                        } catch (ParseException e) {
259                                // should never happen
260                        }
261                }
262                return out;
263        }
264        
265        
266        /**
267         * Returns the list of verified claims sets requested in the ID token.
268         *
269         * @return The ID token verified claims request list, empty list if not
270         *         specified.
271         */
272        public List<VerifiedClaimsSetRequest> getIDTokenVerifiedClaimsRequests() {
273                return idTokenVerified;
274        }
275        
276        
277        /**
278         * Returns the list of verified claims sets requested in the ID token.
279         *
280         * @return The ID token verified claims request list, empty list if not
281         *         specified.
282         */
283        @Deprecated
284        public List<com.nimbusds.openid.connect.sdk.assurance.claims.VerifiedClaimsSetRequest> getIDTokenVerifiedClaimsRequestList() {
285                return toDeprecated(idTokenVerified);
286        }
287        
288        
289        /**
290         * Sets the list of verified claims sets requested in the ID token.
291         *
292         * @param idTokenVerifiedList One or more ID token verified claims
293         *                            requests, empty list if not specified.
294         *
295         * @return The updated OpenID claims request.
296         */
297        public OIDCClaimsRequest withIDTokenVerifiedClaimsRequests(final List<VerifiedClaimsSetRequest> idTokenVerifiedList) {
298                return new OIDCClaimsRequest(
299                        getIDTokenClaimsRequest(),
300                        getUserInfoClaimsRequest(),
301                        idTokenVerifiedList != null ? idTokenVerifiedList : Collections.<VerifiedClaimsSetRequest>emptyList(),
302                        getUserInfoVerifiedClaimsRequests());
303        }
304        
305        
306        /**
307         * Sets the list of verified claims sets requested in the ID token.
308         *
309         * @param idTokenVerifiedList One or more ID token verified claims
310         *                            requests, empty list if not specified.
311         *
312         * @return The updated OpenID claims request.
313         */
314        @Deprecated
315        public OIDCClaimsRequest withIDTokenVerifiedClaimsRequestList(final List<com.nimbusds.openid.connect.sdk.assurance.claims.VerifiedClaimsSetRequest> idTokenVerifiedList) {
316                return new OIDCClaimsRequest(
317                        getIDTokenClaimsRequest(),
318                        getUserInfoClaimsRequest(),
319                        idTokenVerifiedList != null ? toCurrent(idTokenVerifiedList) : Collections.<VerifiedClaimsSetRequest>emptyList(),
320                        getUserInfoVerifiedClaimsRequests());
321        }
322        
323        
324        /**
325         * Sets a single verified claims set requested in the ID token.
326         *
327         * @param idTokenVerified The ID token verified claims request,
328         *                        {@code null} if not specified.
329         *
330         * @return The updated OpenID claims request.
331         */
332        public OIDCClaimsRequest withIDTokenVerifiedClaimsRequest(final VerifiedClaimsSetRequest idTokenVerified) {
333                return new OIDCClaimsRequest(
334                        getIDTokenClaimsRequest(),
335                        getUserInfoClaimsRequest(),
336                        idTokenVerified != null ? Collections.singletonList(idTokenVerified) : Collections.<VerifiedClaimsSetRequest>emptyList(),
337                        getUserInfoVerifiedClaimsRequests());
338        }
339        
340        
341        /**
342         * Sets a single verified claims set requested in the ID token.
343         *
344         * @param idTokenVerified The ID token verified claims request,
345         *                        {@code null} if not specified.
346         *
347         * @return The updated OpenID claims request.
348         */
349        @Deprecated
350        public OIDCClaimsRequest withIDTokenVerifiedClaimsRequest(final com.nimbusds.openid.connect.sdk.assurance.claims.VerifiedClaimsSetRequest idTokenVerified) {
351                return new OIDCClaimsRequest(
352                        getIDTokenClaimsRequest(),
353                        getUserInfoClaimsRequest(),
354                        idTokenVerified != null ? toCurrent(Collections.singletonList(idTokenVerified)) : Collections.<VerifiedClaimsSetRequest>emptyList(),
355                        getUserInfoVerifiedClaimsRequests());
356        }
357        
358        
359        /**
360         * Returns the list of verified claims sets requested at the UserInfo
361         * endpoint.
362         *
363         * @return The UserInfo verified claims request list, empty list if not
364         *         specified.
365         */
366        public List<VerifiedClaimsSetRequest> getUserInfoVerifiedClaimsRequests() {
367                return userInfoVerified;
368        }
369        
370        
371        /**
372         * Returns the list of verified claims sets requested at the UserInfo
373         * endpoint.
374         *
375         * @return The UserInfo verified claims request list, empty list if not
376         *         specified.
377         */
378        @Deprecated
379        public List<com.nimbusds.openid.connect.sdk.assurance.claims.VerifiedClaimsSetRequest> getUserInfoVerifiedClaimsRequestList() {
380                return toDeprecated(userInfoVerified);
381        }
382        
383        
384        /**
385         * Sets the list of verified claims sets requested at the UserInfo
386         * endpoint.
387         *
388         * @param userInfoVerifiedList One or more UserInfo verified claims
389         *                             requests, empty list if not specified.
390         *
391         * @return The updated OpenID claims request.
392         */
393        public OIDCClaimsRequest withUserInfoVerifiedClaimsRequests(final List<VerifiedClaimsSetRequest> userInfoVerifiedList) {
394                return new OIDCClaimsRequest(
395                        getIDTokenClaimsRequest(),
396                        getUserInfoClaimsRequest(),
397                        getIDTokenVerifiedClaimsRequests(),
398                        userInfoVerifiedList != null ? userInfoVerifiedList : Collections.<VerifiedClaimsSetRequest>emptyList());
399        }
400        
401        
402        /**
403         * Sets the list of verified claims sets requested at the UserInfo
404         * endpoint.
405         *
406         * @param userInfoVerifiedList One or more UserInfo verified claims
407         *                             requests, empty list if not specified.
408         *
409         * @return The updated OpenID claims request.
410         */
411        @Deprecated
412        public OIDCClaimsRequest withUserInfoVerifiedClaimsRequestList(final List<com.nimbusds.openid.connect.sdk.assurance.claims.VerifiedClaimsSetRequest> userInfoVerifiedList) {
413                return new OIDCClaimsRequest(
414                        getIDTokenClaimsRequest(),
415                        getUserInfoClaimsRequest(),
416                        getIDTokenVerifiedClaimsRequests(),
417                        userInfoVerifiedList != null ? toCurrent(userInfoVerifiedList) : Collections.<VerifiedClaimsSetRequest>emptyList());
418        }
419        
420        
421        /**
422         * Sets a single verified claims set requested at the UserInfo
423         * endpoint.
424         *
425         * @param userInfoVerified The UserInfo verified claims request,
426         *                         {@code null} if not specified.
427         *
428         * @return The updated OpenID claims request.
429         */
430        public OIDCClaimsRequest withUserInfoVerifiedClaimsRequest(final VerifiedClaimsSetRequest userInfoVerified) {
431                return new OIDCClaimsRequest(
432                        getIDTokenClaimsRequest(),
433                        getUserInfoClaimsRequest(),
434                        getIDTokenVerifiedClaimsRequests(),
435                        userInfoVerified != null ? Collections.singletonList(userInfoVerified) : Collections.<VerifiedClaimsSetRequest>emptyList());
436        }
437        
438        
439        /**
440         * Sets a single verified claims set requested at the UserInfo
441         * endpoint.
442         *
443         * @param userInfoVerified The UserInfo verified claims request,
444         *                         {@code null} if not specified.
445         *
446         * @return The updated OpenID claims request.
447         */
448        @Deprecated
449        public OIDCClaimsRequest withUserInfoVerifiedClaimsRequest(final com.nimbusds.openid.connect.sdk.assurance.claims.VerifiedClaimsSetRequest userInfoVerified) {
450                return new OIDCClaimsRequest(
451                        getIDTokenClaimsRequest(),
452                        getUserInfoClaimsRequest(),
453                        getIDTokenVerifiedClaimsRequests(),
454                        userInfoVerified != null ? toCurrent(Collections.singletonList(userInfoVerified)) : Collections.<VerifiedClaimsSetRequest>emptyList());
455        }
456        
457        
458        private static JSONObject addVerified(final List<VerifiedClaimsSetRequest> verified,
459                                              final JSONObject containingJSONObject) {
460                
461                if (verified != null) {
462                        
463                        if (verified.size() == 1 && verified.get(0) != null) {
464                                JSONObject out = new JSONObject();
465                                if (containingJSONObject != null) {
466                                        out.putAll(containingJSONObject);
467                                }
468                                out.put("verified_claims", verified.get(0).toJSONObject());
469                                return out;
470                        } else if (verified.size() > 1) {
471                                JSONObject out = new JSONObject();
472                                if (containingJSONObject != null) {
473                                        out.putAll(containingJSONObject);
474                                }
475                                JSONArray jsonArray = new JSONArray();
476                                for (VerifiedClaimsSetRequest verifiedClaims: verified) {
477                                        jsonArray.add(verifiedClaims.toJSONObject());
478                                }
479                                out.put("verified_claims", jsonArray);
480                                return out;
481                        }
482                }
483                return containingJSONObject;
484        }
485        
486        
487        /**
488         * Returns the JSON object representation of this OpenID claims
489         * request.
490         *
491         * <p>Example:
492         *
493         * <pre>
494         * {
495         *   "userinfo":
496         *    {
497         *     "given_name": {"essential": true},
498         *     "nickname": null,
499         *     "email": {"essential": true},
500         *     "email_verified": {"essential": true},
501         *     "picture": null,
502         *     "http://example.info/claims/groups": null
503         *    },
504         *   "id_token":
505         *    {
506         *     "auth_time": {"essential": true},
507         *     "acr": {"values": ["urn:mace:incommon:iap:silver"] }
508         *    }
509         * }
510         * </pre>
511         *
512         * @return The JSON object, empty if no ID token and UserInfo claims
513         *         are specified.
514         */
515        public JSONObject toJSONObject() {
516                
517                JSONObject o = new JSONObject();
518                
519                // id_token
520                JSONObject idTokenJSONObject = null;
521                if (idToken != null) {
522                         idTokenJSONObject = idToken.toJSONObject();
523                }
524                idTokenJSONObject = addVerified(idTokenVerified, idTokenJSONObject);
525                if (idTokenJSONObject != null && ! idTokenJSONObject.isEmpty()) {
526                        o.put("id_token", idTokenJSONObject);
527                }
528                
529                // userinfo
530                JSONObject userInfoJSONObject = null;
531                if (userInfo != null) {
532                         userInfoJSONObject = userInfo.toJSONObject();
533                }
534                userInfoJSONObject = addVerified(userInfoVerified, userInfoJSONObject);
535                if (userInfoJSONObject != null && ! userInfoJSONObject.isEmpty()) {
536                        o.put("userinfo", userInfoJSONObject);
537                }
538                
539                return o;
540        }
541        
542        
543        @Override
544        public String toJSONString() {
545                return toJSONObject().toJSONString();
546        }
547        
548        
549        @Override
550        public String toString() {
551                
552                return toJSONString();
553        }
554        
555        
556        /**
557         * Resolves the OpenID claims request for the specified response type
558         * and scope. The scope values that are {@link OIDCScopeValue standard
559         * OpenID scope values} are resolved to their respective individual
560         * claims requests, any other scope values are ignored.
561         *
562         * @param responseType The response type. Must not be {@code null}.
563         * @param scope        The scope, {@code null} if not specified (for a
564         *                     plain OAuth 2.0 authorisation request with no
565         *                     scope explicitly specified).
566         *
567         * @return The OpenID claims request.
568         */
569        public static OIDCClaimsRequest resolve(final ResponseType responseType, final Scope scope) {
570                
571                return resolve(responseType, scope, Collections.<Scope.Value, Set<String>>emptyMap());
572        }
573        
574        
575        /**
576         * Resolves the OpenID claims request for the specified response type
577         * and scope. The scope values that are {@link OIDCScopeValue standard
578         * OpenID scope values} are resolved to their respective individual
579         * claims requests, any other scope values are checked in the specified
580         * custom claims map and resolved accordingly.
581         *
582         * @param responseType The response type. Must not be {@code null}.
583         * @param scope        The scope, {@code null} if not specified (for a
584         *                     plain OAuth 2.0 authorisation request with no
585         *                     scope explicitly specified).
586         * @param customClaims Custom scope value to set of claim names map,
587         *                     {@code null} if not specified.
588         *
589         * @return The OpenID claims request.
590         */
591        public static OIDCClaimsRequest resolve(final ResponseType responseType,
592                                                final Scope scope,
593                                                final Map<Scope.Value, Set<String>> customClaims) {
594                
595                OIDCClaimsRequest claimsRequest = new OIDCClaimsRequest();
596                
597                if (scope == null) {
598                        // Plain OAuth 2.0 mode
599                        return claimsRequest;
600                }
601                
602                List<ClaimsSetRequest.Entry> entries = new LinkedList<>();
603                for (Scope.Value value : scope) {
604                        
605                        if (value.equals(OIDCScopeValue.PROFILE)) {
606                                
607                                entries.addAll(OIDCScopeValue.PROFILE.toClaimsSetRequestEntries());
608                                
609                        } else if (value.equals(OIDCScopeValue.EMAIL)) {
610                                
611                                entries.addAll(OIDCScopeValue.EMAIL.toClaimsSetRequestEntries());
612                                
613                        } else if (value.equals(OIDCScopeValue.PHONE)) {
614                                
615                                entries.addAll(OIDCScopeValue.PHONE.toClaimsSetRequestEntries());
616                                
617                        } else if (value.equals(OIDCScopeValue.ADDRESS)) {
618                                
619                                entries.addAll(OIDCScopeValue.ADDRESS.toClaimsSetRequestEntries());
620                                
621                        } else if (customClaims != null && customClaims.containsKey(value)) {
622                                
623                                // Process custom scope value -> claim names expansion, e.g.
624                                // "corp_profile" -> ["employeeNumber", "dept", "ext"]
625                                Set<String> claimNames = customClaims.get(value);
626                                
627                                if (claimNames == null || claimNames.isEmpty()) {
628                                        continue; // skip
629                                }
630                                
631                                for (String claimName : claimNames) {
632                                        entries.add(new ClaimsSetRequest.Entry(claimName).withClaimRequirement(ClaimRequirement.VOLUNTARY));
633                                }
634                                
635                        }
636                }
637                
638                if (entries.isEmpty()) {
639                        return claimsRequest;
640                }
641                        
642                ClaimsSetRequest claimsSetRequest = new ClaimsSetRequest(entries);
643                
644                // Determine the claims target (ID token or UserInfo)
645                final boolean switchToIDToken =
646                        responseType.contains(OIDCResponseTypeValue.ID_TOKEN) &&
647                                !responseType.contains(ResponseType.Value.CODE) &&
648                                !responseType.contains(ResponseType.Value.TOKEN);
649                        
650                if (switchToIDToken) {
651                        return claimsRequest.withIDTokenClaimsRequest(claimsSetRequest);
652                } else {
653                        return claimsRequest.withUserInfoClaimsRequest(claimsSetRequest);
654                }
655        }
656        
657        
658        /**
659         * Resolves the merged OpenID claims request from the specified OpenID
660         * authentication request parameters. The scope values that are {@link
661         * OIDCScopeValue standard OpenID scope values} are resolved to their
662         * respective individual claims requests, any other scope values are
663         * ignored.
664         *
665         * @param responseType  The response type. Must not be {@code null}.
666         * @param scope         The scope, {@code null} if not specified (for a
667         *                      plain OAuth 2.0 authorisation request with no
668         *                      scope explicitly specified).
669         * @param claimsRequest The OpenID claims request, corresponding to the
670         *                      optional {@code claims} OpenID authentication
671         *                      request parameter, {@code null} if not
672         *                      specified.
673         *
674         * @return The merged OpenID claims request.
675         */
676        public static OIDCClaimsRequest resolve(final ResponseType responseType,
677                                                final Scope scope,
678                                                final OIDCClaimsRequest claimsRequest) {
679                
680                return resolve(responseType, scope, claimsRequest, Collections.<Scope.Value, Set<String>>emptyMap());
681        }
682        
683        
684        /**
685         * Resolves the merged OpenID claims request from the specified OpenID
686         * authentication request parameters. The scope values that are {@link
687         * OIDCScopeValue standard OpenID scope values} are resolved to their
688         * respective individual claims requests, any other scope values are
689         * checked in the specified custom claims map and resolved accordingly.
690         *
691         * @param responseType  The response type. Must not be {@code null}.
692         * @param scope         The scope, {@code null} if not specified (for a
693         *                      plain OAuth 2.0 authorisation request with no
694         *                      scope explicitly specified).
695         * @param claimsRequest The OpenID claims request, corresponding to the
696         *                      optional {@code claims} OpenID authentication
697         *                      request parameter, {@code null} if not
698         *                      specified.
699         * @param customClaims  Custom scope value to set of claim names map,
700         *                      {@code null} if not specified.
701         *
702         * @return The merged OpenID claims request.
703         */
704        public static OIDCClaimsRequest resolve(final ResponseType responseType,
705                                                final Scope scope,
706                                                final OIDCClaimsRequest claimsRequest,
707                                                final Map<Scope.Value, Set<String>> customClaims) {
708                
709                return resolve(responseType, scope, customClaims).add(claimsRequest);
710        }
711        
712        
713        /**
714         * Resolves the merged OpenID claims request for the specified OpenID
715         * authentication request. The scope values that are {@link
716         * OIDCScopeValue standard OpenID scope values} are resolved to their
717         * respective individual claims requests, any other scope values are
718         * ignored.
719         *
720         * @param authRequest The OpenID authentication request. Must not be
721         *                    {@code null}.
722         *
723         * @return The merged OpenID claims request.
724         */
725        public static OIDCClaimsRequest resolve(final AuthenticationRequest authRequest) {
726                
727                return resolve(authRequest.getResponseType(), authRequest.getScope(), authRequest.getOIDCClaims());
728        }
729        
730        
731        private static VerifiedClaimsSetRequest parseVerifiedClaimsSetRequest(final JSONObject jsonObject,
732                                                                              final int position)
733                throws ParseException {
734                
735                try {
736                        return VerifiedClaimsSetRequest.parse(jsonObject);
737                } catch (ParseException e) {
738                        throw new ParseException("Invalid verified claims request" +
739                                (position > -1 ? " at position " + position : "") +
740                                ": " + e.getMessage(),
741                                e);
742                }
743        }
744        
745        
746        private static List<VerifiedClaimsSetRequest> parseVerified(final JSONObject containingJSONObject)
747                throws ParseException {
748                
749                if (! containingJSONObject.containsKey("verified_claims")) {
750                        // No verified claims
751                        return Collections.emptyList();
752                }
753                
754                if (containingJSONObject.get("verified_claims") instanceof JSONObject) {
755                        // Single verified claims element
756                        JSONObject vo = JSONObjectUtils.getJSONObject(containingJSONObject, "verified_claims");
757                        return Collections.singletonList(parseVerifiedClaimsSetRequest(vo, -1));
758                        
759                } else {
760                        // Array of one or more verified claims elements
761                        JSONArray va = JSONObjectUtils.getJSONArray(containingJSONObject, "verified_claims");
762                        List<VerifiedClaimsSetRequest> out = new LinkedList<>();
763                        int pos = 0;
764                        for (JSONObject vo: JSONArrayUtils.toJSONObjectList(va)) {
765                                out.add(parseVerifiedClaimsSetRequest(vo, pos++));
766                        }
767                        return out;
768                }
769        }
770        
771        
772        /**
773         * Parses an OpenID claims request from the specified JSON object
774         * representation.
775         *
776         * <p>Example:
777         *
778         * <pre>
779         * {
780         *   "userinfo":
781         *    {
782         *     "given_name": {"essential": true},
783         *     "nickname": null,
784         *     "email": {"essential": true},
785         *     "email_verified": {"essential": true},
786         *     "picture": null,
787         *     "http://example.info/claims/groups": null
788         *    },
789         *   "id_token":
790         *    {
791         *     "auth_time": {"essential": true},
792         *     "acr": {"values": ["urn:mace:incommon:iap:silver"] }
793         *    }
794         * }
795         * </pre>
796         *
797         * @param jsonObject The JSON object to parse. Must not be
798         *                   {@code null}.
799         *
800         * @return The OpenID claims request.
801         *
802         * @throws ParseException If parsing failed.
803         */
804        public static OIDCClaimsRequest parse(final JSONObject jsonObject)
805                throws ParseException {
806                
807                OIDCClaimsRequest claimsRequest = new OIDCClaimsRequest();
808                
809                JSONObject idTokenObject = JSONObjectUtils.getJSONObject(jsonObject, "id_token", null);
810                
811                if (idTokenObject != null) {
812                        ClaimsSetRequest csr = ClaimsSetRequest.parse(idTokenObject);
813                        if (! csr.getEntries().isEmpty()) {
814                                claimsRequest = claimsRequest.withIDTokenClaimsRequest(csr);
815                        }
816                        claimsRequest = claimsRequest.withIDTokenVerifiedClaimsRequests(parseVerified(idTokenObject));
817                }
818                
819                JSONObject userInfoObject = JSONObjectUtils.getJSONObject(jsonObject, "userinfo", null);
820                
821                if (userInfoObject != null) {
822                        ClaimsSetRequest csr = ClaimsSetRequest.parse(userInfoObject);
823                        if (! csr.getEntries().isEmpty()) {
824                                claimsRequest = claimsRequest.withUserInfoClaimsRequest(ClaimsSetRequest.parse(userInfoObject));
825                        }
826                        claimsRequest = claimsRequest.withUserInfoVerifiedClaimsRequests(parseVerified(userInfoObject));
827                }
828                
829                return claimsRequest;
830        }
831        
832        
833        /**
834         * Parses an OpenID claims request from the specified JSON object
835         * string representation.
836         *
837         * <p>Example:
838         *
839         * <pre>
840         * {
841         *   "userinfo":
842         *    {
843         *     "given_name": {"essential": true},
844         *     "nickname": null,
845         *     "email": {"essential": true},
846         *     "email_verified": {"essential": true},
847         *     "picture": null,
848         *     "http://example.info/claims/groups": null
849         *    },
850         *   "id_token":
851         *    {
852         *     "auth_time": {"essential": true},
853         *     "acr": {"values": ["urn:mace:incommon:iap:silver"] }
854         *    }
855         * }
856         * </pre>
857         *
858         * @param json The JSON object string to parse. Must not be
859         *             {@code null}.
860         *
861         * @return The OpenID claims request.
862         *
863         * @throws ParseException If parsing failed.
864         */
865        public static OIDCClaimsRequest parse(final String json)
866                throws ParseException {
867
868                JSONObject jsonObject;
869                try {
870                        jsonObject = JSONObjectUtils.parse(json);
871                } catch (ParseException e) {
872                        throw new ParseException("Invalid JSON");
873                }
874                return parse(jsonObject);
875        }
876}