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