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