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