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