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, 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(final JSONObject entrySpec) {
677                        
678                        Set<String> stdKeys = new HashSet<>(Arrays.asList("essential", "value", "values", "purpose"));
679                        
680                        Map<String, Object> additionalClaimInformation = new HashMap<>();
681                        
682                        for (Map.Entry<String, Object> additionalClaimInformationEntry : entrySpec.entrySet()) {
683                                if (stdKeys.contains(additionalClaimInformationEntry.getKey())) {
684                                        continue; // skip std key
685                                }
686                                additionalClaimInformation.put(additionalClaimInformationEntry.getKey(), additionalClaimInformationEntry.getValue());
687                        }
688                        
689                        return additionalClaimInformation.isEmpty() ? null : additionalClaimInformation;
690                }
691        }
692        
693        
694        /**
695         * The requested ID token claims, keyed by claim name and language tag.
696         */
697        private final Map<Map.Entry<String, LangTag>, Entry> idTokenClaims = new HashMap<>();
698        
699        
700        /**
701         * The requested verified ID token claims, keyed by claim name and
702         * language tag.
703         */
704        private final Map<Map.Entry<String, LangTag>, Entry> verifiedIDTokenClaims = new HashMap<>();
705        
706        
707        /**
708         * The verification element for the requested verified ID token claims.
709         */
710        private JSONObject idTokenClaimsVerification;
711        
712        
713        /**
714         * The requested UserInfo claims, keyed by claim name and language tag.
715         */
716        private final Map<Map.Entry<String, LangTag>, Entry> userInfoClaims = new HashMap<>();
717        
718        
719        /**
720         * The requested verified UserInfo claims, keyed by claim name and
721         * language tag.
722         */
723        private final Map<Map.Entry<String, LangTag>, Entry> verifiedUserInfoClaims = new HashMap<>();
724        
725        
726        /**
727         * The verification element for the requested verified UserInfo claims.
728         */
729        private JSONObject userInfoClaimsVerification;
730        
731        
732        /**
733         * Creates a new empty claims request.
734         */
735        public ClaimsRequest() {
736                
737                // Nothing to initialise
738        }
739        
740        
741        /**
742         * Adds the entries from the specified other claims request.
743         *
744         * @param other The other claims request. If {@code null} no claims
745         *              request entries will be added to this claims request.
746         */
747        public void add(final ClaimsRequest other) {
748                
749                if (other == null)
750                        return;
751                
752                idTokenClaims.putAll(other.idTokenClaims);
753                verifiedIDTokenClaims.putAll(other.verifiedIDTokenClaims);
754                idTokenClaimsVerification = other.idTokenClaimsVerification;
755                
756                userInfoClaims.putAll(other.userInfoClaims);
757                verifiedUserInfoClaims.putAll(other.verifiedUserInfoClaims);
758                userInfoClaimsVerification = other.userInfoClaimsVerification;
759        }
760        
761        
762        /**
763         * Adds the specified ID token claim to the request. It is marked as
764         * voluntary and no language tag and value(s) are associated with it.
765         *
766         * @param claimName The claim name. Must not be {@code null}.
767         */
768        public void addIDTokenClaim(final String claimName) {
769                
770                addIDTokenClaim(claimName, ClaimRequirement.VOLUNTARY);
771        }
772        
773        
774        /**
775         * Adds the specified ID token claim to the request. No language tag
776         * and value(s) are associated with it.
777         *
778         * @param claimName   The claim name. Must not be {@code null}.
779         * @param requirement The claim requirement. Must not be {@code null}.
780         */
781        public void addIDTokenClaim(final String claimName, final ClaimRequirement requirement) {
782                
783                addIDTokenClaim(claimName, requirement, null);
784        }
785        
786        
787        /**
788         * Adds the specified ID token claim to the request. No value(s) are
789         * associated with it.
790         *
791         * @param claimName   The claim name. Must not be {@code null}.
792         * @param requirement The claim requirement. Must not be {@code null}.
793         * @param langTag     The associated language tag, {@code null} if not
794         *                    specified.
795         */
796        public void addIDTokenClaim(final String claimName, final ClaimRequirement requirement,
797                                    final LangTag langTag) {
798                
799                addIDTokenClaim(claimName, requirement, langTag, (String) null);
800        }
801        
802        
803        /**
804         * Adds the specified ID token claim to the request.
805         *
806         * @param claimName   The claim name. Must not be {@code null}.
807         * @param requirement The claim requirement. Must not be {@code null}.
808         * @param langTag     The associated language tag, {@code null} if not
809         *                    specified.
810         * @param value       The expected claim value, {@code null} if not
811         *                    specified.
812         */
813        public void addIDTokenClaim(final String claimName, final ClaimRequirement requirement,
814                                    final LangTag langTag, final String value) {
815                
816                addIDTokenClaim(new Entry(claimName, requirement, langTag, value));
817        }
818        
819        
820        /**
821         * Adds the specified ID token claim to the request.
822         *
823         * @param claimName             The claim name. Must not be
824         *                              {@code null}.
825         * @param requirement           The claim requirement. Must not be
826         *                              {@code null}.
827         * @param langTag               The associated language tag,
828         *                              {@code null} if not specified.
829         * @param value                 The expected claim value, {@code null}
830         *                              if not specified.
831         * @param additionalInformation The additional information for this
832         *                              claim, {@code null} if not specified.
833         */
834        public void addIDTokenClaim(final String claimName, final ClaimRequirement requirement,
835                                    final LangTag langTag, final String value, final Map<String, Object> additionalInformation) {
836                
837                addIDTokenClaim(new Entry(claimName, requirement, langTag, value, null, null, additionalInformation));
838        }
839        
840        
841        /**
842         * Adds the specified ID token claim to the request.
843         *
844         * @param claimName   The claim name. Must not be {@code null}.
845         * @param requirement The claim requirement. Must not be {@code null}.
846         * @param langTag     The associated language tag, {@code null} if not
847         *                    specified.
848         * @param values      The expected claim values, {@code null} if not
849         *                    specified.
850         */
851        public void addIDTokenClaim(final String claimName, final ClaimRequirement requirement,
852                                    final LangTag langTag, final List<String> values) {
853                
854                addIDTokenClaim(new Entry(claimName, requirement, langTag, values));
855        }
856        
857        
858        /**
859         * Adds the specified ID token claim to the request.
860         *
861         * @param claimName             The claim name. Must not be
862         *                              {@code null}.
863         * @param requirement           The claim requirement. Must not be
864         *                              {@code null}.
865         * @param langTag               The associated language tag,
866         *                              {@code null} if not specified.
867         * @param values                The expected claim values, {@code null}
868         *                              if not specified.
869         * @param additionalInformation The additional information for this
870         *                              claim, {@code null} if not specified.
871         */
872        public void addIDTokenClaim(final String claimName, final ClaimRequirement requirement,
873                                    final LangTag langTag, final List<String> values, final Map<String, Object> additionalInformation) {
874                
875                addIDTokenClaim(new Entry(claimName, requirement, langTag, null, values, null, additionalInformation));
876        }
877        
878        
879        private static Map.Entry<String, LangTag> toKey(final Entry entry) {
880                
881                return new AbstractMap.SimpleImmutableEntry<>(
882                        entry.getClaimName(),
883                        entry.getLangTag());
884        }
885        
886        
887        /**
888         * Adds the specified ID token claim to the request.
889         *
890         * @param entry The individual ID token claim request. Must not be
891         *              {@code null}.
892         */
893        public void addIDTokenClaim(final Entry entry) {
894                
895                idTokenClaims.put(toKey(entry), entry);
896        }
897        
898        
899        /**
900         * Adds the specified verified ID token claim to the request.
901         *
902         * @param entry The individual verified ID token claim request. Must
903         *              not be {@code null}.
904         */
905        public void addVerifiedIDTokenClaim(final Entry entry) {
906                
907                verifiedIDTokenClaims.put(toKey(entry), entry);
908        }
909        
910        
911        /**
912         * Sets the {@code verification} element for the requested verified ID
913         * token claims.
914         *
915         * @param jsonObject The {@code verification} JSON object, {@code null}
916         *                   if not specified.
917         */
918        public void setIDTokenClaimsVerificationJSONObject(final JSONObject jsonObject) {
919                
920                this.idTokenClaimsVerification = jsonObject;
921        }
922        
923        
924        /**
925         * Gets the {@code verification} element for the requested verified ID
926         * token claims.
927         *
928         * @return The {@code verification} JSON object, {@code null} if not
929         *         specified.
930         */
931        public JSONObject getIDTokenClaimsVerificationJSONObject() {
932                
933                return idTokenClaimsVerification;
934        }
935        
936        
937        /**
938         * Gets the requested ID token claims.
939         *
940         * @return The ID token claims, as an unmodifiable collection, empty
941         *         set if none.
942         */
943        public Collection<Entry> getIDTokenClaims() {
944                
945                return Collections.unmodifiableCollection(idTokenClaims.values());
946        }
947        
948        
949        /**
950         * Gets the requested verified ID token claims.
951         *
952         * @return The verified ID token claims, as an unmodifiable collection,
953         *         empty set if none.
954         */
955        public Collection<Entry> getVerifiedIDTokenClaims() {
956                
957                return Collections.unmodifiableCollection(verifiedIDTokenClaims.values());
958        }
959        
960        
961        private static Set<String> getClaimNames(final Map<Map.Entry<String, LangTag>, Entry> claims, final boolean withLangTag) {
962                
963                Set<String> names = new HashSet<>();
964                
965                for (Entry en : claims.values())
966                        names.add(en.getClaimName(withLangTag));
967                
968                return Collections.unmodifiableSet(names);
969        }
970        
971        
972        /**
973         * Gets the names of the requested ID token claim names.
974         *
975         * @param withLangTag If {@code true} the language tags, if any, will
976         *                    be appended to the names, else not.
977         *
978         * @return The ID token claim names, as an unmodifiable set, empty set
979         *         if none.
980         */
981        public Set<String> getIDTokenClaimNames(final boolean withLangTag) {
982                
983                return getClaimNames(idTokenClaims, withLangTag);
984        }
985        
986        
987        /**
988         * Gets the names of the requested verified ID token claim names.
989         *
990         * @param withLangTag If {@code true} the language tags, if any, will
991         *                    be appended to the names, else not.
992         *
993         * @return The ID token claim names, as an unmodifiable set, empty set
994         *         if none.
995         */
996        public Set<String> getVerifiedIDTokenClaimNames(final boolean withLangTag) {
997                
998                return getClaimNames(verifiedIDTokenClaims, withLangTag);
999        }
1000        
1001        
1002        private static Map.Entry<String, LangTag> toKey(final String claimName, final LangTag langTag) {
1003                
1004                return new AbstractMap.SimpleImmutableEntry<>(claimName, langTag);
1005        }
1006        
1007        
1008        /**
1009         * Removes the specified ID token claim from the request.
1010         *
1011         * @param claimName The claim name. Must not be {@code null}.
1012         * @param langTag   The associated language tag, {@code null} if none.
1013         *
1014         * @return The removed ID token claim, {@code null} if not found.
1015         */
1016        public Entry removeIDTokenClaim(final String claimName, final LangTag langTag) {
1017                
1018                return idTokenClaims.remove(toKey(claimName, langTag));
1019        }
1020        
1021        
1022        /**
1023         * Removes the specified verified ID token claim from the request.
1024         *
1025         * @param claimName The claim name. Must not be {@code null}.
1026         * @param langTag   The associated language tag, {@code null} if none.
1027         *
1028         * @return The removed ID token claim, {@code null} if not found.
1029         */
1030        public Entry removeVerifiedIDTokenClaim(final String claimName, final LangTag langTag) {
1031                
1032                return verifiedIDTokenClaims.remove(toKey(claimName, langTag));
1033        }
1034        
1035        
1036        private static Collection<Entry> removeClaims(final Map<Map.Entry<String, LangTag>, Entry> claims, final String claimName) {
1037                
1038                Collection<Entry> removedClaims = new LinkedList<>();
1039                
1040                Iterator<Map.Entry<Map.Entry<String, LangTag>, Entry>> it = claims.entrySet().iterator();
1041                
1042                while (it.hasNext()) {
1043                        
1044                        Map.Entry<Map.Entry<String, LangTag>, Entry> reqEntry = it.next();
1045                        
1046                        if (reqEntry.getKey().getKey().equals(claimName)) {
1047                                
1048                                removedClaims.add(reqEntry.getValue());
1049                                
1050                                it.remove();
1051                        }
1052                }
1053                
1054                return Collections.unmodifiableCollection(removedClaims);
1055        }
1056        
1057        
1058        /**
1059         * Removes the specified ID token claims from the request, in all
1060         * existing language tag variations.
1061         *
1062         * @param claimName The claim name. Must not be {@code null}.
1063         *
1064         * @return The removed ID token claims, as an unmodifiable collection,
1065         *         empty set if none were found.
1066         */
1067        public Collection<Entry> removeIDTokenClaims(final String claimName) {
1068                
1069                return removeClaims(idTokenClaims, claimName);
1070        }
1071        
1072        
1073        /**
1074         * Removes the specified verified ID token claims from the request, in
1075         * all existing language tag variations.
1076         *
1077         * @param claimName The claim name. Must not be {@code null}.
1078         *
1079         * @return The removed ID token claims, as an unmodifiable collection,
1080         *         empty set if none were found.
1081         */
1082        public Collection<Entry> removeVerifiedIDTokenClaims(final String claimName) {
1083                
1084                return removeClaims(verifiedIDTokenClaims, claimName);
1085        }
1086        
1087        
1088        /**
1089         * Adds the specified UserInfo claim to the request. It is marked as
1090         * voluntary and no language tag and value(s) are associated with it.
1091         *
1092         * @param claimName The claim name. Must not be {@code null}.
1093         */
1094        public void addUserInfoClaim(final String claimName) {
1095                
1096                addUserInfoClaim(claimName, ClaimRequirement.VOLUNTARY);
1097        }
1098        
1099        
1100        /**
1101         * Adds the specified UserInfo claim to the request. No language tag and
1102         * value(s) are associated with it.
1103         *
1104         * @param claimName   The claim name. Must not be {@code null}.
1105         * @param requirement The claim requirement. Must not be {@code null}.
1106         */
1107        public void addUserInfoClaim(final String claimName, final ClaimRequirement requirement) {
1108                
1109                addUserInfoClaim(claimName, requirement, null);
1110        }
1111        
1112        
1113        /**
1114         * Adds the specified UserInfo claim to the request. No value(s) are
1115         * associated with it.
1116         *
1117         * @param claimName   The claim name. Must not be {@code null}.
1118         * @param requirement The claim requirement. Must not be {@code null}.
1119         * @param langTag     The associated language tag, {@code null} if not
1120         *                    specified.
1121         */
1122        public void addUserInfoClaim(final String claimName, final ClaimRequirement requirement,
1123                                     final LangTag langTag) {
1124                
1125                
1126                addUserInfoClaim(claimName, requirement, langTag, (String) null);
1127        }
1128        
1129        
1130        /**
1131         * Adds the specified UserInfo claim to the request.
1132         *
1133         * @param claimName   The claim name. Must not be {@code null}.
1134         * @param requirement The claim requirement. Must not be {@code null}.
1135         * @param langTag     The associated language tag, {@code null} if not
1136         *                    specified.
1137         * @param value       The expected claim value, {@code null} if not
1138         *                    specified.
1139         */
1140        public void addUserInfoClaim(final String claimName, final ClaimRequirement requirement,
1141                                     final LangTag langTag, final String value) {
1142                
1143                addUserInfoClaim(new Entry(claimName, requirement, langTag, value));
1144        }
1145        
1146        
1147        /**
1148         * Adds the specified UserInfo claim to the request.
1149         *
1150         * @param claimName             The claim name. Must not be {@code
1151         *                              null}.
1152         * @param requirement           The claim requirement. Must not be
1153         *                              {@code null}.
1154         * @param langTag               The associated language tag, {@code
1155         *                              null} if not specified.
1156         * @param value                 The expected claim value, {@code null}
1157         *                              if not specified.
1158         * @param additionalInformation The additional information for this
1159         *                              claim, {@code null} if not specified.
1160         */
1161        public void addUserInfoClaim(final String claimName, final ClaimRequirement requirement,
1162                                     final LangTag langTag, final String value, final Map<String, Object> additionalInformation) {
1163                
1164                addUserInfoClaim(new Entry(claimName, requirement, langTag, value, null, null, additionalInformation));
1165        }
1166        
1167        
1168        /**
1169         * Adds the specified UserInfo claim to the request.
1170         *
1171         * @param claimName   The claim name. Must not be {@code null}.
1172         * @param requirement The claim requirement. Must not be {@code null}.
1173         * @param langTag     The associated language tag, {@code null} if not
1174         *                    specified.
1175         * @param values      The expected claim values, {@code null} if not
1176         *                    specified.
1177         */
1178        public void addUserInfoClaim(final String claimName, final ClaimRequirement requirement,
1179                                     final LangTag langTag, final List<String> values) {
1180                
1181                addUserInfoClaim(new Entry(claimName, requirement, langTag, values));
1182        }
1183        
1184        
1185        /**
1186         * Adds the specified UserInfo claim to the request.
1187         *
1188         * @param claimName             The claim name. Must not be
1189         *                              {@code null}.
1190         * @param requirement           The claim requirement. Must not be
1191         *                              {@code null}.
1192         * @param langTag               The associated language tag,
1193         *                              {@code null} if not specified.
1194         * @param values                The expected claim values, {@code null}
1195         *                              if not specified.
1196         * @param additionalInformation The additional information for this
1197         *                              claim, {@code null} if not specified.
1198         */
1199        public void addUserInfoClaim(final String claimName, final ClaimRequirement requirement,
1200                                     final LangTag langTag, final List<String> values, final Map<String, Object> additionalInformation) {
1201                
1202                addUserInfoClaim(new Entry(claimName, requirement, langTag, null, values, null, additionalInformation));
1203        }
1204        
1205        
1206        /**
1207         * Adds the specified UserInfo claim to the request.
1208         *
1209         * @param entry The individual UserInfo claim request. Must not be
1210         *              {@code null}.
1211         */
1212        public void addUserInfoClaim(final Entry entry) {
1213                
1214                userInfoClaims.put(toKey(entry), entry);
1215        }
1216        
1217        
1218        /**
1219         * Adds the specified verified UserInfo claim to the request.
1220         *
1221         * @param entry The individual verified UserInfo claim request. Must
1222         *              not be {@code null}.
1223         */
1224        public void addVerifiedUserInfoClaim(final Entry entry) {
1225                
1226                verifiedUserInfoClaims.put(toKey(entry), entry);
1227        }
1228        
1229        
1230        /**
1231         * Sets the {@code verification} element for the requested verified
1232         * UserInfo claims.
1233         *
1234         * @param jsonObject The {@code verification} JSON object, {@code null}
1235         *                   if not specified.
1236         */
1237        public void setUserInfoClaimsVerificationJSONObject(final JSONObject jsonObject) {
1238                
1239                this.userInfoClaimsVerification = jsonObject;
1240        }
1241        
1242        
1243        /**
1244         * Gets the {@code verification} element for the requested verified
1245         * UserInfo claims.
1246         *
1247         * @return The {@code verification} JSON object, {@code null} if not
1248         *         specified.
1249         */
1250        public JSONObject getUserInfoClaimsVerificationJSONObject() {
1251                
1252                return userInfoClaimsVerification;
1253        }
1254        
1255        
1256        /**
1257         * Gets the requested UserInfo claims.
1258         *
1259         * @return The UserInfo claims, as an unmodifiable collection, empty
1260         *         set if none.
1261         */
1262        public Collection<Entry> getUserInfoClaims() {
1263                
1264                return Collections.unmodifiableCollection(userInfoClaims.values());
1265        }
1266        
1267        
1268        /**
1269         * Gets the requested verified UserInfo claims.
1270         *
1271         * @return The UserInfo claims, as an unmodifiable collection, empty
1272         *         set if none.
1273         */
1274        public Collection<Entry> getVerifiedUserInfoClaims() {
1275                
1276                return Collections.unmodifiableCollection(verifiedUserInfoClaims.values());
1277        }
1278        
1279        
1280        /**
1281         * Gets the names of the requested UserInfo claim names.
1282         *
1283         * @param withLangTag If {@code true} the language tags, if any, will
1284         *                    be appended to the names, else not.
1285         *
1286         * @return The UserInfo claim names, as an unmodifiable set, empty set
1287         *         if none.
1288         */
1289        public Set<String> getUserInfoClaimNames(final boolean withLangTag) {
1290                
1291                return getClaimNames(userInfoClaims, withLangTag);
1292        }
1293        
1294        
1295        /**
1296         * Gets the names of the requested verified UserInfo claim names.
1297         *
1298         * @param withLangTag If {@code true} the language tags, if any, will
1299         *                    be appended to the names, else not.
1300         *
1301         * @return The UserInfo claim names, as an unmodifiable set, empty set
1302         *         if none.
1303         */
1304        public Set<String> getVerifiedUserInfoClaimNames(final boolean withLangTag) {
1305                
1306                return getClaimNames(verifiedUserInfoClaims, withLangTag);
1307        }
1308        
1309        
1310        /**
1311         * Removes the specified UserInfo claim from the request.
1312         *
1313         * @param claimName The claim name. Must not be {@code null}.
1314         * @param langTag   The associated language tag, {@code null} if none.
1315         *
1316         * @return The removed UserInfo claim, {@code null} if not found.
1317         */
1318        public Entry removeUserInfoClaim(final String claimName, final LangTag langTag) {
1319                
1320                return userInfoClaims.remove(toKey(claimName, langTag));
1321        }
1322        
1323        
1324        /**
1325         * Removes the specified verified UserInfo claim from the request.
1326         *
1327         * @param claimName The claim name. Must not be {@code null}.
1328         * @param langTag   The associated language tag, {@code null} if none.
1329         *
1330         * @return The removed UserInfo claim, {@code null} if not found.
1331         */
1332        public Entry removeVerifiedUserInfoClaim(final String claimName, final LangTag langTag) {
1333                
1334                return verifiedUserInfoClaims.remove(toKey(claimName, langTag));
1335        }
1336        
1337        
1338        /**
1339         * Removes the specified UserInfo claims from the request, in all
1340         * existing language tag variations.
1341         *
1342         * @param claimName The claim name. Must not be {@code null}.
1343         *
1344         * @return The removed UserInfo claims, as an unmodifiable collection,
1345         *         empty set if none were found.
1346         */
1347        public Collection<Entry> removeUserInfoClaims(final String claimName) {
1348                
1349                return removeClaims(userInfoClaims, claimName);
1350        }
1351        
1352        
1353        /**
1354         * Removes the specified verified UserInfo claims from the request, in
1355         * all existing language tag variations.
1356         *
1357         * @param claimName The claim name. Must not be {@code null}.
1358         *
1359         * @return The removed UserInfo claims, as an unmodifiable collection,
1360         *         empty set if none were found.
1361         */
1362        public Collection<Entry> removeVerifiedUserInfoClaims(final String claimName) {
1363                
1364                return removeClaims(verifiedUserInfoClaims, claimName);
1365        }
1366        
1367        
1368        /**
1369         * Returns the JSON object representation of this claims request.
1370         *
1371         * <p>Example:
1372         *
1373         * <pre>
1374         * {
1375         *   "userinfo":
1376         *    {
1377         *     "given_name": {"essential": true},
1378         *     "nickname": null,
1379         *     "email": {"essential": true},
1380         *     "email_verified": {"essential": true},
1381         *     "picture": null,
1382         *     "http://example.info/claims/groups": null
1383         *    },
1384         *   "id_token":
1385         *    {
1386         *     "auth_time": {"essential": true},
1387         *     "acr": {"values": ["urn:mace:incommon:iap:silver"] }
1388         *    }
1389         * }
1390         * </pre>
1391         *
1392         * @return The corresponding JSON object, empty if no ID token and
1393         *         UserInfo claims are specified.
1394         */
1395        public JSONObject toJSONObject() {
1396                
1397                JSONObject o = new JSONObject();
1398                
1399                if (! getIDTokenClaims().isEmpty()) {
1400                        
1401                        o.put("id_token", Entry.toJSONObject(getIDTokenClaims()));
1402                }
1403                
1404                if (! getVerifiedIDTokenClaims().isEmpty()) {
1405                        
1406                        JSONObject idTokenObject;
1407                        if (o.get("id_token") != null) {
1408                                idTokenObject = (JSONObject) o.get("id_token");
1409                        } else {
1410                                idTokenObject = new JSONObject();
1411                        }
1412                        
1413                        JSONObject verifiedClaims = new JSONObject();
1414                        
1415                        verifiedClaims.put("claims", Entry.toJSONObject(getVerifiedIDTokenClaims()));
1416                        
1417                        if (getIDTokenClaimsVerificationJSONObject() != null) {
1418                                verifiedClaims.put("verification", getIDTokenClaimsVerificationJSONObject());
1419                        }
1420                        
1421                        idTokenObject.put("verified_claims", verifiedClaims);
1422                        o.put("id_token", idTokenObject);
1423                }
1424                
1425                if (! getUserInfoClaims().isEmpty()) {
1426                        
1427                        o.put("userinfo", Entry.toJSONObject(getUserInfoClaims()));
1428                }
1429                
1430                if (! getVerifiedUserInfoClaims().isEmpty()) {
1431                        
1432                        JSONObject userInfoObject;
1433                        if (o.get("userinfo") != null) {
1434                                userInfoObject = (JSONObject) o.get("userinfo");
1435                        } else {
1436                                userInfoObject = new JSONObject();
1437                        }
1438                        
1439                        JSONObject verifiedClaims = new JSONObject();
1440                        
1441                        verifiedClaims.put("claims", Entry.toJSONObject(getVerifiedUserInfoClaims()));
1442                        
1443                        if (getUserInfoClaimsVerificationJSONObject() != null) {
1444                                verifiedClaims.put("verification", getUserInfoClaimsVerificationJSONObject());
1445                        }
1446                        
1447                        userInfoObject.put("verified_claims", verifiedClaims);
1448                        o.put("userinfo", userInfoObject);
1449                }
1450                
1451                return o;
1452        }
1453        
1454        
1455        @Override
1456        public String toJSONString() {
1457                return toJSONObject().toJSONString();
1458        }
1459        
1460        
1461        @Override
1462        public String toString() {
1463                
1464                return toJSONString();
1465        }
1466        
1467        
1468        /**
1469         * Resolves the claims request for the specified response type and
1470         * scope. The scope values that are {@link OIDCScopeValue standard
1471         * OpenID scope values} are resolved to their respective individual
1472         * claims requests, any other scope values are ignored.
1473         *
1474         * @param responseType The response type. Must not be {@code null}.
1475         * @param scope        The scope, {@code null} if not specified (for a
1476         *                     plain OAuth 2.0 authorisation request with no
1477         *                     scope explicitly specified).
1478         *
1479         * @return The claims request.
1480         */
1481        public static ClaimsRequest resolve(final ResponseType responseType, final Scope scope) {
1482                
1483                return resolve(responseType, scope, Collections.<Scope.Value, Set<String>>emptyMap());
1484        }
1485        
1486        
1487        /**
1488         * Resolves the claims request for the specified response type and
1489         * scope. The scope values that are {@link OIDCScopeValue standard
1490         * OpenID scope values} are resolved to their respective individual
1491         * claims requests, any other scope values are checked in the specified
1492         * custom claims map and resolved accordingly.
1493         *
1494         * @param responseType The response type. Must not be {@code null}.
1495         * @param scope        The scope, {@code null} if not specified (for a
1496         *                     plain OAuth 2.0 authorisation request with no
1497         *                     scope explicitly specified).
1498         * @param customClaims Custom scope value to set of claim names map,
1499         *                     {@code null} if not specified.
1500         *
1501         * @return The claims request.
1502         */
1503        public static ClaimsRequest resolve(final ResponseType responseType,
1504                                            final Scope scope,
1505                                            final Map<Scope.Value, Set<String>> customClaims) {
1506                
1507                // Determine the claims target (ID token or UserInfo)
1508                final boolean switchToIDToken =
1509                        responseType.contains(OIDCResponseTypeValue.ID_TOKEN) &&
1510                                !responseType.contains(ResponseType.Value.CODE) &&
1511                                !responseType.contains(ResponseType.Value.TOKEN);
1512                
1513                ClaimsRequest claimsRequest = new ClaimsRequest();
1514                
1515                if (scope == null) {
1516                        // Plain OAuth 2.0 mode
1517                        return claimsRequest;
1518                }
1519                
1520                for (Scope.Value value : scope) {
1521                        
1522                        Set<ClaimsRequest.Entry> entries;
1523                        
1524                        if (value.equals(OIDCScopeValue.PROFILE)) {
1525                                
1526                                entries = OIDCScopeValue.PROFILE.toClaimsRequestEntries();
1527                                
1528                        } else if (value.equals(OIDCScopeValue.EMAIL)) {
1529                                
1530                                entries = OIDCScopeValue.EMAIL.toClaimsRequestEntries();
1531                                
1532                        } else if (value.equals(OIDCScopeValue.PHONE)) {
1533                                
1534                                entries = OIDCScopeValue.PHONE.toClaimsRequestEntries();
1535                                
1536                        } else if (value.equals(OIDCScopeValue.ADDRESS)) {
1537                                
1538                                entries = OIDCScopeValue.ADDRESS.toClaimsRequestEntries();
1539                                
1540                        } else if (customClaims != null && customClaims.containsKey(value)) {
1541                                
1542                                // Process custom scope value -> claim names expansion, e.g.
1543                                // "corp_profile" -> ["employeeNumber", "dept", "ext"]
1544                                Set<String> claimNames = customClaims.get(value);
1545                                
1546                                if (claimNames == null || claimNames.isEmpty()) {
1547                                        continue; // skip
1548                                }
1549                                
1550                                entries = new HashSet<>();
1551                                
1552                                for (String claimName: claimNames) {
1553                                        entries.add(new ClaimsRequest.Entry(claimName, ClaimRequirement.VOLUNTARY));
1554                                }
1555                                
1556                        } else {
1557                                
1558                                continue; // skip
1559                        }
1560                        
1561                        for (ClaimsRequest.Entry en : entries) {
1562                                
1563                                if (switchToIDToken)
1564                                        claimsRequest.addIDTokenClaim(en);
1565                                else
1566                                        claimsRequest.addUserInfoClaim(en);
1567                        }
1568                }
1569                
1570                return claimsRequest;
1571        }
1572        
1573        
1574        /**
1575         * Resolves the merged claims request from the specified OpenID
1576         * authentication request parameters. The scope values that are {@link
1577         * OIDCScopeValue standard OpenID scope values} are resolved to their
1578         * respective individual claims requests, any other scope values are
1579         * ignored.
1580         *
1581         * @param responseType  The response type. Must not be {@code null}.
1582         * @param scope         The scope, {@code null} if not specified (for a
1583         *                      plain OAuth 2.0 authorisation request with no
1584         *                      scope explicitly specified).
1585         * @param claimsRequest The claims request, corresponding to the
1586         *                      optional {@code claims} OpenID Connect
1587         *                      authorisation request parameter, {@code null}
1588         *                      if not specified.
1589         *
1590         * @return The merged claims request.
1591         */
1592        public static ClaimsRequest resolve(final ResponseType responseType,
1593                                            final Scope scope,
1594                                            final ClaimsRequest claimsRequest) {
1595                
1596                return resolve(responseType, scope, claimsRequest, Collections.<Scope.Value, Set<String>>emptyMap());
1597        }
1598        
1599        
1600        /**
1601         * Resolves the merged claims request from the specified OpenID
1602         * authentication request parameters. The scope values that are {@link
1603         * OIDCScopeValue standard OpenID scope values} are resolved to their
1604         * respective individual claims requests, any other scope values are
1605         * checked in the specified custom claims map and resolved accordingly.
1606         *
1607         * @param responseType  The response type. Must not be {@code null}.
1608         * @param scope         The scope, {@code null} if not specified (for a
1609         *                      plain OAuth 2.0 authorisation request with no
1610         *                      scope explicitly specified).
1611         * @param claimsRequest The claims request, corresponding to the
1612         *                      optional {@code claims} OpenID Connect
1613         *                      authorisation request parameter, {@code null}
1614         *                      if not specified.
1615         * @param customClaims  Custom scope value to set of claim names map,
1616         *                      {@code null} if not specified.
1617         *
1618         * @return The merged claims request.
1619         */
1620        public static ClaimsRequest resolve(final ResponseType responseType,
1621                                            final Scope scope,
1622                                            final ClaimsRequest claimsRequest,
1623                                            final Map<Scope.Value, Set<String>> customClaims) {
1624                
1625                ClaimsRequest mergedClaimsRequest = resolve(responseType, scope, customClaims);
1626                
1627                mergedClaimsRequest.add(claimsRequest);
1628                
1629                return mergedClaimsRequest;
1630        }
1631        
1632        
1633        /**
1634         * Resolves the merged claims request for the specified OpenID
1635         * authentication request. The scope values that are {@link
1636         * OIDCScopeValue standard OpenID scope values} are resolved to their
1637         * respective individual claims requests, any other scope values are
1638         * ignored.
1639         *
1640         * @param authRequest The OpenID authentication request. Must not be
1641         *                    {@code null}.
1642         *
1643         * @return The merged claims request.
1644         */
1645        public static ClaimsRequest resolve(final AuthenticationRequest authRequest) {
1646                
1647                return resolve(authRequest.getResponseType(), authRequest.getScope(), authRequest.getClaims());
1648        }
1649        
1650        
1651        /**
1652         * Parses a claims request from the specified JSON object
1653         * representation. Unexpected members in the JSON object are silently
1654         * ignored.
1655         *
1656         * @param jsonObject The JSON object to parse. Must not be
1657         *                   {@code null}.
1658         *
1659         * @return The claims request.
1660         */
1661        public static ClaimsRequest parse(final JSONObject jsonObject) {
1662                
1663                ClaimsRequest claimsRequest = new ClaimsRequest();
1664                
1665                try {
1666                        JSONObject idTokenObject = JSONObjectUtils.getJSONObject(jsonObject, "id_token", null);
1667                        
1668                        if (idTokenObject != null) {
1669                                
1670                                for (Entry entry : Entry.parseEntries(idTokenObject)) {
1671                                        if ("verified_claims".equals(entry.getClaimName())) {
1672                                                continue; //skip
1673                                        }
1674                                        claimsRequest.addIDTokenClaim(entry);
1675                                }
1676                                
1677                                JSONObject verifiedClaimsObject = JSONObjectUtils.getJSONObject(idTokenObject, "verified_claims", null);
1678                                
1679                                if (verifiedClaimsObject != null) {
1680                                        // id_token -> verified_claims -> claims
1681                                        JSONObject claimsObject = JSONObjectUtils.getJSONObject(verifiedClaimsObject, "claims", null);
1682                                        if (claimsObject != null) {
1683                                                for (Entry entry : Entry.parseEntries(claimsObject)) {
1684                                                        claimsRequest.addVerifiedIDTokenClaim(entry);
1685                                                }
1686                                        }
1687                                        // id_token -> verified_claims -> verification
1688                                        claimsRequest.setIDTokenClaimsVerificationJSONObject(JSONObjectUtils.getJSONObject(verifiedClaimsObject, "verification", null));
1689                                }
1690                        }
1691                        
1692                        JSONObject userInfoObject = JSONObjectUtils.getJSONObject(jsonObject, "userinfo", null);
1693                        
1694                        if (userInfoObject != null) {
1695                                
1696                                for (Entry entry : Entry.parseEntries(userInfoObject)) {
1697                                        if ("verified_claims".equals(entry.getClaimName())) {
1698                                                continue; //skip
1699                                        }
1700                                        claimsRequest.addUserInfoClaim(entry);
1701                                }
1702                                
1703                                JSONObject verifiedClaimsObject = JSONObjectUtils.getJSONObject(userInfoObject, "verified_claims", null);
1704                                
1705                                if (verifiedClaimsObject != null) {
1706                                        // userinfo -> verified_claims -> claims
1707                                        JSONObject claimsObject = JSONObjectUtils.getJSONObject(verifiedClaimsObject, "claims", null);
1708                                        if (claimsObject != null) {
1709                                                for (Entry entry : Entry.parseEntries(claimsObject)) {
1710                                                        claimsRequest.addVerifiedUserInfoClaim(entry);
1711                                                }
1712                                        }
1713                                        // userinfo -> verified_claims -> verification
1714                                        claimsRequest.setUserInfoClaimsVerificationJSONObject(JSONObjectUtils.getJSONObject(verifiedClaimsObject, "verification", null));
1715                                }
1716                        }
1717                        
1718                } catch (Exception e) {
1719                        
1720                        // Ignore
1721                }
1722                
1723                return claimsRequest;
1724        }
1725        
1726        
1727        /**
1728         * Parses a claims request from the specified JSON object string
1729         * representation. Unexpected members in the JSON object are silently
1730         * ignored.
1731         *
1732         * @param json The JSON object string to parse. Must not be
1733         *             {@code null}.
1734         *
1735         * @return The claims request.
1736         *
1737         * @throws ParseException If the string couldn't be parsed to a valid
1738         *                        JSON object.
1739         */
1740        public static ClaimsRequest parse(final String json)
1741                throws ParseException {
1742                
1743                return parse(JSONObjectUtils.parse(json));
1744        }
1745}