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;
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.langtag.LangTag;
029import com.nimbusds.langtag.LangTagException;
030import com.nimbusds.oauth2.sdk.OAuth2Error;
031import com.nimbusds.oauth2.sdk.ParseException;
032import com.nimbusds.oauth2.sdk.ResponseType;
033import com.nimbusds.oauth2.sdk.Scope;
034import com.nimbusds.oauth2.sdk.util.JSONArrayUtils;
035import com.nimbusds.oauth2.sdk.util.JSONObjectUtils;
036import com.nimbusds.openid.connect.sdk.claims.ClaimRequirement;
037
038
039/**
040 * Specifies the individual OpenID claims to return from the UserInfo endpoint
041 * and / or in the ID Token.
042 *
043 * <p>Related specifications:
044 *
045 * <ul>
046 *     <li>OpenID Connect Core 1.0, section 5.5.
047 *     <li>OpenID Connect for Identity Assurance 1.0.
048 * </ul>
049 */
050@Deprecated
051public class ClaimsRequest implements JSONAware {
052        
053        
054        /**
055         * Individual OpenID claim request.
056         *
057         * <p>Related specifications:
058         *
059         * <ul>
060         *     <li>OpenID Connect Core 1.0, section 5.5.1.
061         *     <li>OpenID Connect for Identity Assurance 1.0.
062         * </ul>
063         */
064        @Immutable
065        public static class Entry {
066                
067                
068                /**
069                 * The claim name.
070                 */
071                private final String claimName;
072                
073                
074                /**
075                 * The claim requirement.
076                 */
077                private final ClaimRequirement requirement;
078                
079                
080                /**
081                 * Optional language tag.
082                 */
083                private final LangTag langTag;
084                
085                
086                /**
087                 * Optional claim value.
088                 */
089                private final String value;
090                
091                
092                /**
093                 * Optional claim values.
094                 */
095                private final List<String> values;
096                
097                
098                /**
099                 * The claim purpose.
100                 */
101                private final String purpose;
102                
103                
104                /**
105                 * Optional additional claim information.
106                 *
107                 * <p>Example additional information in the "info" member:
108                 *
109                 * <pre>
110                 * {
111                 *   "userinfo" : {
112                 *       "email": null,
113                 *       "email_verified": null,
114                 *       "http://example.info/claims/groups" : { "info" : "custom information" } }
115                 * }
116                 * </pre>
117                 */
118                private final Map<String, Object> additionalInformation;
119                
120                
121                /**
122                 * Creates a new individual claim request. The claim
123                 * requirement is set to voluntary (the default) and no
124                 * expected value(s) or other parameters are specified.
125                 *
126                 * @param claimName The claim name. Must not be {@code null}.
127                 */
128                public Entry(final String claimName) {
129                        
130                        this(claimName, ClaimRequirement.VOLUNTARY, null, null, null, null, null);
131                }
132                
133                
134                /**
135                 * Creates a new individual claim request. The claim
136                 * requirement is set to voluntary (the default) and no
137                 * expected value(s) are specified.
138                 *
139                 * @param claimName The claim name. Must not be {@code null}.
140                 * @param langTag   Optional language tag for the claim.
141                 */
142                @Deprecated
143                public Entry(final String claimName, final LangTag langTag) {
144                        
145                        this(claimName, ClaimRequirement.VOLUNTARY, langTag, null, null);
146                }
147                
148                
149                /**
150                 * Creates a new individual claim request.
151                 *
152                 * @param claimName   The claim name. Must not be {@code null}.
153                 * @param requirement The claim requirement. Must not be
154                 *                    {@code null}.
155                 */
156                @Deprecated
157                public Entry(final String claimName, final ClaimRequirement requirement) {
158                        
159                        this(claimName, requirement, null, null, null);
160                }
161                
162                
163                /**
164                 * Creates a new individual claim request.
165                 *
166                 * @param claimName   The claim name. Must not be {@code null}.
167                 * @param requirement The claim requirement. Must not be
168                 *                    {@code null}.
169                 * @param langTag     Optional language tag for the claim.
170                 * @param value       Optional expected value for the claim.
171                 */
172                @Deprecated
173                public Entry(final String claimName, final ClaimRequirement requirement,
174                             final LangTag langTag, final String value) {
175                        
176                        this(claimName, requirement, langTag, value, null);
177                }
178                
179                
180                /**
181                 * Creates a new individual claim request.
182                 *
183                 * @param claimName   The claim name. Must not be {@code null}.
184                 * @param requirement The claim requirement. Must not be
185                 *                    {@code null}.
186                 * @param langTag     Optional language tag for the claim.
187                 * @param values      Optional expected values for the claim.
188                 */
189                @Deprecated
190                public Entry(final String claimName, final ClaimRequirement requirement,
191                             final LangTag langTag, final List<String> values) {
192                        
193                        this(claimName, requirement, langTag, null, values, null, null);
194                }
195                
196                
197                /**
198                 * Creates a new individual claim request. This constructor is
199                 * to be used privately. Ensures that {@code value} and
200                 * {@code values} are not simultaneously specified.
201                 *
202                 * @param claimName   The claim name. Must not be {@code null}.
203                 * @param requirement The claim requirement. Must not be
204                 *                    {@code null}.
205                 * @param langTag     Optional language tag for the claim.
206                 * @param value       Optional expected value for the claim. If
207                 *                    set, then the {@code values} parameter
208                 *                    must not be set.
209                 * @param values      Optional expected values for the claim.
210                 *                    If set, then the {@code value} parameter
211                 *                    must not be set.
212                 */
213                @Deprecated
214                private Entry(final String claimName, final ClaimRequirement requirement, final LangTag langTag,
215                              final String value, final List<String> values) {
216                        this(claimName, requirement, langTag, value, values, null, null);
217                }
218                
219                
220                /**
221                 * Creates a new individual claim request. This constructor is
222                 * to be used privately. Ensures that {@code value} and
223                 * {@code values} are not simultaneously specified.
224                 *
225                 * @param claimName             The claim name. Must not be
226                 *                              {@code null}.
227                 * @param requirement           The claim requirement. Must not
228                 *                              be {@code null}.
229                 * @param langTag               Optional language tag for the
230                 *                              claim.
231                 * @param value                 Optional expected value for the
232                 *                              claim. If set, then the {@code
233                 *                              values} parameter must not be
234                 *                              set.
235                 * @param values                Optional expected values for
236                 *                              the claim. If set, then the
237                 *                              {@code value} parameter must
238                 *                              not be set.
239                 * @param purpose               The purpose for the requested
240                 *                              claim, {@code null} if not
241                 *                              specified.
242                 * @param additionalInformation Optional additional information
243                 */
244                private Entry(final String claimName,
245                              final ClaimRequirement requirement,
246                              final LangTag langTag,
247                              final String value,
248                              final List<String> values,
249                              final String purpose,
250                              final Map<String, Object> additionalInformation) {
251                        
252                        if (claimName == null)
253                                throw new IllegalArgumentException("The claim name must not be null");
254                        
255                        this.claimName = claimName;
256                        
257                        
258                        if (requirement == null)
259                                throw new IllegalArgumentException("The claim requirement must not be null");
260                        
261                        this.requirement = requirement;
262                        
263                        
264                        this.langTag = langTag;
265                        
266                        
267                        if (value != null && values == null) {
268                                
269                                this.value = value;
270                                this.values = null;
271                                
272                        } else if (value == null && values != null) {
273                                
274                                this.value = null;
275                                this.values = values;
276                                
277                        } else if (value == null && values == null) {
278                                
279                                this.value = null;
280                                this.values = null;
281                                
282                        } else {
283                                
284                                throw new IllegalArgumentException("Either value or values must be specified, but not both");
285                        }
286                        
287                        this.purpose = purpose;
288                        
289                        this.additionalInformation = additionalInformation;
290                }
291                
292                
293                /**
294                 * Returns the claim name.
295                 *
296                 * @return The claim name.
297                 */
298                public String getClaimName() {
299                        
300                        return claimName;
301                }
302                
303                
304                /**
305                 * Returns the claim name, optionally with the language tag
306                 * appended.
307                 *
308                 * <p>Example with language tag:
309                 *
310                 * <pre>
311                 * name#de-DE
312                 * </pre>
313                 *
314                 * @param withLangTag If {@code true} the language tag will be
315                 *                    appended to the name (if any), else not.
316                 *
317                 * @return The claim name, with optionally appended language
318                 *         tag.
319                 */
320                public String getClaimName(final boolean withLangTag) {
321                        
322                        if (withLangTag && langTag != null)
323                                return claimName + "#" + langTag.toString();
324                        else
325                                return claimName;
326                }
327                
328                
329                /**
330                 * Returns a new claim entry with the specified requirement.
331                 *
332                 * @param requirement The claim requirement.
333                 *
334                 * @return The new entry.
335                 */
336                public Entry withClaimRequirement(final ClaimRequirement requirement) {
337                        
338                        return new Entry(claimName, requirement, langTag, value, values, purpose, additionalInformation);
339                }
340                
341                
342                /**
343                 * Returns the claim requirement.
344                 *
345                 * @return The claim requirement.
346                 */
347                public ClaimRequirement getClaimRequirement() {
348                        
349                        return requirement;
350                }
351                
352                
353                /**
354                 * Returns a new claim entry with the specified language tag
355                 * for the claim.
356                 *
357                 * @param langTag The language tag, {@code null} if not
358                 *                specified.
359                 *
360                 * @return The new entry.
361                 */
362                public Entry withLangTag(final LangTag langTag) {
363                        
364                        return new Entry(claimName, requirement, langTag, value, values, purpose, additionalInformation);
365                }
366                
367                
368                /**
369                 * Returns the optional language tag for the claim.
370                 *
371                 * @return The language tag, {@code null} if not specified.
372                 */
373                public LangTag getLangTag() {
374                        
375                        return langTag;
376                }
377                
378                
379                /**
380                 * Returns a new claim entry with the specified requested value
381                 * for the claim.
382                 *
383                 * @param value The value, {@code null} if not specified.
384                 *
385                 * @return The new entry.
386                 */
387                public Entry withValue(final String value) {
388                        
389                        return new Entry(claimName, requirement, langTag, value, null, purpose, additionalInformation);
390                }
391                
392                
393                /**
394                 * Returns the requested value for the claim.
395                 *
396                 * @return The value, {@code null} if not specified.
397                 */
398                public String getValue() {
399                        
400                        return value;
401                }
402                
403                
404                /**
405                 * Returns a new claim entry with the specified requested
406                 * values for the claim.
407                 *
408                 * @param values The values, {@code null} if not specified.
409                 *
410                 * @return The new entry.
411                 */
412                public Entry withValues(final List<String> values) {
413                        
414                        return new Entry(claimName, requirement, langTag, null, values, purpose, additionalInformation);
415                }
416                
417                
418                /**
419                 * Returns the optional values for the claim.
420                 *
421                 * @return The values, {@code null} if not specified.
422                 */
423                public List<String> getValues() {
424                        
425                        return values;
426                }
427                
428                
429                /**
430                 * Returns a new claim entry with the specified purpose for the
431                 * requested claim.
432                 *
433                 * @param purpose The purpose, {@code null} if not specified.
434                 *
435                 * @return The new entry.
436                 */
437                public Entry withPurpose(final String purpose) {
438                        
439                        return new Entry(claimName, requirement, langTag, value, values, purpose, additionalInformation);
440                }
441                
442                
443                /**
444                 * Returns the optional purpose for the requested claim.
445                 *
446                 * @return The purpose, {@code null} if not specified.
447                 */
448                public String getPurpose() {
449                        
450                        return purpose;
451                }
452                
453                
454                /**
455                 * Returns a new claim entry with the specified additional
456                 * information for the claim.
457                 *
458                 * <p>Example additional information in the "info" member:
459                 *
460                 * <pre>
461                 * {
462                 *   "userinfo" : {
463                 *       "email": null,
464                 *       "email_verified": null,
465                 *       "http://example.info/claims/groups" : { "info" : "custom information" } }
466                 * }
467                 * </pre>
468                 *
469                 * @param additionalInformation The additional information,
470                 *                              {@code null} if not specified.
471                 *
472                 * @return The new entry.
473                 */
474                public Entry withAdditionalInformation(final Map<String, Object> additionalInformation) {
475                        
476                        return new Entry(claimName, requirement, langTag, value, values, purpose, additionalInformation);
477                }
478                
479                
480                /**
481                 * Returns the additional information for the claim.
482                 *
483                 * <p>Example additional information in the "info" member:
484                 *
485                 * <pre>
486                 * {
487                 *   "userinfo" : {
488                 *       "email": null,
489                 *       "email_verified": null,
490                 *       "http://example.info/claims/groups" : { "info" : "custom information" } }
491                 * }
492                 * </pre>
493                 *
494                 * @return The additional information, {@code null} if not
495                 *         specified.
496                 */
497                public Map<String, Object> getAdditionalInformation() {
498                        return additionalInformation;
499                }
500                
501                
502                /**
503                 * Returns the JSON object representation of the specified
504                 * collection of individual claim requests.
505                 *
506                 * <p>Example:
507                 *
508                 * <pre>
509                 * {
510                 *   "given_name": {"essential": true},
511                 *   "nickname": null,
512                 *   "email": {"essential": true},
513                 *   "email_verified": {"essential": true},
514                 *   "picture": null,
515                 *   "http://example.info/claims/groups": null
516                 * }
517                 * </pre>
518                 *
519                 * @param entries The entries to serialise. Must not be
520                 *                {@code null}.
521                 * @return The corresponding JSON object, empty if no claims
522                 *         were found.
523                 */
524                public static JSONObject toJSONObject(final Collection<Entry> entries) {
525                        
526                        JSONObject o = new JSONObject();
527                        
528                        for (Entry entry : entries) {
529                                
530                                // Compose the optional value
531                                JSONObject entrySpec = null;
532                                
533                                if (entry.getValue() != null) {
534                                        
535                                        entrySpec = new JSONObject();
536                                        entrySpec.put("value", entry.getValue());
537                                }
538                                
539                                if (entry.getValues() != null) {
540                                        
541                                        // Either "value" or "values", or none
542                                        // may be defined
543                                        entrySpec = new JSONObject();
544                                        entrySpec.put("values", entry.getValues());
545                                }
546                                
547                                if (entry.getClaimRequirement().equals(ClaimRequirement.ESSENTIAL)) {
548                                        
549                                        if (entrySpec == null)
550                                                entrySpec = new JSONObject();
551                                        
552                                        entrySpec.put("essential", true);
553                                }
554                                
555                                if (entry.getPurpose() != null) {
556                                        if (entrySpec == null) {
557                                                entrySpec = new JSONObject();
558                                        }
559                                        entrySpec.put("purpose", entry.getPurpose());
560                                }
561                                
562                                if (entry.getAdditionalInformation() != null) {
563                                        if (entrySpec == null) {
564                                                entrySpec = new JSONObject();
565                                        }
566                                        for (Map.Entry<String, Object> additionalInformationEntry : entry.getAdditionalInformation().entrySet()) {
567                                                entrySpec.put(additionalInformationEntry.getKey(), additionalInformationEntry.getValue());
568                                        }
569                                }
570                                
571                                o.put(entry.getClaimName(true), entrySpec);
572                        }
573                        
574                        return o;
575                }
576                
577                
578                /**
579                 * Parses a collection of individual claim requests from the
580                 * specified JSON object. Request entries that are not
581                 * understood are silently ignored.
582                 *
583                 * @param jsonObject The JSON object to parse. Must not be
584                 *                   {@code null}.
585                 *
586                 * @return The collection of claim requests.
587                 */
588                public static Collection<Entry> parseEntries(final JSONObject jsonObject) {
589                        
590                        Collection<Entry> entries = new LinkedList<>();
591                        
592                        if (jsonObject.isEmpty())
593                                return entries;
594                        
595                        for (Map.Entry<String, Object> member : jsonObject.entrySet()) {
596                                
597                                // Process the key
598                                String claimNameWithOptLangTag = member.getKey();
599                                
600                                String claimName;
601                                LangTag langTag = null;
602                                
603                                if (claimNameWithOptLangTag.contains("#")) {
604                                        
605                                        String[] parts = claimNameWithOptLangTag.split("#", 2);
606                                        
607                                        claimName = parts[0];
608                                        
609                                        try {
610                                                langTag = LangTag.parse(parts[1]);
611                                                
612                                        } catch (LangTagException e) {
613                                                
614                                                // Ignore and continue
615                                                continue;
616                                        }
617                                        
618                                } else {
619                                        claimName = claimNameWithOptLangTag;
620                                }
621                                
622                                // Parse the optional value
623                                if (member.getValue() == null) {
624                                        
625                                        // Voluntary claim with no value(s)
626                                        entries.add(new Entry(claimName, langTag));
627                                        continue;
628                                }
629                                
630                                try {
631                                        JSONObject entrySpec = (JSONObject) member.getValue();
632                                        
633                                        ClaimRequirement requirement = ClaimRequirement.VOLUNTARY;
634                                        
635                                        if (entrySpec.containsKey("essential")) {
636                                                
637                                                boolean isEssential = (Boolean) entrySpec.get("essential");
638                                                
639                                                if (isEssential)
640                                                        requirement = ClaimRequirement.ESSENTIAL;
641                                        }
642                                        
643                                        String purpose = null;
644                                        if (entrySpec.containsKey("purpose")) {
645                                                purpose = (String) entrySpec.get("purpose");
646                                        }
647                                        
648                                        if (entrySpec.containsKey("value")) {
649                                                
650                                                String expectedValue = (String) entrySpec.get("value");
651                                                Map<String, Object> additionalInformation = getAdditionalInformationFromClaim(entrySpec);
652                                                entries.add(new Entry(claimName, requirement, langTag, expectedValue, null, purpose, additionalInformation));
653                                                
654                                        } else if (entrySpec.containsKey("values")) {
655                                                
656                                                List<String> expectedValues = new LinkedList<>();
657                                                
658                                                for (Object v : (List) entrySpec.get("values")) {
659                                                        
660                                                        expectedValues.add((String) v);
661                                                }
662                                                Map<String, Object> additionalInformation = getAdditionalInformationFromClaim(entrySpec);
663                                                
664                                                entries.add(new Entry(claimName, requirement, langTag, null, expectedValues, purpose, additionalInformation));
665                                                
666                                        } else {
667                                                Map<String, Object> additionalInformation = getAdditionalInformationFromClaim(entrySpec);
668                                                entries.add(new Entry(claimName, requirement, langTag, null, null, purpose, additionalInformation));
669                                        }
670                                        
671                                } catch (Exception e) {
672                                        // Ignore and continue
673                                }
674                        }
675                        
676                        return entries;
677                }
678                
679                
680                private static Map<String, Object> getAdditionalInformationFromClaim(final JSONObject entrySpec) {
681                        
682                        Set<String> stdKeys = new HashSet<>(Arrays.asList("essential", "value", "values", "purpose"));
683                        
684                        Map<String, Object> additionalClaimInformation = new HashMap<>();
685                        
686                        for (Map.Entry<String, Object> additionalClaimInformationEntry : entrySpec.entrySet()) {
687                                if (stdKeys.contains(additionalClaimInformationEntry.getKey())) {
688                                        continue; // skip std key
689                                }
690                                additionalClaimInformation.put(additionalClaimInformationEntry.getKey(), additionalClaimInformationEntry.getValue());
691                        }
692                        
693                        return additionalClaimInformation.isEmpty() ? null : additionalClaimInformation;
694                }
695        }
696        
697        
698        /**
699         * The requested ID token claims, keyed by claim name and language tag.
700         */
701        private final Map<Map.Entry<String, LangTag>, Entry> idTokenClaims = new HashMap<>();
702        
703        
704        /**
705         * The requested verified ID token claims, keyed by claim name and
706         * language tag.
707         */
708        private final Map<Map.Entry<String, LangTag>, Entry> verifiedIDTokenClaims = new HashMap<>();
709        
710        
711        /**
712         * The verification element for the requested verified ID token claims.
713         */
714        private JSONObject idTokenClaimsVerification;
715        
716        
717        /**
718         * The requested UserInfo claims, keyed by claim name and language tag.
719         */
720        private final Map<Map.Entry<String, LangTag>, Entry> userInfoClaims = new HashMap<>();
721        
722        
723        /**
724         * The requested verified UserInfo claims, keyed by claim name and
725         * language tag.
726         */
727        private final Map<Map.Entry<String, LangTag>, Entry> verifiedUserInfoClaims = new HashMap<>();
728        
729        
730        /**
731         * The verification element for the requested verified UserInfo claims.
732         */
733        private JSONObject userInfoClaimsVerification;
734        
735        
736        /**
737         * Creates a new empty claims request.
738         */
739        public ClaimsRequest() {
740                
741                // Nothing to initialise
742        }
743        
744        
745        /**
746         * Adds the entries from the specified other claims request.
747         *
748         * @param other The other claims request. If {@code null} no claims
749         *              request entries will be added to this claims request.
750         */
751        public void add(final ClaimsRequest other) {
752                
753                if (other == null)
754                        return;
755                
756                idTokenClaims.putAll(other.idTokenClaims);
757                verifiedIDTokenClaims.putAll(other.verifiedIDTokenClaims);
758                idTokenClaimsVerification = other.idTokenClaimsVerification;
759                
760                userInfoClaims.putAll(other.userInfoClaims);
761                verifiedUserInfoClaims.putAll(other.verifiedUserInfoClaims);
762                userInfoClaimsVerification = other.userInfoClaimsVerification;
763        }
764        
765        
766        /**
767         * Adds the specified ID token claim to the request. It is marked as
768         * voluntary and no language tag and value(s) are associated with it.
769         *
770         * @param claimName The claim name. Must not be {@code null}.
771         */
772        public void addIDTokenClaim(final String claimName) {
773                
774                addIDTokenClaim(claimName, ClaimRequirement.VOLUNTARY);
775        }
776        
777        
778        /**
779         * Adds the specified ID token claim to the request. No language tag
780         * and value(s) are associated with it.
781         *
782         * @param claimName   The claim name. Must not be {@code null}.
783         * @param requirement The claim requirement. Must not be {@code null}.
784         */
785        public void addIDTokenClaim(final String claimName, final ClaimRequirement requirement) {
786                
787                addIDTokenClaim(claimName, requirement, null);
788        }
789        
790        
791        /**
792         * Adds the specified ID token claim to the request. No value(s) are
793         * associated with it.
794         *
795         * @param claimName   The claim name. Must not be {@code null}.
796         * @param requirement The claim requirement. Must not be {@code null}.
797         * @param langTag     The associated language tag, {@code null} if not
798         *                    specified.
799         */
800        public void addIDTokenClaim(final String claimName, final ClaimRequirement requirement,
801                                    final LangTag langTag) {
802                
803                addIDTokenClaim(claimName, requirement, langTag, (String) null);
804        }
805        
806        
807        /**
808         * Adds the specified ID token claim to the request.
809         *
810         * @param claimName   The claim name. Must not be {@code null}.
811         * @param requirement The claim requirement. Must not be {@code null}.
812         * @param langTag     The associated language tag, {@code null} if not
813         *                    specified.
814         * @param value       The expected claim value, {@code null} if not
815         *                    specified.
816         */
817        public void addIDTokenClaim(final String claimName, final ClaimRequirement requirement,
818                                    final LangTag langTag, final String value) {
819                
820                addIDTokenClaim(new Entry(claimName, requirement, langTag, value));
821        }
822        
823        
824        /**
825         * Adds the specified ID token claim to the request.
826         *
827         * @param claimName             The claim name. Must not be
828         *                              {@code null}.
829         * @param requirement           The claim requirement. Must not be
830         *                              {@code null}.
831         * @param langTag               The associated language tag,
832         *                              {@code null} if not specified.
833         * @param value                 The expected claim value, {@code null}
834         *                              if not specified.
835         * @param additionalInformation The additional information for this
836         *                              claim, {@code null} if not specified.
837         */
838        public void addIDTokenClaim(final String claimName, final ClaimRequirement requirement,
839                                    final LangTag langTag, final String value, final Map<String, Object> additionalInformation) {
840                
841                addIDTokenClaim(new Entry(claimName, requirement, langTag, value, null, null, additionalInformation));
842        }
843        
844        
845        /**
846         * Adds the specified ID token claim to the request.
847         *
848         * @param claimName   The claim name. Must not be {@code null}.
849         * @param requirement The claim requirement. Must not be {@code null}.
850         * @param langTag     The associated language tag, {@code null} if not
851         *                    specified.
852         * @param values      The expected claim values, {@code null} if not
853         *                    specified.
854         */
855        public void addIDTokenClaim(final String claimName, final ClaimRequirement requirement,
856                                    final LangTag langTag, final List<String> values) {
857                
858                addIDTokenClaim(new Entry(claimName, requirement, langTag, values));
859        }
860        
861        
862        /**
863         * Adds the specified ID token claim to the request.
864         *
865         * @param claimName             The claim name. Must not be
866         *                              {@code null}.
867         * @param requirement           The claim requirement. Must not be
868         *                              {@code null}.
869         * @param langTag               The associated language tag,
870         *                              {@code null} if not specified.
871         * @param values                The expected claim values, {@code null}
872         *                              if not specified.
873         * @param additionalInformation The additional information for this
874         *                              claim, {@code null} if not specified.
875         */
876        public void addIDTokenClaim(final String claimName, final ClaimRequirement requirement,
877                                    final LangTag langTag, final List<String> values, final Map<String, Object> additionalInformation) {
878                
879                addIDTokenClaim(new Entry(claimName, requirement, langTag, null, values, null, additionalInformation));
880        }
881        
882        
883        private static Map.Entry<String, LangTag> toKey(final Entry entry) {
884                
885                return new AbstractMap.SimpleImmutableEntry<>(
886                        entry.getClaimName(),
887                        entry.getLangTag());
888        }
889        
890        
891        /**
892         * Adds the specified ID token claim to the request.
893         *
894         * @param entry The individual ID token claim request. Must not be
895         *              {@code null}.
896         */
897        public void addIDTokenClaim(final Entry entry) {
898                
899                idTokenClaims.put(toKey(entry), entry);
900        }
901        
902        
903        /**
904         * Adds the specified verified ID token claim to the request.
905         *
906         * @param entry The individual verified ID token claim request. Must
907         *              not be {@code null}.
908         */
909        public void addVerifiedIDTokenClaim(final Entry entry) {
910                
911                verifiedIDTokenClaims.put(toKey(entry), entry);
912        }
913        
914        
915        /**
916         * Sets the {@code verification} element for the requested verified ID
917         * token claims.
918         *
919         * @param jsonObject The {@code verification} JSON object, {@code null}
920         *                   if not specified.
921         */
922        public void setIDTokenClaimsVerificationJSONObject(final JSONObject jsonObject) {
923                
924                this.idTokenClaimsVerification = jsonObject;
925        }
926        
927        
928        /**
929         * Gets the {@code verification} element for the requested verified ID
930         * token claims.
931         *
932         * @return The {@code verification} JSON object, {@code null} if not
933         *         specified.
934         */
935        public JSONObject getIDTokenClaimsVerificationJSONObject() {
936                
937                return idTokenClaimsVerification;
938        }
939        
940        
941        /**
942         * Gets the requested ID token claims.
943         *
944         * @return The ID token claims, as an unmodifiable collection, empty
945         *         set if none.
946         */
947        public Collection<Entry> getIDTokenClaims() {
948                
949                return Collections.unmodifiableCollection(idTokenClaims.values());
950        }
951        
952        
953        /**
954         * Gets the requested verified ID token claims.
955         *
956         * @return The verified ID token claims, as an unmodifiable collection,
957         *         empty set if none.
958         */
959        public Collection<Entry> getVerifiedIDTokenClaims() {
960                
961                return Collections.unmodifiableCollection(verifiedIDTokenClaims.values());
962        }
963        
964        
965        private static Set<String> getClaimNames(final Map<Map.Entry<String, LangTag>, Entry> claims, final boolean withLangTag) {
966                
967                Set<String> names = new HashSet<>();
968                
969                for (Entry en : claims.values())
970                        names.add(en.getClaimName(withLangTag));
971                
972                return Collections.unmodifiableSet(names);
973        }
974        
975        
976        /**
977         * Gets the names of the requested ID token claim names.
978         *
979         * @param withLangTag If {@code true} the language tags, if any, will
980         *                    be appended to the names, else not.
981         *
982         * @return The ID token claim names, as an unmodifiable set, empty set
983         *         if none.
984         */
985        public Set<String> getIDTokenClaimNames(final boolean withLangTag) {
986                
987                return getClaimNames(idTokenClaims, withLangTag);
988        }
989        
990        
991        /**
992         * Gets the names of the requested verified ID token claim names.
993         *
994         * @param withLangTag If {@code true} the language tags, if any, will
995         *                    be appended to the names, else not.
996         *
997         * @return The ID token claim names, as an unmodifiable set, empty set
998         *         if none.
999         */
1000        public Set<String> getVerifiedIDTokenClaimNames(final boolean withLangTag) {
1001                
1002                return getClaimNames(verifiedIDTokenClaims, withLangTag);
1003        }
1004        
1005        
1006        private static Map.Entry<String, LangTag> toKey(final String claimName, final LangTag langTag) {
1007                
1008                return new AbstractMap.SimpleImmutableEntry<>(claimName, langTag);
1009        }
1010        
1011        
1012        /**
1013         * Removes the specified ID token claim from the request.
1014         *
1015         * @param claimName The claim name. Must not be {@code null}.
1016         * @param langTag   The associated language tag, {@code null} if none.
1017         *
1018         * @return The removed ID token claim, {@code null} if not found.
1019         */
1020        public Entry removeIDTokenClaim(final String claimName, final LangTag langTag) {
1021                
1022                return idTokenClaims.remove(toKey(claimName, langTag));
1023        }
1024        
1025        
1026        /**
1027         * Removes the specified verified ID token claim from the request.
1028         *
1029         * @param claimName The claim name. Must not be {@code null}.
1030         * @param langTag   The associated language tag, {@code null} if none.
1031         *
1032         * @return The removed ID token claim, {@code null} if not found.
1033         */
1034        public Entry removeVerifiedIDTokenClaim(final String claimName, final LangTag langTag) {
1035                
1036                return verifiedIDTokenClaims.remove(toKey(claimName, langTag));
1037        }
1038        
1039        
1040        private static Collection<Entry> removeClaims(final Map<Map.Entry<String, LangTag>, Entry> claims, final String claimName) {
1041                
1042                Collection<Entry> removedClaims = new LinkedList<>();
1043                
1044                Iterator<Map.Entry<Map.Entry<String, LangTag>, Entry>> it = claims.entrySet().iterator();
1045                
1046                while (it.hasNext()) {
1047                        
1048                        Map.Entry<Map.Entry<String, LangTag>, Entry> reqEntry = it.next();
1049                        
1050                        if (reqEntry.getKey().getKey().equals(claimName)) {
1051                                
1052                                removedClaims.add(reqEntry.getValue());
1053                                
1054                                it.remove();
1055                        }
1056                }
1057                
1058                return Collections.unmodifiableCollection(removedClaims);
1059        }
1060        
1061        
1062        /**
1063         * Removes the specified ID token claims from the request, in all
1064         * existing language tag variations.
1065         *
1066         * @param claimName The claim name. Must not be {@code null}.
1067         *
1068         * @return The removed ID token claims, as an unmodifiable collection,
1069         *         empty set if none were found.
1070         */
1071        public Collection<Entry> removeIDTokenClaims(final String claimName) {
1072                
1073                return removeClaims(idTokenClaims, claimName);
1074        }
1075        
1076        
1077        /**
1078         * Removes the specified verified ID token claims from the request, in
1079         * all existing language tag variations.
1080         *
1081         * @param claimName The claim name. Must not be {@code null}.
1082         *
1083         * @return The removed ID token claims, as an unmodifiable collection,
1084         *         empty set if none were found.
1085         */
1086        public Collection<Entry> removeVerifiedIDTokenClaims(final String claimName) {
1087                
1088                return removeClaims(verifiedIDTokenClaims, claimName);
1089        }
1090        
1091        
1092        /**
1093         * Adds the specified UserInfo claim to the request. It is marked as
1094         * voluntary and no language tag and value(s) are associated with it.
1095         *
1096         * @param claimName The claim name. Must not be {@code null}.
1097         */
1098        public void addUserInfoClaim(final String claimName) {
1099                
1100                addUserInfoClaim(claimName, ClaimRequirement.VOLUNTARY);
1101        }
1102        
1103        
1104        /**
1105         * Adds the specified UserInfo claim to the request. No language tag and
1106         * value(s) are associated with it.
1107         *
1108         * @param claimName   The claim name. Must not be {@code null}.
1109         * @param requirement The claim requirement. Must not be {@code null}.
1110         */
1111        public void addUserInfoClaim(final String claimName, final ClaimRequirement requirement) {
1112                
1113                addUserInfoClaim(claimName, requirement, null);
1114        }
1115        
1116        
1117        /**
1118         * Adds the specified UserInfo claim to the request. No value(s) are
1119         * associated with it.
1120         *
1121         * @param claimName   The claim name. Must not be {@code null}.
1122         * @param requirement The claim requirement. Must not be {@code null}.
1123         * @param langTag     The associated language tag, {@code null} if not
1124         *                    specified.
1125         */
1126        public void addUserInfoClaim(final String claimName, final ClaimRequirement requirement,
1127                                     final LangTag langTag) {
1128                
1129                
1130                addUserInfoClaim(claimName, requirement, langTag, (String) null);
1131        }
1132        
1133        
1134        /**
1135         * Adds the specified UserInfo claim to the request.
1136         *
1137         * @param claimName   The claim name. Must not be {@code null}.
1138         * @param requirement The claim requirement. Must not be {@code null}.
1139         * @param langTag     The associated language tag, {@code null} if not
1140         *                    specified.
1141         * @param value       The expected claim value, {@code null} if not
1142         *                    specified.
1143         */
1144        public void addUserInfoClaim(final String claimName, final ClaimRequirement requirement,
1145                                     final LangTag langTag, final String value) {
1146                
1147                addUserInfoClaim(new Entry(claimName, requirement, langTag, value));
1148        }
1149        
1150        
1151        /**
1152         * Adds the specified UserInfo claim to the request.
1153         *
1154         * @param claimName             The claim name. Must not be {@code
1155         *                              null}.
1156         * @param requirement           The claim requirement. Must not be
1157         *                              {@code null}.
1158         * @param langTag               The associated language tag, {@code
1159         *                              null} if not specified.
1160         * @param value                 The expected claim value, {@code null}
1161         *                              if not specified.
1162         * @param additionalInformation The additional information for this
1163         *                              claim, {@code null} if not specified.
1164         */
1165        public void addUserInfoClaim(final String claimName, final ClaimRequirement requirement,
1166                                     final LangTag langTag, final String value, final Map<String, Object> additionalInformation) {
1167                
1168                addUserInfoClaim(new Entry(claimName, requirement, langTag, value, null, null, additionalInformation));
1169        }
1170        
1171        
1172        /**
1173         * Adds the specified UserInfo claim to the request.
1174         *
1175         * @param claimName   The claim name. Must not be {@code null}.
1176         * @param requirement The claim requirement. Must not be {@code null}.
1177         * @param langTag     The associated language tag, {@code null} if not
1178         *                    specified.
1179         * @param values      The expected claim values, {@code null} if not
1180         *                    specified.
1181         */
1182        public void addUserInfoClaim(final String claimName, final ClaimRequirement requirement,
1183                                     final LangTag langTag, final List<String> values) {
1184                
1185                addUserInfoClaim(new Entry(claimName, requirement, langTag, values));
1186        }
1187        
1188        
1189        /**
1190         * Adds the specified UserInfo claim to the request.
1191         *
1192         * @param claimName             The claim name. Must not be
1193         *                              {@code null}.
1194         * @param requirement           The claim requirement. Must not be
1195         *                              {@code null}.
1196         * @param langTag               The associated language tag,
1197         *                              {@code null} if not specified.
1198         * @param values                The expected claim values, {@code null}
1199         *                              if not specified.
1200         * @param additionalInformation The additional information for this
1201         *                              claim, {@code null} if not specified.
1202         */
1203        public void addUserInfoClaim(final String claimName, final ClaimRequirement requirement,
1204                                     final LangTag langTag, final List<String> values, final Map<String, Object> additionalInformation) {
1205                
1206                addUserInfoClaim(new Entry(claimName, requirement, langTag, null, values, null, additionalInformation));
1207        }
1208        
1209        
1210        /**
1211         * Adds the specified UserInfo claim to the request.
1212         *
1213         * @param entry The individual UserInfo claim request. Must not be
1214         *              {@code null}.
1215         */
1216        public void addUserInfoClaim(final Entry entry) {
1217                
1218                userInfoClaims.put(toKey(entry), entry);
1219        }
1220        
1221        
1222        /**
1223         * Adds the specified verified UserInfo claim to the request.
1224         *
1225         * @param entry The individual verified UserInfo claim request. Must
1226         *              not be {@code null}.
1227         */
1228        public void addVerifiedUserInfoClaim(final Entry entry) {
1229                
1230                verifiedUserInfoClaims.put(toKey(entry), entry);
1231        }
1232        
1233        
1234        /**
1235         * Sets the {@code verification} element for the requested verified
1236         * UserInfo claims.
1237         *
1238         * @param jsonObject The {@code verification} JSON object, {@code null}
1239         *                   if not specified.
1240         */
1241        public void setUserInfoClaimsVerificationJSONObject(final JSONObject jsonObject) {
1242                
1243                this.userInfoClaimsVerification = jsonObject;
1244        }
1245        
1246        
1247        /**
1248         * Gets the {@code verification} element for the requested verified
1249         * UserInfo claims.
1250         *
1251         * @return The {@code verification} JSON object, {@code null} if not
1252         *         specified.
1253         */
1254        public JSONObject getUserInfoClaimsVerificationJSONObject() {
1255                
1256                return userInfoClaimsVerification;
1257        }
1258        
1259        
1260        /**
1261         * Gets the requested UserInfo claims.
1262         *
1263         * @return The UserInfo claims, as an unmodifiable collection, empty
1264         *         set if none.
1265         */
1266        public Collection<Entry> getUserInfoClaims() {
1267                
1268                return Collections.unmodifiableCollection(userInfoClaims.values());
1269        }
1270        
1271        
1272        /**
1273         * Gets the requested verified UserInfo claims.
1274         *
1275         * @return The UserInfo claims, as an unmodifiable collection, empty
1276         *         set if none.
1277         */
1278        public Collection<Entry> getVerifiedUserInfoClaims() {
1279                
1280                return Collections.unmodifiableCollection(verifiedUserInfoClaims.values());
1281        }
1282        
1283        
1284        /**
1285         * Gets the names of the requested UserInfo claim names.
1286         *
1287         * @param withLangTag If {@code true} the language tags, if any, will
1288         *                    be appended to the names, else not.
1289         *
1290         * @return The UserInfo claim names, as an unmodifiable set, empty set
1291         *         if none.
1292         */
1293        public Set<String> getUserInfoClaimNames(final boolean withLangTag) {
1294                
1295                return getClaimNames(userInfoClaims, withLangTag);
1296        }
1297        
1298        
1299        /**
1300         * Gets the names of the requested verified UserInfo claim names.
1301         *
1302         * @param withLangTag If {@code true} the language tags, if any, will
1303         *                    be appended to the names, else not.
1304         *
1305         * @return The UserInfo claim names, as an unmodifiable set, empty set
1306         *         if none.
1307         */
1308        public Set<String> getVerifiedUserInfoClaimNames(final boolean withLangTag) {
1309                
1310                return getClaimNames(verifiedUserInfoClaims, withLangTag);
1311        }
1312        
1313        
1314        /**
1315         * Removes the specified UserInfo claim from the request.
1316         *
1317         * @param claimName The claim name. Must not be {@code null}.
1318         * @param langTag   The associated language tag, {@code null} if none.
1319         *
1320         * @return The removed UserInfo claim, {@code null} if not found.
1321         */
1322        public Entry removeUserInfoClaim(final String claimName, final LangTag langTag) {
1323                
1324                return userInfoClaims.remove(toKey(claimName, langTag));
1325        }
1326        
1327        
1328        /**
1329         * Removes the specified verified UserInfo claim from the request.
1330         *
1331         * @param claimName The claim name. Must not be {@code null}.
1332         * @param langTag   The associated language tag, {@code null} if none.
1333         *
1334         * @return The removed UserInfo claim, {@code null} if not found.
1335         */
1336        public Entry removeVerifiedUserInfoClaim(final String claimName, final LangTag langTag) {
1337                
1338                return verifiedUserInfoClaims.remove(toKey(claimName, langTag));
1339        }
1340        
1341        
1342        /**
1343         * Removes the specified UserInfo claims from the request, in all
1344         * existing language tag variations.
1345         *
1346         * @param claimName The claim name. Must not be {@code null}.
1347         *
1348         * @return The removed UserInfo claims, as an unmodifiable collection,
1349         *         empty set if none were found.
1350         */
1351        public Collection<Entry> removeUserInfoClaims(final String claimName) {
1352                
1353                return removeClaims(userInfoClaims, claimName);
1354        }
1355        
1356        
1357        /**
1358         * Removes the specified verified UserInfo claims from the request, in
1359         * all existing language tag variations.
1360         *
1361         * @param claimName The claim name. Must not be {@code null}.
1362         *
1363         * @return The removed UserInfo claims, as an unmodifiable collection,
1364         *         empty set if none were found.
1365         */
1366        public Collection<Entry> removeVerifiedUserInfoClaims(final String claimName) {
1367                
1368                return removeClaims(verifiedUserInfoClaims, claimName);
1369        }
1370        
1371        
1372        /**
1373         * Returns the JSON object representation of this claims request.
1374         *
1375         * <p>Example:
1376         *
1377         * <pre>
1378         * {
1379         *   "userinfo":
1380         *    {
1381         *     "given_name": {"essential": true},
1382         *     "nickname": null,
1383         *     "email": {"essential": true},
1384         *     "email_verified": {"essential": true},
1385         *     "picture": null,
1386         *     "http://example.info/claims/groups": null
1387         *    },
1388         *   "id_token":
1389         *    {
1390         *     "auth_time": {"essential": true},
1391         *     "acr": {"values": ["urn:mace:incommon:iap:silver"] }
1392         *    }
1393         * }
1394         * </pre>
1395         *
1396         * @return The corresponding JSON object, empty if no ID token and
1397         *         UserInfo claims are specified.
1398         */
1399        public JSONObject toJSONObject() {
1400                
1401                JSONObject o = new JSONObject();
1402                
1403                if (! getIDTokenClaims().isEmpty()) {
1404                        
1405                        o.put("id_token", Entry.toJSONObject(getIDTokenClaims()));
1406                }
1407                
1408                if (! getVerifiedIDTokenClaims().isEmpty()) {
1409                        
1410                        JSONObject idTokenObject;
1411                        if (o.get("id_token") != null) {
1412                                idTokenObject = (JSONObject) o.get("id_token");
1413                        } else {
1414                                idTokenObject = new JSONObject();
1415                        }
1416                        
1417                        JSONObject verifiedClaims = new JSONObject();
1418                        
1419                        verifiedClaims.put("claims", Entry.toJSONObject(getVerifiedIDTokenClaims()));
1420                        
1421                        if (getIDTokenClaimsVerificationJSONObject() != null) {
1422                                verifiedClaims.put("verification", getIDTokenClaimsVerificationJSONObject());
1423                        }
1424                        
1425                        idTokenObject.put("verified_claims", verifiedClaims);
1426                        o.put("id_token", idTokenObject);
1427                }
1428                
1429                if (! getUserInfoClaims().isEmpty()) {
1430                        
1431                        o.put("userinfo", Entry.toJSONObject(getUserInfoClaims()));
1432                }
1433                
1434                if (! getVerifiedUserInfoClaims().isEmpty()) {
1435                        
1436                        JSONObject userInfoObject;
1437                        if (o.get("userinfo") != null) {
1438                                userInfoObject = (JSONObject) o.get("userinfo");
1439                        } else {
1440                                userInfoObject = new JSONObject();
1441                        }
1442                        
1443                        JSONObject verifiedClaims = new JSONObject();
1444                        
1445                        verifiedClaims.put("claims", Entry.toJSONObject(getVerifiedUserInfoClaims()));
1446                        
1447                        if (getUserInfoClaimsVerificationJSONObject() != null) {
1448                                verifiedClaims.put("verification", getUserInfoClaimsVerificationJSONObject());
1449                        }
1450                        
1451                        userInfoObject.put("verified_claims", verifiedClaims);
1452                        o.put("userinfo", userInfoObject);
1453                }
1454                
1455                return o;
1456        }
1457        
1458        
1459        @Override
1460        public String toJSONString() {
1461                return toJSONObject().toJSONString();
1462        }
1463        
1464        
1465        @Override
1466        public String toString() {
1467                
1468                return toJSONString();
1469        }
1470        
1471        
1472        /**
1473         * Resolves the claims request for the specified response type and
1474         * scope. The scope values that are {@link OIDCScopeValue standard
1475         * OpenID scope values} are resolved to their respective individual
1476         * claims requests, any other scope values are ignored.
1477         *
1478         * @param responseType The response type. Must not be {@code null}.
1479         * @param scope        The scope, {@code null} if not specified (for a
1480         *                     plain OAuth 2.0 authorisation request with no
1481         *                     scope explicitly specified).
1482         *
1483         * @return The claims request.
1484         */
1485        public static ClaimsRequest resolve(final ResponseType responseType, final Scope scope) {
1486                
1487                return resolve(responseType, scope, Collections.<Scope.Value, Set<String>>emptyMap());
1488        }
1489        
1490        
1491        /**
1492         * Resolves the claims request for the specified response type and
1493         * scope. The scope values that are {@link OIDCScopeValue standard
1494         * OpenID scope values} are resolved to their respective individual
1495         * claims requests, any other scope values are checked in the specified
1496         * custom claims map and resolved accordingly.
1497         *
1498         * @param responseType The response type. Must not be {@code null}.
1499         * @param scope        The scope, {@code null} if not specified (for a
1500         *                     plain OAuth 2.0 authorisation request with no
1501         *                     scope explicitly specified).
1502         * @param customClaims Custom scope value to set of claim names map,
1503         *                     {@code null} if not specified.
1504         *
1505         * @return The claims request.
1506         */
1507        public static ClaimsRequest resolve(final ResponseType responseType,
1508                                            final Scope scope,
1509                                            final Map<Scope.Value, Set<String>> customClaims) {
1510                
1511                // Determine the claims target (ID token or UserInfo)
1512                final boolean switchToIDToken =
1513                        responseType.contains(OIDCResponseTypeValue.ID_TOKEN) &&
1514                                !responseType.contains(ResponseType.Value.CODE) &&
1515                                !responseType.contains(ResponseType.Value.TOKEN);
1516                
1517                ClaimsRequest claimsRequest = new ClaimsRequest();
1518                
1519                if (scope == null) {
1520                        // Plain OAuth 2.0 mode
1521                        return claimsRequest;
1522                }
1523                
1524                for (Scope.Value value : scope) {
1525                        
1526                        Set<ClaimsRequest.Entry> entries;
1527                        
1528                        if (value.equals(OIDCScopeValue.PROFILE)) {
1529                                
1530                                entries = OIDCScopeValue.PROFILE.toClaimsRequestEntries();
1531                                
1532                        } else if (value.equals(OIDCScopeValue.EMAIL)) {
1533                                
1534                                entries = OIDCScopeValue.EMAIL.toClaimsRequestEntries();
1535                                
1536                        } else if (value.equals(OIDCScopeValue.PHONE)) {
1537                                
1538                                entries = OIDCScopeValue.PHONE.toClaimsRequestEntries();
1539                                
1540                        } else if (value.equals(OIDCScopeValue.ADDRESS)) {
1541                                
1542                                entries = OIDCScopeValue.ADDRESS.toClaimsRequestEntries();
1543                                
1544                        } else if (customClaims != null && customClaims.containsKey(value)) {
1545                                
1546                                // Process custom scope value -> claim names expansion, e.g.
1547                                // "corp_profile" -> ["employeeNumber", "dept", "ext"]
1548                                Set<String> claimNames = customClaims.get(value);
1549                                
1550                                if (claimNames == null || claimNames.isEmpty()) {
1551                                        continue; // skip
1552                                }
1553                                
1554                                entries = new HashSet<>();
1555                                
1556                                for (String claimName: claimNames) {
1557                                        entries.add(new ClaimsRequest.Entry(claimName, ClaimRequirement.VOLUNTARY));
1558                                }
1559                                
1560                        } else {
1561                                
1562                                continue; // skip
1563                        }
1564                        
1565                        for (ClaimsRequest.Entry en : entries) {
1566                                
1567                                if (switchToIDToken)
1568                                        claimsRequest.addIDTokenClaim(en);
1569                                else
1570                                        claimsRequest.addUserInfoClaim(en);
1571                        }
1572                }
1573                
1574                return claimsRequest;
1575        }
1576        
1577        
1578        /**
1579         * Resolves the merged claims request from the specified OpenID
1580         * authentication request parameters. The scope values that are {@link
1581         * OIDCScopeValue standard OpenID scope values} are resolved to their
1582         * respective individual claims requests, any other scope values are
1583         * ignored.
1584         *
1585         * @param responseType  The response type. Must not be {@code null}.
1586         * @param scope         The scope, {@code null} if not specified (for a
1587         *                      plain OAuth 2.0 authorisation request with no
1588         *                      scope explicitly specified).
1589         * @param claimsRequest The claims request, corresponding to the
1590         *                      optional {@code claims} OpenID Connect
1591         *                      authorisation request parameter, {@code null}
1592         *                      if not specified.
1593         *
1594         * @return The merged claims request.
1595         */
1596        public static ClaimsRequest resolve(final ResponseType responseType,
1597                                            final Scope scope,
1598                                            final ClaimsRequest claimsRequest) {
1599                
1600                return resolve(responseType, scope, claimsRequest, Collections.<Scope.Value, Set<String>>emptyMap());
1601        }
1602        
1603        
1604        /**
1605         * Resolves the merged claims request from the specified OpenID
1606         * authentication request parameters. The scope values that are {@link
1607         * OIDCScopeValue standard OpenID scope values} are resolved to their
1608         * respective individual claims requests, any other scope values are
1609         * checked in the specified custom claims map and resolved accordingly.
1610         *
1611         * @param responseType  The response type. Must not be {@code null}.
1612         * @param scope         The scope, {@code null} if not specified (for a
1613         *                      plain OAuth 2.0 authorisation request with no
1614         *                      scope explicitly specified).
1615         * @param claimsRequest The claims request, corresponding to the
1616         *                      optional {@code claims} OpenID Connect
1617         *                      authorisation request parameter, {@code null}
1618         *                      if not specified.
1619         * @param customClaims  Custom scope value to set of claim names map,
1620         *                      {@code null} if not specified.
1621         *
1622         * @return The merged claims request.
1623         */
1624        public static ClaimsRequest resolve(final ResponseType responseType,
1625                                            final Scope scope,
1626                                            final ClaimsRequest claimsRequest,
1627                                            final Map<Scope.Value, Set<String>> customClaims) {
1628                
1629                ClaimsRequest mergedClaimsRequest = resolve(responseType, scope, customClaims);
1630                
1631                mergedClaimsRequest.add(claimsRequest);
1632                
1633                return mergedClaimsRequest;
1634        }
1635        
1636        
1637        /**
1638         * Resolves the merged claims request for the specified OpenID
1639         * authentication request. The scope values that are {@link
1640         * OIDCScopeValue standard OpenID scope values} are resolved to their
1641         * respective individual claims requests, any other scope values are
1642         * ignored.
1643         *
1644         * @param authRequest The OpenID authentication request. Must not be
1645         *                    {@code null}.
1646         *
1647         * @return The merged claims request.
1648         */
1649        public static ClaimsRequest resolve(final AuthenticationRequest authRequest) {
1650                
1651                return resolve(authRequest.getResponseType(), authRequest.getScope(), authRequest.getClaims());
1652        }
1653        
1654        
1655        private static JSONObject parseFirstVerifiedClaimsObject(final JSONObject containingObject)
1656                throws ParseException {
1657                
1658                if (containingObject.get("verified_claims") instanceof JSONObject) {
1659                        // JSON object is the simple case
1660                        return JSONObjectUtils.getJSONObject(containingObject, "verified_claims");
1661                }
1662                
1663                if (containingObject.get("verified_claims") instanceof JSONArray) {
1664                        // Try JSON array, take first element, ignore rest (use new OIDCClaimsRequest class to handle this case)
1665                        List<JSONObject> elements = JSONArrayUtils.toJSONObjectList(JSONObjectUtils.getJSONArray(containingObject, "verified_claims"));
1666                        if (elements.size() > 0) {
1667                                return elements.get(0);
1668                        }
1669                }
1670                
1671                return null;
1672        }
1673        
1674        
1675        /**
1676         * Parses a claims request from the specified JSON object
1677         * representation. Unexpected members in the JSON object are silently
1678         * ignored.
1679         *
1680         * @param jsonObject The JSON object to parse. Must not be
1681         *                   {@code null}.
1682         *
1683         * @return The claims request.
1684         *
1685         * @throws ParseException If parsing failed.
1686         */
1687        public static ClaimsRequest parse(final JSONObject jsonObject)
1688                throws ParseException {
1689                
1690                ClaimsRequest claimsRequest = new ClaimsRequest();
1691                
1692                try {
1693                        JSONObject idTokenObject = JSONObjectUtils.getJSONObject(jsonObject, "id_token", null);
1694                        
1695                        if (idTokenObject != null) {
1696                                
1697                                for (Entry entry : Entry.parseEntries(idTokenObject)) {
1698                                        if ("verified_claims".equals(entry.getClaimName())) {
1699                                                continue; //skip
1700                                        }
1701                                        claimsRequest.addIDTokenClaim(entry);
1702                                }
1703                                
1704                                JSONObject verifiedClaimsObject = parseFirstVerifiedClaimsObject(idTokenObject);
1705                                
1706                                if (verifiedClaimsObject != null) {
1707                                        // id_token -> verified_claims -> claims
1708                                        JSONObject claimsObject = JSONObjectUtils.getJSONObject(verifiedClaimsObject, "claims", null);
1709                                        if (claimsObject != null) {
1710                                                
1711                                                if (claimsObject.isEmpty()) {
1712                                                        String msg = "Invalid claims object: Empty verification claims object";
1713                                                        throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg));
1714                                                }
1715                                                
1716                                                for (Entry entry : Entry.parseEntries(claimsObject)) {
1717                                                        claimsRequest.addVerifiedIDTokenClaim(entry);
1718                                                }
1719                                        }
1720                                        // id_token -> verified_claims -> verification
1721                                        claimsRequest.setIDTokenClaimsVerificationJSONObject(JSONObjectUtils.getJSONObject(verifiedClaimsObject, "verification", null));
1722                                }
1723                        }
1724                        
1725                        JSONObject userInfoObject = JSONObjectUtils.getJSONObject(jsonObject, "userinfo", null);
1726                        
1727                        if (userInfoObject != null) {
1728                                
1729                                for (Entry entry : Entry.parseEntries(userInfoObject)) {
1730                                        if ("verified_claims".equals(entry.getClaimName())) {
1731                                                continue; //skip
1732                                        }
1733                                        claimsRequest.addUserInfoClaim(entry);
1734                                }
1735                                
1736                                JSONObject verifiedClaimsObject = parseFirstVerifiedClaimsObject(userInfoObject);
1737                                
1738                                if (verifiedClaimsObject != null) {
1739                                        // userinfo -> verified_claims -> claims
1740                                        JSONObject claimsObject = JSONObjectUtils.getJSONObject(verifiedClaimsObject, "claims", null);
1741                                        
1742                                        if (claimsObject != null) {
1743                                                
1744                                                if (claimsObject.isEmpty()) {
1745                                                        String msg = "Invalid claims object: Empty verification claims object";
1746                                                        throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg));
1747                                                }
1748                                                
1749                                                for (Entry entry : Entry.parseEntries(claimsObject)) {
1750                                                        claimsRequest.addVerifiedUserInfoClaim(entry);
1751                                                }
1752                                        }
1753                                        // userinfo -> verified_claims -> verification
1754                                        claimsRequest.setUserInfoClaimsVerificationJSONObject(JSONObjectUtils.getJSONObject(verifiedClaimsObject, "verification", null));
1755                                }
1756                        }
1757                        
1758                } catch (Exception e) {
1759                        
1760                        if (e instanceof ParseException) {
1761                                throw e;
1762                        }
1763                }
1764                
1765                return claimsRequest;
1766        }
1767        
1768        
1769        /**
1770         * Parses a claims request from the specified JSON object string
1771         * representation. Unexpected members in the JSON object are silently
1772         * ignored.
1773         *
1774         * @param json The JSON object string to parse. Must not be
1775         *             {@code null}.
1776         *
1777         * @return The claims request.
1778         *
1779         * @throws ParseException If the string couldn't be parsed to a valid
1780         *                        JSON object.
1781         */
1782        public static ClaimsRequest parse(final String json)
1783                throws ParseException {
1784                
1785                return parse(JSONObjectUtils.parse(json));
1786        }
1787}