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