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