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