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