001package com.nimbusds.openid.connect.sdk;
002
003
004import java.util.Collection;
005import java.util.Collections;
006import java.util.Iterator;
007import java.util.HashMap;
008import java.util.HashSet;
009import java.util.LinkedList;
010import java.util.List;
011import java.util.Map;
012import java.util.Set;
013
014import net.jcip.annotations.Immutable;
015
016import org.apache.commons.lang3.tuple.ImmutablePair;
017
018import net.minidev.json.JSONObject;
019
020import com.nimbusds.langtag.LangTag;
021import com.nimbusds.langtag.LangTagException;
022
023import com.nimbusds.oauth2.sdk.ResponseType;
024import com.nimbusds.oauth2.sdk.Scope;
025
026import com.nimbusds.openid.connect.sdk.claims.ClaimRequirement;
027
028
029/**
030 * Specifies the individual claims to return from the UserInfo endpoint and / 
031 * or in the ID Token.
032 *
033 * <p>Related specifications: 
034 * 
035 * <ul>
036 *     <li>OpenID Connect Core 1.0, section 5.5.
037 * </ul>
038 */
039public class ClaimsRequest {
040
041
042        /**
043         * Individual claim request.
044         *
045         * <p>Related specifications: 
046         * 
047         * <ul>
048         *     <li>OpenID Connect Core 1.0, section 5.5.1.
049         * </ul>
050         */
051        @Immutable
052        public static class Entry {
053
054
055                /**
056                 * The claim name.
057                 */
058                private final String claimName;
059
060
061                /**
062                 * The claim requirement.
063                 */
064                private final ClaimRequirement requirement;
065
066
067                /**
068                 * Optional language tag.
069                 */
070                private final LangTag langTag;
071
072
073                /**
074                 * Optional claim value.
075                 */
076                private final String value;
077
078
079                /**
080                 * Optional claim values.
081                 */
082                private final List<String> values;
083
084
085                /**
086                 * Creates a new individual claim request. The claim 
087                 * requirement is set to voluntary (the default) and no 
088                 * expected value(s) are specified.
089                 *
090                 * @param claimName   The claim name. Must not be {@code null}.
091                 * @param langTag     Optional language tag for the claim.
092                 */
093                public Entry(final String claimName, final LangTag langTag) {
094
095                        this(claimName, ClaimRequirement.VOLUNTARY, langTag, null, null);
096                }
097                
098                
099                /**
100                 * Creates a new individual claim request.
101                 *
102                 * @param claimName   The claim name. Must not be {@code null}.
103                 * @param requirement The claim requirement. Must not be 
104                 *                    {@code null}.
105                 */
106                public Entry(final String claimName, final ClaimRequirement requirement) {
107
108                        this(claimName, requirement, null, null, null);
109                }
110
111
112                /**
113                 * Creates a new individual claim request.
114                 *
115                 * @param claimName   The claim name. Must not be {@code null}.
116                 * @param requirement The claim requirement. Must not be 
117                 *                    {@code null}.
118                 * @param langTag     Optional language tag for the claim.
119                 * @param value       Optional expected value for the claim.
120                 */
121                public Entry(final String claimName, final ClaimRequirement requirement, 
122                             final LangTag langTag, final String value) {
123
124                        this(claimName, requirement, langTag, value, null);
125                }
126
127
128                /**
129                 * Creates a new individual claim request.
130                 *
131                 * @param claimName   The claim name. Must not be {@code null}.
132                 * @param requirement The claim requirement. Must not be 
133                 *                    {@code null}.
134                 * @param langTag     Optional language tag for the claim.
135                 * @param values      Optional expected values for the claim.
136                 */
137                public Entry(final String claimName, final ClaimRequirement requirement, 
138                             final LangTag langTag, final List<String> values) {
139
140                        this(claimName, requirement, langTag, null, values);
141                }
142
143
144                /**
145                 * Creates a new individual claim request. This constructor is
146                 * to be used privately. Ensures that {@code value} and 
147                 * {@code values} are not simultaneously specified.
148                 *
149                 * @param claimName   The claim name. Must not be {@code null}.
150                 * @param requirement The claim requirement. Must not be 
151                 *                    {@code null}.
152                 * @param langTag     Optional language tag for the claim.
153                 * @param value       Optional expected value for the claim. If
154                 *                    set, then the {@code values} parameter
155                 *                    must not be set.
156                 * @param values      Optional expected values for the claim. 
157                 *                    If set, then the {@code value} parameter
158                 *                    must not be set.
159                 */
160                private Entry(final String claimName, final ClaimRequirement requirement, final LangTag langTag, 
161                              final String value, final List<String> values) {
162
163                        if (claimName == null)
164                                throw new IllegalArgumentException("The claim name must not be null");
165
166                        this.claimName = claimName;
167
168
169                        if (requirement == null)
170                                throw new IllegalArgumentException("The claim requirement must not be null");
171
172                        this.requirement = requirement;
173
174
175                        this.langTag = langTag;
176
177
178                        if (value != null && values == null) {
179
180                                this.value = value;
181                                this.values = null;
182
183                        } else if (value == null && values != null) {
184
185                                this.value = null;
186                                this.values = values;
187
188                        } else if (value == null && values == null) {
189
190                                this.value = null;
191                                this.values = null;
192
193                        } else {
194
195                                throw new IllegalArgumentException("Either value or values must be specified, but not both");
196                        }
197                }
198
199
200                /**
201                 * Gets the claim name.
202                 *
203                 * @return The claim name.
204                 */
205                public String getClaimName() {
206
207                        return claimName;
208                }
209                
210                
211                /**
212                 * Gets the claim name, optionally with the language tag
213                 * appended.
214                 * 
215                 * <p>Example with language tag:
216                 * 
217                 * <pre>
218                 * name#de-DE
219                 * </pre>
220                 * 
221                 * @param withLangTag If {@code true} the language tag will be
222                 *                    appended to the name (if any), else not.
223                 * 
224                 * @return The claim name, with optionally appended language
225                 *         tag.
226                 */
227                public String getClaimName(final boolean withLangTag) {
228                        
229                        if (withLangTag && langTag != null)
230                                return claimName + "#" + langTag.toString();
231                        else
232                                return claimName;
233                }
234
235
236                /**
237                 * Gets the claim requirement.
238                 *
239                 * @return The claim requirement.
240                 */
241                public ClaimRequirement getClaimRequirement() {
242
243                        return requirement;
244                }
245
246
247                /**
248                 * Gets the optional language tag for the claim.
249                 *
250                 * @return The language tag, {@code null} if not specified.
251                 */
252                public LangTag getLangTag() {
253
254                        return langTag;
255                }
256
257
258                /**
259                 * Gets the optional value for the claim.
260                 *
261                 * @return The value, {@code null} if not specified.
262                 */
263                public String getValue() {
264
265                        return value;
266                }
267
268
269                /**
270                 * Gets the optional values for the claim.
271                 *
272                 * @return The values, {@code null} if not specified.
273                 */
274                public List<String> getValues() {
275
276                        return values;
277                }
278
279
280                /**
281                 * Returns the JSON object representation of the specified 
282                 * collection of individual claim requests.
283                 *
284                 * <p>Example:
285                 *
286                 * <pre>
287                 * {
288                 *   "given_name": {"essential": true},
289                 *   "nickname": null,
290                 *   "email": {"essential": true},
291                 *   "email_verified": {"essential": true},
292                 *   "picture": null,
293                 *   "http://example.info/claims/groups": null
294                 * }  
295                 * </pre>
296                 *
297                 * @param entries The entries to serialise. Must not be 
298                 *                {@code null}.
299                 *
300                 * @return The corresponding JSON object, empty if no claims 
301                 *         were found.
302                 */
303                public static JSONObject toJSONObject(final Collection<Entry> entries) {
304
305                        JSONObject o = new JSONObject();
306
307                        for (Entry entry: entries) {
308
309                                // Compose the optional value
310                                JSONObject entrySpec = null;
311
312                                if (entry.getValue() != null) {
313
314                                        entrySpec = new JSONObject();
315                                        entrySpec.put("value", entry.getValue());
316                                }
317
318                                if (entry.getValues() != null) {
319
320                                        // Either "value" or "values", or none
321                                        // may be defined
322                                        entrySpec = new JSONObject();
323                                        entrySpec.put("values", entry.getValues());
324                                }
325
326                                if (entry.getClaimRequirement().equals(ClaimRequirement.ESSENTIAL)) {
327
328                                        if (entrySpec == null)
329                                                entrySpec = new JSONObject();
330
331                                        entrySpec.put("essential", true);
332                                }
333
334                                o.put(entry.getClaimName(true), entrySpec);
335                        }
336
337                        return o;
338                }
339
340
341                /**
342                 * Parses a collection of individual claim requests from the
343                 * specified JSON object. Request entries that are not 
344                 * understood are silently ignored.
345                 */
346                public static Collection<Entry> parseEntries(final JSONObject jsonObject) {
347
348                        Collection<Entry> entries = new LinkedList<Entry>();
349
350                        if (jsonObject.isEmpty())
351                                return entries;
352
353                        for (Map.Entry<String,Object> member: jsonObject.entrySet()) {
354
355                                // Process the key
356                                String claimNameWithOptLangTag = member.getKey();
357
358                                String claimName;
359                                LangTag langTag = null;
360
361                                if (claimNameWithOptLangTag.contains("#")) {
362
363                                        String[] parts = claimNameWithOptLangTag.split("#", 2);
364
365                                        claimName = parts[0];
366
367                                        try {
368                                                langTag = LangTag.parse(parts[1]);
369
370                                        } catch (LangTagException e) {
371
372                                                // Ignore and continue
373                                                continue;
374                                        }
375
376                                } else {
377                                        claimName = claimNameWithOptLangTag;
378                                }
379
380                                // Parse the optional value
381                                if (member.getValue() == null) {
382
383                                        // Voluntary claim with no value(s)
384                                        entries.add(new Entry(claimName, langTag));
385                                        continue;
386                                }
387
388                                try {
389                                        JSONObject entrySpec = (JSONObject)member.getValue();
390
391                                        ClaimRequirement requirement = ClaimRequirement.VOLUNTARY;
392
393                                        if (entrySpec.containsKey("essential")) {
394
395                                                boolean isEssential = (Boolean)entrySpec.get("essential");
396
397                                                if (isEssential)
398                                                        requirement = ClaimRequirement.ESSENTIAL;
399                                        }
400
401                                        if (entrySpec.containsKey("value")) {
402
403                                                String expectedValue = (String)entrySpec.get("value");
404
405                                                entries.add(new Entry(claimName, requirement, langTag, expectedValue));
406
407                                        } else if (entrySpec.containsKey("values")) {
408
409                                                List<String> expectedValues = new LinkedList<String>();
410
411                                                for (Object v: (List)entrySpec.get("values")) {
412
413                                                        expectedValues.add((String)v);
414                                                }
415
416                                                entries.add(new Entry(claimName, requirement, langTag, expectedValues));
417
418                                        } else {
419                                                entries.add(new Entry(claimName, requirement, langTag, (String)null));
420                                        }
421
422                                } catch (Exception e) {
423                                        // Ignore and continue
424                                }
425                        }
426
427                        return entries;
428                }
429        }
430
431
432        /**
433         * The requested ID token claims, keyed by claim name and language tag.
434         */
435        private final Map<ImmutablePair<String,LangTag>,Entry> idTokenClaims =
436                new HashMap<ImmutablePair<String,LangTag>,Entry>();
437
438
439        /**
440         * The requested UserInfo claims, keyed by claim name and language tag.
441         */
442        private final Map<ImmutablePair<String,LangTag>,Entry> userInfoClaims =
443                new HashMap<ImmutablePair<String,LangTag>,Entry>(); 
444        
445
446        /**
447         * Creates a new empty claims request.
448         */
449        public ClaimsRequest() {
450
451                // Nothing to initialise
452        }
453        
454        
455        /**
456         * Adds the entries from the specified other claims request.
457         * 
458         * @param other The other claims request. If {@code null} no claims
459         *              request entries will be added to this claims request.
460         */
461        public void add(final ClaimsRequest other) {
462                
463                if (other == null)
464                        return;
465                
466                idTokenClaims.putAll(other.idTokenClaims);
467                userInfoClaims.putAll(other.userInfoClaims);
468        }
469
470
471        /**
472         * Adds the specified ID token claim to the request. It is marked as
473         * voluntary and no language tag and value(s) are associated with it.
474         *
475         * @param claimName The claim name. Must not be {@code null}.
476         */
477        public void addIDTokenClaim(final String claimName) {
478
479                addIDTokenClaim(claimName, ClaimRequirement.VOLUNTARY);
480        }
481
482
483        /**
484         * Adds the specified ID token claim to the request. No language tag 
485         * and value(s) are associated with it.
486         *
487         * @param claimName   The claim name. Must not be {@code null}.
488         * @param requirement The claim requirement. Must not be {@code null}.
489         */
490        public void addIDTokenClaim(final String claimName, final ClaimRequirement requirement) {
491
492                addIDTokenClaim(claimName, requirement, null);
493        }
494
495
496        /**
497         * Adds the specified ID token claim to the request. No value(s) are 
498         * associated with it.
499         *
500         * @param claimName   The claim name. Must not be {@code null}.
501         * @param requirement The claim requirement. Must not be {@code null}.
502         * @param langTag     The associated language tag, {@code null} if not
503         *                    specified.
504         */
505        public void addIDTokenClaim(final String claimName, final ClaimRequirement requirement, 
506                                    final LangTag langTag) {
507
508
509                addIDTokenClaim(claimName, requirement, langTag, (String)null);
510        }
511
512
513        /**
514         * Adds the specified ID token claim to the request.
515         *
516         * @param claimName   The claim name. Must not be {@code null}.
517         * @param requirement The claim requirement. Must not be {@code null}.
518         * @param langTag     The associated language tag, {@code null} if not
519         *                    specified.
520         * @param value       The expected claim value, {@code null} if not
521         *                    specified.
522         */
523        public void addIDTokenClaim(final String claimName, final ClaimRequirement requirement, 
524                                    final LangTag langTag, final String value) {
525
526                addIDTokenClaim(new Entry(claimName, requirement, langTag, value));
527        }
528
529
530        /**
531         * Adds the specified ID token claim to the request.
532         *
533         * @param claimName   The claim name. Must not be {@code null}.
534         * @param requirement The claim requirement. Must not be {@code null}.
535         * @param langTag     The associated language tag, {@code null} if not
536         *                    specified.
537         * @param values      The expected claim values, {@code null} if not
538         *                    specified.
539         */
540        public void addIDTokenClaim(final String claimName, final ClaimRequirement requirement, 
541                                    final LangTag langTag, final List<String> values) {
542
543                addIDTokenClaim(new Entry(claimName, requirement, langTag, values));
544        }
545
546
547        /**
548         * Adds the specified ID token claim to the request.
549         *
550         * @param entry The individual ID token claim request. Must not be
551         *              {@code null}.
552         */
553        public void addIDTokenClaim(final Entry entry) {
554
555                ImmutablePair<String,LangTag> key = 
556                        new ImmutablePair<String,LangTag>(entry.getClaimName(), entry.getLangTag());
557
558                idTokenClaims.put(key, entry);
559        }
560
561
562        /**
563         * Gets the requested ID token claims.
564         *
565         * @return The ID token claims, as an unmodifiable collection, empty 
566         *         set if none.
567         */
568        public Collection<Entry> getIDTokenClaims() {
569
570                return Collections.unmodifiableCollection(idTokenClaims.values());
571        }
572        
573        
574        /**
575         * Gets the names of the requested ID token claim names.
576         * 
577         * @param withLangTag If {@code true} the language tags, if any, will 
578         *                    be appended to the names, else not.
579         * 
580         * 
581         * @return The ID token claim names, as an unmodifiable set, empty set
582         *         if none.
583         */
584        public Set<String> getIDTokenClaimNames(final boolean withLangTag) {
585                
586                Set<String> names = new HashSet<String>();
587                
588                for (Entry en: idTokenClaims.values())
589                        names.add(en.getClaimName(withLangTag));
590                
591                return Collections.unmodifiableSet(names);
592        }
593
594
595        /**
596         * Removes the specified ID token claim from the request.
597         *
598         * @param claimName The claim name. Must not be {@code null}.
599         * @param langTag   The associated language tag, {@code null} if none.
600         *
601         * @return The removed ID token claim, {@code null} if not found.
602         */
603        public Entry removeIDTokenClaim(final String claimName, final LangTag langTag) {
604
605                ImmutablePair<String,LangTag> key = 
606                        new ImmutablePair<String,LangTag>(claimName, langTag);
607
608                return idTokenClaims.remove(key);
609        }
610
611
612        /**
613         * Removes the specified ID token claims from the request, in all
614         * existing language tag variations.
615         *
616         * @param claimName The claim name. Must not be {@code null}.
617         *
618         * @return The removed ID token claims, as an unmodifiable collection,
619         *         empty set if none were found.
620         */
621        public Collection<Entry> removeIDTokenClaims(final String claimName) {
622
623                Collection<Entry> removedClaims = new LinkedList<Entry>();
624
625                Iterator<Map.Entry<ImmutablePair<String,LangTag>,Entry>> it = idTokenClaims.entrySet().iterator();
626
627                while (it.hasNext()) {
628
629                        Map.Entry<ImmutablePair<String,LangTag>,Entry> reqEntry = it.next();
630
631                        if (reqEntry.getKey().getLeft().equals(claimName)) {
632
633                                removedClaims.add(reqEntry.getValue());
634
635                                it.remove();
636                        }
637                }
638
639                return Collections.unmodifiableCollection(removedClaims);
640        }
641
642
643        /**
644         * Adds the specified UserInfo claim to the request. It is marked as
645         * voluntary and no language tag and value(s) are associated with it.
646         *
647         * @param claimName The claim name. Must not be {@code null}.
648         */
649        public void addUserInfoClaim(final String claimName) {
650
651                addUserInfoClaim(claimName, ClaimRequirement.VOLUNTARY);
652        }
653
654
655        /**
656         * Adds the specified UserInfo claim to the request. No language tag 
657         * and value(s) are associated with it.
658         *
659         * @param claimName   The claim name. Must not be {@code null}.
660         * @param requirement The claim requirement. Must not be {@code null}.
661         */
662        public void addUserInfoClaim(final String claimName, final ClaimRequirement requirement) {
663
664                addUserInfoClaim(claimName, requirement, null);
665        }
666
667
668        /**
669         * Adds the specified UserInfo claim to the request. No value(s) are 
670         * associated with it.
671         *
672         * @param claimName   The claim name. Must not be {@code null}.
673         * @param requirement The claim requirement. Must not be {@code null}.
674         * @param langTag     The associated language tag, {@code null} if not
675         *                    specified.
676         */
677        public void addUserInfoClaim(final String claimName, final ClaimRequirement requirement, 
678                                     final LangTag langTag) {
679
680
681                addUserInfoClaim(claimName, requirement, langTag, (String)null);
682        }
683
684
685        /**
686         * Adds the specified UserInfo claim to the request.
687         *
688         * @param claimName   The claim name. Must not be {@code null}.
689         * @param requirement The claim requirement. Must not be {@code null}.
690         * @param langTag     The associated language tag, {@code null} if not
691         *                    specified.
692         * @param value       The expected claim value, {@code null} if not
693         *                    specified.
694         */
695        public void addUserInfoClaim(final String claimName, final ClaimRequirement requirement, 
696                                     final LangTag langTag, final String value) {
697
698                addUserInfoClaim(new Entry(claimName, requirement, langTag, value));
699        }
700
701
702        /**
703         * Adds the specified UserInfo claim to the request.
704         *
705         * @param claimName   The claim name. Must not be {@code null}.
706         * @param requirement The claim requirement. Must not be {@code null}.
707         * @param langTag     The associated language tag, {@code null} if not
708         *                    specified.
709         * @param values      The expected claim values, {@code null} if not
710         *                    specified.
711         */
712        public void addUserInfoClaim(final String claimName, final ClaimRequirement requirement, 
713                                     final LangTag langTag, final List<String> values) {
714
715                addUserInfoClaim(new Entry(claimName, requirement, langTag, values));
716        }
717
718
719        /**
720         * Adds the specified UserInfo claim to the request.
721         *
722         * @param entry The individual UserInfo claim request. Must not be
723         *              {@code null}.
724         */
725        public void addUserInfoClaim(final Entry entry) {
726
727                ImmutablePair<String,LangTag> key = 
728                        new ImmutablePair<String,LangTag>(entry.getClaimName(), entry.getLangTag());
729
730                userInfoClaims.put(key, entry);
731        }
732
733
734        /**
735         * Gets the requested UserInfo claims.
736         *
737         * @return The UserInfo claims, as an unmodifiable collection, empty 
738         *         set if none.
739         */
740        public Collection<Entry> getUserInfoClaims() {
741
742                return Collections.unmodifiableCollection(userInfoClaims.values());
743        }
744        
745        
746        /**
747         * Gets the names of the requested UserInfo claim names.
748         * 
749         * @param withLangTag If {@code true} the language tags, if any, will 
750         *                    be appended to the names, else not.
751         * 
752         * 
753         * @return The UserInfo claim names, as an unmodifiable set, empty set
754         *         if none.
755         */
756        public Set<String> getUserInfoClaimNames(final boolean withLangTag) {
757                
758                Set<String> names = new HashSet<String>();
759                
760                for (Entry en: userInfoClaims.values())
761                        names.add(en.getClaimName(withLangTag));
762                
763                return Collections.unmodifiableSet(names);
764        }
765
766
767        /**
768         * Removes the specified UserInfo claim from the request.
769         *
770         * @param claimName The claim name. Must not be {@code null}.
771         * @param langTag   The associated language tag, {@code null} if none.
772         *
773         * @return The removed UserInfo claim, {@code null} if not found.
774         */
775        public Entry removeUserInfoClaim(final String claimName, final LangTag langTag) {
776
777                ImmutablePair<String,LangTag> key = 
778                        new ImmutablePair<String,LangTag>(claimName, langTag);
779
780                return userInfoClaims.remove(key);
781        }
782
783
784        /**
785         * Removes the specified UserInfo claims from the request, in all
786         * existing language tag variations.
787         *
788         * @param claimName The claim name. Must not be {@code null}.
789         *
790         * @return The removed UserInfo claims, as an unmodifiable collection,
791         *         empty set if none were found.
792         */
793        public Collection<Entry> removeUserInfoClaims(final String claimName) {
794
795                Collection<Entry> removedClaims = new LinkedList<Entry>();
796
797                Iterator<Map.Entry<ImmutablePair<String,LangTag>,Entry>> it = userInfoClaims.entrySet().iterator();
798
799                while (it.hasNext()) {
800
801                        Map.Entry<ImmutablePair<String,LangTag>,Entry> reqEntry = it.next();
802
803                        if (reqEntry.getKey().getLeft().equals(claimName)) {
804
805                                removedClaims.add(reqEntry.getValue());
806
807                                it.remove();
808                        }
809                }
810
811                return Collections.unmodifiableCollection(removedClaims);
812        }
813
814
815        /**
816         * Returns the JSON object representation of this claims request.
817         *
818         * <p>Example:
819         *
820         * <pre>
821         * {
822         *   "userinfo":
823         *    {
824         *     "given_name": {"essential": true},
825         *     "nickname": null,
826         *     "email": {"essential": true},
827         *     "email_verified": {"essential": true},
828         *     "picture": null,
829         *     "http://example.info/claims/groups": null
830         *    },
831         *   "id_token":
832         *    {
833         *     "auth_time": {"essential": true},
834         *     "acr": {"values": ["urn:mace:incommon:iap:silver"] }
835         *    }
836         * }
837         * </pre>
838         *
839         * @return The corresponding JSON object, empty if no ID token and 
840         *         UserInfo claims are specified.
841         */
842        public JSONObject toJSONObject() {
843
844                JSONObject o = new JSONObject();
845
846                Collection<Entry> idTokenEntries = getIDTokenClaims();
847
848                if (! idTokenEntries.isEmpty()) {
849
850                        o.put("id_token", Entry.toJSONObject(idTokenEntries));
851                }
852
853                Collection<Entry> userInfoEntries = getUserInfoClaims();
854
855                if (! userInfoEntries.isEmpty()) {
856
857                        o.put("userinfo", Entry.toJSONObject(userInfoEntries));
858                }
859
860                return o;
861        }
862
863
864        @Override
865        public String toString() {
866
867                return toJSONObject().toString();
868        }
869        
870        
871        /**
872         * Resolves the claims request for the specified response type and
873         * scope. The scope values that are {@link OIDCScopeValue standard
874         * OpenID Connect scope values} are resolved to their respective
875         * individual claims requests, any other scope values are ignored.
876         *
877         * @param responseType The response type. Must not be {@code null}.
878         * @param scope        The scope. Must not be {@code null}.
879         * 
880         * @return The claims request.
881         */
882        public static ClaimsRequest resolve(final ResponseType responseType, final Scope scope) {
883
884                // Determine the claims target (ID token or UserInfo)
885                final boolean switchToIDToken =
886                        responseType.contains(OIDCResponseTypeValue.ID_TOKEN) &&
887                        ! responseType.contains(ResponseType.Value.CODE) &&
888                        ! responseType.contains(ResponseType.Value.TOKEN);
889
890                ClaimsRequest claimsRequest = new ClaimsRequest();
891                
892                for (Scope.Value value: scope) {
893                        
894                        Set<ClaimsRequest.Entry> entries;
895                        
896                        if (value.equals(OIDCScopeValue.PROFILE)) {
897                                
898                                entries = OIDCScopeValue.PROFILE.toClaimsRequestEntries();
899                                
900                        } else if (value.equals(OIDCScopeValue.EMAIL)) {
901                                
902                                entries = OIDCScopeValue.EMAIL.toClaimsRequestEntries();
903                                
904                        } else if (value.equals(OIDCScopeValue.PHONE)) {
905                                
906                                entries = OIDCScopeValue.PHONE.toClaimsRequestEntries();
907                                
908                        } else if (value.equals(OIDCScopeValue.ADDRESS)) {
909                                
910                                entries = OIDCScopeValue.ADDRESS.toClaimsRequestEntries();
911                                
912                        } else {
913                                
914                                continue; // skip
915                        }
916                        
917                        for (ClaimsRequest.Entry en: entries) {
918
919                                if (switchToIDToken)
920                                        claimsRequest.addIDTokenClaim(en);
921                                else
922                                        claimsRequest.addUserInfoClaim(en);
923                        }
924                }
925                
926                return claimsRequest;
927        }
928
929
930        /**
931         * Resolves the merged claims request from the specified OpenID Connect
932         * authorisation request parameters. The scope values that are
933         * {@link OIDCScopeValue standard OpenID Connect scope values} are
934         * resolved to their respective individual claims requests, any other
935         * scope values are ignored.
936         *
937         * @param responseType  The response type. Must not be {@code null}.
938         * @param scope         The scope. Must not be {@code null}.
939         * @param claimsRequest The claims request, corresponding to the
940         *                      optional {@code claims} OpenID Connect
941         *                      authorisation request parameter, {@code null}
942         *                      if not specified.
943         *
944         * @return The merged claims request.
945         */
946        public static ClaimsRequest resolve(final ResponseType responseType,
947                                            final Scope scope,
948                                            final ClaimsRequest claimsRequest) {
949
950                ClaimsRequest mergedClaimsRequest = resolve(responseType, scope);
951
952                mergedClaimsRequest.add(claimsRequest);
953
954                return mergedClaimsRequest;
955        }
956
957
958        /**
959         * Resolves the merged claims request for the specified OpenID Connect
960         * authentication request. The scope values that are
961         * {@link OIDCScopeValue standard OpenID Connect scope values} are
962         * resolved to their respective individual claims requests, any other
963         * scope values are ignored.
964         *
965         * @param authRequest The OpenID Connect authentication request. Must
966         *                    not be {@code null}.
967         *
968         * @return The merged claims request.
969         */
970        public static ClaimsRequest resolve(final AuthenticationRequest authRequest) {
971
972                return resolve(authRequest.getResponseType(), authRequest.getScope(), authRequest.getClaims());
973        }
974
975
976        /**
977         * Parses a claims request from the specified JSON object 
978         * representation. Unexpected members in the JSON object are silently
979         * ignored.
980         *
981         * @param jsonObject The JSON object to parse. Must not be 
982         *                   {@code null}.
983         *
984         * @return The claims request.
985         */
986        public static ClaimsRequest parse(final JSONObject jsonObject) {
987
988                ClaimsRequest claimsRequest = new ClaimsRequest();
989
990                try {
991                        if (jsonObject.containsKey("id_token")) {
992
993                                JSONObject idTokenObject = (JSONObject)jsonObject.get("id_token");
994
995                                Collection<Entry> idTokenClaims = Entry.parseEntries(idTokenObject);
996
997                                for (Entry entry: idTokenClaims)
998                                        claimsRequest.addIDTokenClaim(entry);
999                        }
1000
1001
1002                        if (jsonObject.containsKey("userinfo")) {
1003
1004                                JSONObject userInfoObject = (JSONObject)jsonObject.get("userinfo");
1005
1006                                Collection<Entry> userInfoClaims = Entry.parseEntries(userInfoObject);
1007
1008                                for (Entry entry: userInfoClaims)
1009                                        claimsRequest.addUserInfoClaim(entry);
1010                        }
1011
1012                } catch (Exception e) {
1013
1014                        // Ignore
1015                }
1016
1017                return claimsRequest;
1018        }
1019}