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.claims;
019
020
021import java.net.URI;
022import java.net.URISyntaxException;
023import java.util.*;
024import javax.mail.internet.InternetAddress;
025
026import com.nimbusds.jwt.JWT;
027import com.nimbusds.jwt.JWTClaimsSet;
028import com.nimbusds.jwt.JWTParser;
029import com.nimbusds.langtag.LangTag;
030import com.nimbusds.oauth2.sdk.ParseException;
031import com.nimbusds.oauth2.sdk.id.Audience;
032import com.nimbusds.oauth2.sdk.id.Issuer;
033import com.nimbusds.oauth2.sdk.id.Subject;
034import com.nimbusds.oauth2.sdk.token.AccessToken;
035import com.nimbusds.oauth2.sdk.token.TypelessAccessToken;
036import com.nimbusds.oauth2.sdk.util.JSONObjectUtils;
037import net.minidev.json.JSONObject;
038
039
040/**
041 * UserInfo claims set, serialisable to a JSON object.
042 *
043 * <p>Supports normal, aggregated and distributed claims.
044 *
045 * <p>Example UserInfo claims set:
046 *
047 * <pre>
048 * {
049 *   "sub"                : "248289761001",
050 *   "name"               : "Jane Doe",
051 *   "given_name"         : "Jane",
052 *   "family_name"        : "Doe",
053 *   "preferred_username" : "j.doe",
054 *   "email"              : "[email protected]",
055 *   "picture"            : "http://example.com/janedoe/me.jpg"
056 * }
057 * </pre>
058 *
059 * <p>Related specifications:
060 *
061 * <ul>
062 *     <li>OpenID Connect Core 1.0, sections 5.1 and 5.6.
063 * </ul>
064 */
065public class UserInfo extends ClaimsSet {
066
067
068        /**
069         * The subject claim name.
070         */
071        public static final String SUB_CLAIM_NAME = "sub";
072        
073        
074        /**
075         * The issuer claim name.
076         */
077        public static final String ISS_CLAIM_NAME = "iss";
078        
079        
080        /**
081         * The audience claim name.
082         */
083        public static final String AUD_CLAIM_NAME = "aud";
084
085
086        /**
087         * The name claim name.
088         */
089        public static final String NAME_CLAIM_NAME = "name";
090
091
092        /**
093         * The given name claim name.
094         */
095        public static final String GIVEN_NAME_CLAIM_NAME = "given_name";
096
097
098        /**
099         * The family name claim name.
100         */
101        public static final String FAMILY_NAME_CLAIM_NAME = "family_name";
102
103
104        /**
105         * The middle name claim name.
106         */
107        public static final String MIDDLE_NAME_CLAIM_NAME = "middle_name";
108
109
110        /**
111         * The nickname claim name.
112         */
113        public static final String NICKNAME_CLAIM_NAME = "nickname";
114
115
116        /**
117         * The preferred username claim name.
118         */
119        public static final String PREFERRED_USERNAME_CLAIM_NAME = "preferred_username";
120
121
122        /**
123         * The profile claim name.
124         */
125        public static final String PROFILE_CLAIM_NAME = "profile";
126
127
128        /**
129         * The picture claim name.
130         */
131        public static final String PICTURE_CLAIM_NAME = "picture";
132
133
134        /**
135         * The website claim name.
136         */
137        public static final String WEBSITE_CLAIM_NAME = "website";
138
139
140        /**
141         * The email claim name.
142         */
143        public static final String EMAIL_CLAIM_NAME = "email";
144
145
146        /**
147         * The email verified claim name.
148         */
149        public static final String EMAIL_VERIFIED_CLAIM_NAME = "email_verified";
150
151
152        /**
153         * The gender claim name.
154         */
155        public static final String GENDER_CLAIM_NAME = "gender";
156
157
158        /**
159         * The birth date claim name.
160         */
161        public static final String BIRTHDATE_CLAIM_NAME = "birthdate";
162
163
164        /**
165         * The zoneinfo claim name.
166         */
167        public static final String ZONEINFO_CLAIM_NAME = "zoneinfo";
168
169
170        /**
171         * The locale claim name.
172         */
173        public static final String LOCALE_CLAIM_NAME = "locale";
174
175
176        /**
177         * The phone number claim name.
178         */
179        public static final String PHONE_NUMBER_CLAIM_NAME = "phone_number";
180
181
182        /**
183         * The phone number verified claim name.
184         */
185        public static final String PHONE_NUMBER_VERIFIED_CLAIM_NAME = "phone_number_verified";
186
187
188        /**
189         * The address claim name.
190         */
191        public static final String ADDRESS_CLAIM_NAME = "address";
192
193
194        /**
195         * The updated at claim name.
196         */
197        public static final String UPDATED_AT_CLAIM_NAME = "updated_at";
198
199
200        /**
201         * The names of the standard top-level UserInfo claims.
202         */
203        private static final Set<String> stdClaimNames = new LinkedHashSet<>();
204        
205        
206        static {
207                stdClaimNames.add(SUB_CLAIM_NAME);
208                stdClaimNames.add(ISS_CLAIM_NAME);
209                stdClaimNames.add(AUD_CLAIM_NAME);
210                stdClaimNames.add(NAME_CLAIM_NAME);
211                stdClaimNames.add(GIVEN_NAME_CLAIM_NAME);
212                stdClaimNames.add(FAMILY_NAME_CLAIM_NAME);
213                stdClaimNames.add(MIDDLE_NAME_CLAIM_NAME);
214                stdClaimNames.add(NICKNAME_CLAIM_NAME);
215                stdClaimNames.add(PREFERRED_USERNAME_CLAIM_NAME);
216                stdClaimNames.add(PROFILE_CLAIM_NAME);
217                stdClaimNames.add(PICTURE_CLAIM_NAME);
218                stdClaimNames.add(WEBSITE_CLAIM_NAME);
219                stdClaimNames.add(EMAIL_CLAIM_NAME);
220                stdClaimNames.add(EMAIL_VERIFIED_CLAIM_NAME);
221                stdClaimNames.add(GENDER_CLAIM_NAME);
222                stdClaimNames.add(BIRTHDATE_CLAIM_NAME);
223                stdClaimNames.add(ZONEINFO_CLAIM_NAME);
224                stdClaimNames.add(LOCALE_CLAIM_NAME);
225                stdClaimNames.add(PHONE_NUMBER_CLAIM_NAME);
226                stdClaimNames.add(PHONE_NUMBER_VERIFIED_CLAIM_NAME);
227                stdClaimNames.add(ADDRESS_CLAIM_NAME);
228                stdClaimNames.add(UPDATED_AT_CLAIM_NAME);
229        }
230        
231        
232        /**
233         * Gets the names of the standard top-level UserInfo claims.
234         *
235         * @return The names of the standard top-level UserInfo claims 
236         *         (read-only set).
237         */
238        public static Set<String> getStandardClaimNames() {
239        
240                return Collections.unmodifiableSet(stdClaimNames);
241        }
242        
243        
244        /**
245         * Creates a new minimal UserInfo claims set.
246         *
247         * @param sub The subject. Must not be {@code null}.
248         */
249        public UserInfo(final Subject sub) {
250        
251                setClaim(SUB_CLAIM_NAME, sub.getValue());
252        }
253
254
255        /**
256         * Creates a new UserInfo claims set from the specified JSON object.
257         *
258         * @param jsonObject The JSON object. Must not be {@code null}.
259         *
260         * @throws IllegalArgumentException If the JSON object doesn't contain
261         *                                  a subject {@code sub} string claim.
262         */
263        public UserInfo(final JSONObject jsonObject) {
264
265                super(jsonObject);
266
267                if (getStringClaim(SUB_CLAIM_NAME) == null)
268                        throw new IllegalArgumentException("Missing or invalid \"sub\" claim");
269        }
270
271
272        /**
273         * Creates a new UserInfo claims set from the specified JSON Web Token
274         * (JWT) claims set.
275         *
276         * @param jwtClaimsSet The JWT claims set. Must not be {@code null}.
277         *
278         * @throws IllegalArgumentException If the JWT claims set doesn't
279         *                                  contain a subject {@code sub}
280         *                                  string claim.
281         */
282        public UserInfo(final JWTClaimsSet jwtClaimsSet) {
283
284                this(jwtClaimsSet.toJSONObject());
285        }
286
287
288        /**
289         * Puts all claims from the specified other UserInfo claims set.
290         * Aggregated and distributed claims are properly merged.
291         *
292         * @param other The other UserInfo. Must have the same
293         *              {@link #getSubject subject}. Must not be {@code null}.
294         *
295         * @throws IllegalArgumentException If the other UserInfo claims set
296         *                                  doesn't have an identical subject,
297         *                                  or if the external claims source ID
298         *                                  of the other UserInfo matches an
299         *                                  existing source ID.
300         */
301        public void putAll(final UserInfo other) {
302
303                Subject otherSubject = other.getSubject();
304
305                if (otherSubject == null)
306                        throw new IllegalArgumentException("The subject of the other UserInfo is missing");
307
308                if (! otherSubject.equals(getSubject()))
309                        throw new IllegalArgumentException("The subject of the other UserInfo must be identical");
310                
311                // Save present aggregated and distributed claims, to prevent
312                // overwrite by put to claims JSON object
313                Set<AggregatedClaims> savedAggregatedClaims = getAggregatedClaims();
314                Set<DistributedClaims> savedDistributedClaims = getDistributedClaims();
315                
316                // Save other present aggregated and distributed claims
317                Set<AggregatedClaims> otherAggregatedClaims = other.getAggregatedClaims();
318                Set<DistributedClaims> otherDistributedClaims = other.getDistributedClaims();
319                
320                // Ensure external source IDs don't conflict during merge
321                Set<String> externalSourceIDs = new HashSet<>();
322                
323                if (savedAggregatedClaims != null) {
324                        for (AggregatedClaims ac: savedAggregatedClaims) {
325                                externalSourceIDs.add(ac.getSourceID());
326                        }
327                }
328                
329                if (savedDistributedClaims != null) {
330                        for (DistributedClaims dc: savedDistributedClaims) {
331                                externalSourceIDs.add(dc.getSourceID());
332                        }
333                }
334                
335                if (otherAggregatedClaims != null) {
336                        for (AggregatedClaims ac: otherAggregatedClaims) {
337                                if (externalSourceIDs.contains(ac.getSourceID())) {
338                                        throw new IllegalArgumentException("Aggregated claims source ID conflict: " + ac.getSourceID());
339                                }
340                        }
341                }
342                
343                if (otherDistributedClaims != null) {
344                        for (DistributedClaims dc: otherDistributedClaims) {
345                                if (externalSourceIDs.contains(dc.getSourceID())) {
346                                        throw new IllegalArgumentException("Distributed claims source ID conflict: " + dc.getSourceID());
347                                }
348                        }
349                }
350                
351                putAll((ClaimsSet)other);
352                
353                // Merge saved external claims, if any
354                if (savedAggregatedClaims != null) {
355                        for (AggregatedClaims ac: savedAggregatedClaims) {
356                                addAggregatedClaims(ac);
357                        }
358                }
359                
360                if (savedDistributedClaims != null) {
361                        for (DistributedClaims dc: savedDistributedClaims) {
362                                addDistributedClaims(dc);
363                        }
364                }
365        }
366        
367        
368        /**
369         * Gets the UserInfo subject. Corresponds to the {@code sub} claim.
370         *
371         * @return The subject.
372         */
373        public Subject getSubject() {
374        
375                return new Subject(getStringClaim(SUB_CLAIM_NAME));
376        }
377        
378        
379        /**
380         * Gets the issuer. Corresponds to the {@code iss} claim.
381         *
382         * @return The issuer, {@code null} if not specified.
383         */
384        public Issuer getIssuer() {
385                
386                String iss = getStringClaim(ISS_CLAIM_NAME);
387                
388                return iss != null ? new Issuer(iss) : null;
389        }
390        
391        
392        /**
393         * Sets the issuer. Corresponds to the {@code iss} claim.
394         *
395         * @param iss The issuer, {@code null} if not specified.
396         */
397        public void setIssuer(final Issuer iss) {
398                
399                if (iss != null)
400                        setClaim(ISS_CLAIM_NAME, iss.getValue());
401                else
402                        setClaim(ISS_CLAIM_NAME, null);
403        }
404        
405        
406        /**
407         * Gets the audience. Corresponds to the {@code aud} claim.
408         *
409         * @return The audience list, {@code null} if not specified.
410         */
411        public List<Audience> getAudience() {
412                
413                List<String> list = getStringListClaim(AUD_CLAIM_NAME);
414                
415                return list != null && ! list.isEmpty() ? Audience.create(list) : null;
416        }
417        
418        
419        /**
420         * Sets the audience. Corresponds to the {@code aud} claim.
421         *
422         * @param aud The audience, {@code null} if not specified.
423         */
424        public void setAudience(final Audience aud) {
425                
426                if (aud != null)
427                        setAudience(aud.toSingleAudienceList());
428                else
429                        setClaim(AUD_CLAIM_NAME, null);
430        }
431        
432        
433        /**
434         * Sets the audience list. Corresponds to the {@code aud} claim.
435         *
436         * @param audList The audience list, {@code null} if not specified.
437         */
438        public void setAudience(final List<Audience> audList) {
439        
440                if (audList != null)
441                        setClaim(AUD_CLAIM_NAME, Audience.toStringList(audList));
442                else
443                        setClaim(AUD_CLAIM_NAME, null);
444        }
445
446        
447        /**
448         * Gets the full name. Corresponds to the {@code name} claim, with no
449         * language tag.
450         *
451         * @return The full name, {@code null} if not specified.
452         */
453        public String getName() {
454        
455                return getStringClaim(NAME_CLAIM_NAME);
456        }
457        
458        
459        /**
460         * Gets the full name. Corresponds to the {@code name} claim, with an
461         * optional language tag.
462         *
463         * @param langTag The language tag of the entry, {@code null} to get 
464         *                the non-tagged entry.
465         *
466         * @return The full name, {@code null} if not specified.
467         */
468        public String getName(final LangTag langTag) {
469        
470                return getStringClaim(NAME_CLAIM_NAME, langTag);
471        }
472        
473        
474        /**
475         * Gets the full name entries. Correspond to the {@code name} claim.
476         *
477         * @return The full name entries, empty map if none.
478         */
479        public Map<LangTag,String> getNameEntries() {
480        
481                return getLangTaggedClaim(NAME_CLAIM_NAME, String.class);
482        }
483
484
485        /**
486         * Sets the full name. Corresponds to the {@code name} claim, with no
487         * language tag.
488         *
489         * @param name The full name. If {@code null} the claim will be 
490         *             removed.
491         */
492        public void setName(final String name) {
493        
494                setClaim(NAME_CLAIM_NAME, name);
495        }
496        
497        
498        /**
499         * Sets the full name. Corresponds to the {@code name} claim, with an
500         * optional language tag.
501         *
502         * @param name    The full name. If {@code null} the claim will be 
503         *                removed.
504         * @param langTag The language tag, {@code null} if not specified.
505         */
506        public void setName(final String name, final LangTag langTag) {
507        
508                setClaim(NAME_CLAIM_NAME, name, langTag);
509        }       
510        
511        
512        /**
513         * Gets the given or first name. Corresponds to the {@code given_name} 
514         * claim, with no language tag.
515         *
516         * @return The given or first name, {@code null} if not specified.
517         */
518        public String getGivenName() {
519        
520                return getStringClaim(GIVEN_NAME_CLAIM_NAME);
521        }
522        
523        
524        /**
525         * Gets the given or first name. Corresponds to the {@code given_name} 
526         * claim, with an optional language tag.
527         *
528         * @param langTag The language tag of the entry, {@code null} to get 
529         *                the non-tagged entry.
530         *
531         * @return The given or first name, {@code null} if not specified.
532         */
533        public String getGivenName(final LangTag langTag) {
534        
535                return getStringClaim(GIVEN_NAME_CLAIM_NAME, langTag);
536        }
537        
538        
539        /**
540         * Gets the given or first name entries. Correspond to the 
541         * {@code given_name} claim.
542         *
543         * @return The given or first name entries, empty map if none.
544         */
545        public Map<LangTag,String> getGivenNameEntries() {
546        
547                return getLangTaggedClaim(GIVEN_NAME_CLAIM_NAME, String.class);
548        }
549
550
551        /**
552         * Sets the given or first name. Corresponds to the {@code given_name} 
553         * claim, with no language tag.
554         *
555         * @param givenName The given or first name. If {@code null} the claim
556         *                  will be removed.
557         */
558        public void setGivenName(final String givenName) {
559        
560                setClaim(GIVEN_NAME_CLAIM_NAME, givenName);
561        }
562        
563        
564        /**
565         * Sets the given or first name. Corresponds to the {@code given_name}
566         * claim, with an optional language tag.
567         *
568         * @param givenName The given or first full name. If {@code null} the 
569         *                  claim will be removed.
570         * @param langTag   The language tag, {@code null} if not specified.
571         */
572        public void setGivenName(final String givenName, final LangTag langTag) {
573        
574                setClaim(GIVEN_NAME_CLAIM_NAME, givenName, langTag);
575        }
576
577        
578        /**
579         * Gets the surname or last name. Corresponds to the 
580         * {@code family_name} claim, with no language tag.
581         *
582         * @return The surname or last name, {@code null} if not specified.
583         */
584        public String getFamilyName() {
585        
586                return getStringClaim(FAMILY_NAME_CLAIM_NAME);
587        }
588        
589        
590        /**
591         * Gets the surname or last name. Corresponds to the 
592         * {@code family_name} claim, with an optional language tag.
593         *
594         * @param langTag The language tag of the entry, {@code null} to get 
595         *                the non-tagged entry.
596         *
597         * @return The surname or last name, {@code null} if not specified.
598         */
599        public String getFamilyName(final LangTag langTag) {
600        
601                return getStringClaim(FAMILY_NAME_CLAIM_NAME, langTag);
602        }
603        
604        
605        /**
606         * Gets the surname or last name entries. Correspond to the 
607         * {@code family_name} claim.
608         *
609         * @return The surname or last name entries, empty map if none.
610         */
611        public Map<LangTag,String> getFamilyNameEntries() {
612        
613                return getLangTaggedClaim(FAMILY_NAME_CLAIM_NAME, String.class);
614        }
615
616
617        /**
618         * Sets the surname or last name. Corresponds to the 
619         * {@code family_name} claim, with no language tag.
620         *
621         * @param familyName The surname or last name. If {@code null} the 
622         *                   claim will be removed.
623         */
624        public void setFamilyName(final String familyName) {
625        
626                setClaim(FAMILY_NAME_CLAIM_NAME, familyName);
627        }
628        
629        
630        /**
631         * Sets the surname or last name. Corresponds to the 
632         * {@code family_name} claim, with an optional language tag.
633         *
634         * @param familyName The surname or last name. If {@code null} the 
635         *                   claim will be removed.
636         * @param langTag    The language tag, {@code null} if not specified.
637         */
638        public void setFamilyName(final String familyName, final LangTag langTag) {
639        
640                setClaim(FAMILY_NAME_CLAIM_NAME, familyName, langTag);
641        }
642
643        
644        /**
645         * Gets the middle name. Corresponds to the {@code middle_name} claim, 
646         * with no language tag.
647         *
648         * @return The middle name, {@code null} if not specified.
649         */
650        public String getMiddleName() {
651        
652                return getStringClaim(MIDDLE_NAME_CLAIM_NAME);
653        }
654        
655        
656        /**
657         * Gets the middle name. Corresponds to the {@code middle_name} claim,
658         * with an optional language tag.
659         *
660         * @param langTag The language tag of the entry, {@code null} to get 
661         *                the non-tagged entry.
662         *
663         * @return The middle name, {@code null} if not specified.
664         */
665        public String getMiddleName(final LangTag langTag) {
666        
667                return getStringClaim(MIDDLE_NAME_CLAIM_NAME, langTag);
668        }
669        
670        
671        /**
672         * Gets the middle name entries. Correspond to the {@code middle_name}
673         * claim.
674         *
675         * @return The middle name entries, empty map if none.
676         */
677        public Map<LangTag,String> getMiddleNameEntries() {
678        
679                return getLangTaggedClaim(MIDDLE_NAME_CLAIM_NAME, String.class);
680        }
681
682
683        /**
684         * Sets the middle name. Corresponds to the {@code middle_name} claim,
685         * with no language tag.
686         *
687         * @param middleName The middle name. If {@code null} the claim will be
688         *                   removed.
689         */
690        public void setMiddleName(final String middleName) {
691        
692                setClaim(MIDDLE_NAME_CLAIM_NAME, middleName);
693        }
694        
695        
696        /**
697         * Sets the middle name. Corresponds to the {@code middle_name} claim, 
698         * with an optional language tag.
699         *
700         * @param middleName The middle name. If {@code null} the claim will be
701         *                   removed.
702         * @param langTag    The language tag, {@code null} if not specified.
703         */
704        public void setMiddleName(final String middleName, final LangTag langTag) {
705        
706                setClaim(MIDDLE_NAME_CLAIM_NAME, middleName, langTag);
707        }
708        
709        
710        /**
711         * Gets the casual name. Corresponds to the {@code nickname} claim, 
712         * with no language tag.
713         *
714         * @return The casual name, {@code null} if not specified.
715         */
716        public String getNickname() {
717        
718                return getStringClaim(NICKNAME_CLAIM_NAME);
719        }
720        
721        
722        /**
723         * Gets the casual name. Corresponds to the {@code nickname} claim, 
724         * with an optional language tag.
725         *
726         * @param langTag The language tag of the entry, {@code null} to get 
727         *                the non-tagged entry.
728         *
729         * @return The casual name, {@code null} if not specified.
730         */
731        public String getNickname(final LangTag langTag) {
732        
733                return getStringClaim(NICKNAME_CLAIM_NAME, langTag);
734        }
735        
736        
737        /**
738         * Gets the casual name entries. Correspond to the {@code nickname} 
739         * claim.
740         *
741         * @return The casual name entries, empty map if none.
742         */
743        public Map<LangTag,String> getNicknameEntries() {
744        
745                return getLangTaggedClaim(NICKNAME_CLAIM_NAME, String.class);
746        }
747
748
749        /**
750         * Sets the casual name. Corresponds to the {@code nickname} claim, 
751         * with no language tag.
752         *
753         * @param nickname The casual name. If {@code null} the claim will be
754         *                 removed.
755         */
756        public void setNickname(final String nickname) {
757        
758                setClaim(NICKNAME_CLAIM_NAME, nickname);
759        }
760        
761        
762        /**
763         * Sets the casual name. Corresponds to the {@code nickname} claim, 
764         * with an optional language tag.
765         *
766         * @param nickname The casual name. If {@code null} the claim will be
767         *                 removed.
768         * @param langTag  The language tag, {@code null} if not specified.
769         */
770        public void setNickname(final String nickname, final LangTag langTag) {
771        
772                setClaim(NICKNAME_CLAIM_NAME, nickname, langTag);
773        }
774        
775        
776        /**
777         * Gets the preferred username. Corresponds to the 
778         * {@code preferred_username} claim.
779         *
780         * @return The preferred username, {@code null} if not specified.
781         */
782        public String getPreferredUsername() {
783        
784                return getStringClaim(PREFERRED_USERNAME_CLAIM_NAME);
785        }
786        
787        
788        /**
789         * Sets the preferred username. Corresponds to the 
790         * {@code preferred_username} claim.
791         *
792         * @param preferredUsername The preferred username. If {@code null} the
793         *                          claim will be removed.
794         */
795        public void setPreferredUsername(final String preferredUsername) {
796        
797                setClaim(PREFERRED_USERNAME_CLAIM_NAME, preferredUsername);
798        }
799        
800        
801        /**
802         * Gets the profile page. Corresponds to the {@code profile} claim.
803         *
804         * @return The profile page URI, {@code null} if not specified.
805         */
806        public URI getProfile() {
807        
808                return getURIClaim(PROFILE_CLAIM_NAME);
809        }
810        
811        
812        /**
813         * Sets the profile page. Corresponds to the {@code profile} claim.
814         *
815         * @param profile The profile page URI. If {@code null} the claim will
816         *                be removed.
817         */
818        public void setProfile(final URI profile) {
819        
820                setURIClaim(PROFILE_CLAIM_NAME, profile);
821        }
822        
823        
824        /**
825         * Gets the picture. Corresponds to the {@code picture} claim.
826         *
827         * @return The picture URI, {@code null} if not specified.
828         */
829        public URI getPicture() {
830        
831                return getURIClaim(PICTURE_CLAIM_NAME);
832        }
833        
834        
835        /**
836         * Sets the picture. Corresponds to the {@code picture} claim.
837         *
838         * @param picture The picture URI. If {@code null} the claim will be
839         *                removed.
840         */
841        public void setPicture(final URI picture) {
842        
843                setURIClaim(PICTURE_CLAIM_NAME, picture);
844        }
845        
846        
847        /**
848         * Gets the web page or blog. Corresponds to the {@code website} claim.
849         *
850         * @return The web page or blog URI, {@code null} if not specified.
851         */
852        public URI getWebsite() {
853        
854                return getURIClaim(WEBSITE_CLAIM_NAME);
855        }
856        
857        
858        /**
859         * Sets the web page or blog. Corresponds to the {@code website} claim.
860         *
861         * @param website The web page or blog URI. If {@code null} the claim
862         *                will be removed.
863         */
864        public void setWebsite(final URI website) {
865        
866                setURIClaim(WEBSITE_CLAIM_NAME, website);
867        }
868        
869        
870        /**
871         * Gets the preferred email address. Corresponds to the {@code email}
872         * claim.
873         *
874         * <p>Use {@link #getEmailAddress()} instead.
875         *
876         * @return The preferred email address, {@code null} if not specified.
877         */
878        @Deprecated
879        public InternetAddress getEmail() {
880        
881                return getEmailClaim(EMAIL_CLAIM_NAME);
882        }
883        
884        
885        /**
886         * Sets the preferred email address. Corresponds to the {@code email}
887         * claim.
888         *
889         * <p>Use {@link #setEmailAddress(String)} instead.
890         *
891         * @param email The preferred email address. If {@code null} the claim
892         *              will be removed.
893         */
894        @Deprecated
895        public void setEmail(final InternetAddress email) {
896        
897                setEmailClaim(EMAIL_CLAIM_NAME, email);
898        }
899        
900        
901        /**
902         * Gets the preferred email address. Corresponds to the {@code email}
903         * claim.
904         *
905         * @return The preferred email address, {@code null} if not specified.
906         */
907        public String getEmailAddress() {
908        
909                return getStringClaim(EMAIL_CLAIM_NAME);
910        }
911        
912        
913        /**
914         * Sets the preferred email address. Corresponds to the {@code email}
915         * claim.
916         *
917         * @param email The preferred email address. If {@code null} the claim
918         *              will be removed.
919         */
920        public void setEmailAddress(final String email) {
921        
922                setClaim(EMAIL_CLAIM_NAME, email);
923        }
924        
925        
926        /**
927         * Gets the email verification status. Corresponds to the 
928         * {@code email_verified} claim.
929         *
930         * @return The email verification status, {@code null} if not 
931         *         specified.
932         */
933        public Boolean getEmailVerified() {
934        
935                return getBooleanClaim(EMAIL_VERIFIED_CLAIM_NAME);
936        }
937        
938        
939        /**
940         * Sets the email verification status. Corresponds to the
941         * {@code email_verified} claim.
942         *
943         * @param emailVerified The email verification status. If {@code null} 
944         *                      the claim will be removed.
945         */
946        public void setEmailVerified(final Boolean emailVerified) {
947        
948                setClaim(EMAIL_VERIFIED_CLAIM_NAME, emailVerified);
949        }
950        
951        
952        /**
953         * Gets the gender. Corresponds to the {@code gender} claim.
954         *
955         * @return The gender, {@code null} if not specified.
956         */
957        public Gender getGender() {
958        
959                String value = getStringClaim(GENDER_CLAIM_NAME);
960                
961                if (value == null)
962                        return null;
963
964                return new Gender(value);
965        }
966        
967        
968        /**
969         * Sets the gender. Corresponds to the {@code gender} claim.
970         *
971         * @param gender The gender. If {@code null} the claim will be removed.
972         */
973        public void setGender(final Gender gender) {
974        
975                if (gender != null)
976                        setClaim(GENDER_CLAIM_NAME, gender.getValue());
977                else
978                        setClaim(GENDER_CLAIM_NAME, null);
979        }
980        
981        
982        /**
983         * Gets the date of birth. Corresponds to the {@code birthdate} claim.
984         *
985         * @return The date of birth, {@code null} if not specified.
986         */
987        public String getBirthdate() {
988        
989                return getStringClaim(BIRTHDATE_CLAIM_NAME);
990        }
991        
992        
993        /**
994         * Sets the date of birth. Corresponds to the {@code birthdate} claim.
995         *
996         * @param birthdate The date of birth. If {@code null} the claim will
997         *                  be removed.
998         */
999        public void setBirthdate(final String birthdate) {
1000        
1001                setClaim(BIRTHDATE_CLAIM_NAME, birthdate);
1002        }
1003        
1004        
1005        /**
1006         * Gets the zoneinfo. Corresponds to the {@code zoneinfo} claim.
1007         *
1008         * @return The zoneinfo, {@code null} if not specified.
1009         */
1010        public String getZoneinfo() {
1011        
1012                return getStringClaim(ZONEINFO_CLAIM_NAME);
1013        }
1014        
1015        
1016        /**
1017         * Sets the zoneinfo. Corresponds to the {@code zoneinfo} claim.
1018         *
1019         * @param zoneinfo The zoneinfo. If {@code null} the claim will be 
1020         *                 removed.
1021         */
1022        public void setZoneinfo(final String zoneinfo) {
1023        
1024                setClaim(ZONEINFO_CLAIM_NAME, zoneinfo);
1025        }
1026        
1027        
1028        /**
1029         * Gets the locale. Corresponds to the {@code locale} claim.
1030         *
1031         * @return The locale, {@code null} if not specified.
1032         */
1033        public String getLocale() {
1034        
1035                return getStringClaim(LOCALE_CLAIM_NAME);
1036        }
1037        
1038        
1039        /**
1040         * Sets the locale. Corresponds to the {@code locale} claim.
1041         *
1042         * @param locale The locale. If {@code null} the claim will be 
1043         *               removed.
1044         */
1045        public void setLocale(final String locale) {
1046        
1047                setClaim(LOCALE_CLAIM_NAME, locale);
1048        }
1049        
1050        
1051        /**
1052         * Gets the preferred telephone number. Corresponds to the 
1053         * {@code phone_number} claim.
1054         *
1055         * @return The preferred telephone number, {@code null} if not 
1056         *         specified.
1057         */
1058        public String getPhoneNumber() {
1059        
1060                return getStringClaim(PHONE_NUMBER_CLAIM_NAME);
1061        }
1062        
1063        
1064        /**
1065         * Sets the preferred telephone number. Corresponds to the 
1066         * {@code phone_number} claim.
1067         *
1068         * @param phoneNumber The preferred telephone number. If {@code null} 
1069         *                    the claim will be removed.
1070         */
1071        public void setPhoneNumber(final String phoneNumber) {
1072        
1073                setClaim(PHONE_NUMBER_CLAIM_NAME, phoneNumber);
1074        }
1075        
1076        
1077        /**
1078         * Gets the phone number verification status. Corresponds to the 
1079         * {@code phone_number_verified} claim.
1080         *
1081         * @return The phone number verification status, {@code null} if not 
1082         *         specified.
1083         */
1084        public Boolean getPhoneNumberVerified() {
1085        
1086                return getBooleanClaim(PHONE_NUMBER_VERIFIED_CLAIM_NAME);
1087        }
1088        
1089        
1090        /**
1091         * Sets the email verification status. Corresponds to the
1092         * {@code phone_number_verified} claim.
1093         *
1094         * @param phoneNumberVerified The phone number verification status. If 
1095         *                            {@code null} the claim will be removed.
1096         */
1097        public void setPhoneNumberVerified(final Boolean phoneNumberVerified) {
1098        
1099                setClaim(PHONE_NUMBER_VERIFIED_CLAIM_NAME, phoneNumberVerified);
1100        }
1101
1102
1103        /**
1104         * Gets the preferred address. Corresponds to the {@code address} 
1105         * claim, with no language tag.
1106         *
1107         * @return The preferred address, {@code null} if not specified.
1108         */
1109        public Address getAddress() {
1110        
1111                return getAddress(null);
1112        }
1113        
1114        
1115        /**
1116         * Gets the preferred address. Corresponds to the {@code address} 
1117         * claim, with an optional language tag.
1118         *
1119         * @param langTag The language tag of the entry, {@code null} to get 
1120         *                the non-tagged entry.
1121         *
1122         * @return The preferred address, {@code null} if not specified.
1123         */
1124        public Address getAddress(final LangTag langTag) {
1125        
1126                String name;
1127
1128                if (langTag!= null)
1129                        name = ADDRESS_CLAIM_NAME + "#" + langTag;
1130                else
1131                        name = ADDRESS_CLAIM_NAME;
1132
1133                JSONObject jsonObject = getClaim(name, JSONObject.class);
1134
1135                if (jsonObject == null)
1136                        return null;
1137
1138                return new Address(jsonObject);
1139        }
1140        
1141        
1142        /**
1143         * Gets the preferred address entries. Correspond to the 
1144         * {@code address} claim.
1145         *
1146         * @return The preferred address entries, empty map if none.
1147         */
1148        public Map<LangTag,Address> getAddressEntries() {
1149        
1150                Map<LangTag,JSONObject> entriesIn = getLangTaggedClaim(ADDRESS_CLAIM_NAME, JSONObject.class);
1151
1152                Map<LangTag,Address> entriesOut = new HashMap<>();
1153
1154                for (Map.Entry<LangTag,JSONObject> en: entriesIn.entrySet())
1155                        entriesOut.put(en.getKey(), new Address(en.getValue()));
1156
1157                return entriesOut;
1158        }
1159
1160
1161        /**
1162         * Sets the preferred address. Corresponds to the {@code address} 
1163         * claim, with no language tag.
1164         *
1165         * @param address The preferred address. If {@code null} the claim will
1166         *                be removed.
1167         */
1168        public void setAddress(final Address address) {
1169        
1170                if (address != null)
1171                        setClaim(ADDRESS_CLAIM_NAME, address.toJSONObject());
1172                else
1173                        setClaim(ADDRESS_CLAIM_NAME, null);
1174        }
1175        
1176        
1177        /**
1178         * Sets the preferred address. Corresponds to the {@code address}
1179         * claim, with an optional language tag.
1180         *
1181         * @param address  The preferred address. If {@code null} the claim 
1182         *                 will be removed.
1183         * @param langTag The language tag, {@code null} if not specified.
1184         */
1185        public void setAddress(final Address address, final LangTag langTag) {
1186
1187                String key = langTag == null ? ADDRESS_CLAIM_NAME : ADDRESS_CLAIM_NAME + "#" + langTag;
1188
1189                if (address != null)
1190                        setClaim(key, address.toJSONObject());
1191                else
1192                        setClaim(key, null);
1193        }
1194        
1195        
1196        /**
1197         * Gets the time the end-user information was last updated. Corresponds 
1198         * to the {@code updated_at} claim.
1199         *
1200         * @return The time the end-user information was last updated, 
1201         *         {@code null} if not specified.
1202         */
1203        public Date getUpdatedTime() {
1204        
1205                return getDateClaim(UPDATED_AT_CLAIM_NAME);
1206        }
1207        
1208        
1209        /**
1210         * Sets the time the end-user information was last updated. Corresponds
1211         * to the {@code updated_at} claim.
1212         *
1213         * @param updatedTime The time the end-user information was last 
1214         *                    updated. If {@code null} the claim will be 
1215         *                    removed.
1216         */
1217        public void setUpdatedTime(final Date updatedTime) {
1218        
1219                setDateClaim(UPDATED_AT_CLAIM_NAME, updatedTime);
1220        }
1221        
1222        
1223        /**
1224         * Adds the specified aggregated claims provided by an external claims
1225         * source.
1226         *
1227         * @param aggregatedClaims The aggregated claims instance, if
1228         *                         {@code null} nothing will be added.
1229         */
1230        public void addAggregatedClaims(final AggregatedClaims aggregatedClaims) {
1231                
1232                if (aggregatedClaims == null) {
1233                        return;
1234                }
1235                
1236                aggregatedClaims.mergeInto(claims);
1237        }
1238        
1239        
1240        /**
1241         * Gets the included aggregated claims provided by each external claims
1242         * source.
1243         *
1244         * @return The aggregated claims, {@code null} if none are found.
1245         */
1246        public Set<AggregatedClaims> getAggregatedClaims() {
1247        
1248                Map<String,JSONObject> claimSources = ExternalClaimsUtils.getExternalClaimSources(claims);
1249                
1250                if (claimSources == null) {
1251                        return null; // No external _claims_sources
1252                }
1253                
1254                Set<AggregatedClaims> aggregatedClaimsSet = new HashSet<>();
1255                
1256                for (Map.Entry<String,JSONObject> en: claimSources.entrySet()) {
1257                        
1258                        String sourceID = en.getKey();
1259                        JSONObject sourceSpec = en.getValue();
1260                        
1261                        Object jwtValue = sourceSpec.get("JWT");
1262                        if (! (jwtValue instanceof String)) {
1263                                continue; // skip
1264                        }
1265                        
1266                        JWT claimsJWT;
1267                        try {
1268                                claimsJWT = JWTParser.parse((String)jwtValue);
1269                        } catch (java.text.ParseException e) {
1270                                continue; // invalid JWT, skip
1271                        }
1272                        
1273                        Set<String> claimNames = ExternalClaimsUtils.getExternalClaimNamesForSource(claims, sourceID);
1274                        
1275                        if (claimNames.isEmpty()) {
1276                                continue; // skip
1277                        }
1278                        
1279                        aggregatedClaimsSet.add(new AggregatedClaims(sourceID, claimNames, claimsJWT));
1280                }
1281                
1282                if (aggregatedClaimsSet.isEmpty()) {
1283                        return null;
1284                }
1285                
1286                return aggregatedClaimsSet;
1287        }
1288        
1289        
1290        /**
1291         * Adds the specified distributed claims from an external claims source.
1292         *
1293         * @param distributedClaims The distributed claims instance, if
1294         *                          {@code null} nothing will be added.
1295         */
1296        public void addDistributedClaims(final DistributedClaims distributedClaims) {
1297                
1298                if (distributedClaims == null) {
1299                        return;
1300                }
1301                
1302                distributedClaims.mergeInto(claims);
1303        }
1304        
1305        
1306        /**
1307         * Gets the included distributed claims provided by each external
1308         * claims source.
1309         *
1310         * @return The distributed claims, {@code null} if none are found.
1311         */
1312        public Set<DistributedClaims> getDistributedClaims() {
1313                
1314                Map<String,JSONObject> claimSources = ExternalClaimsUtils.getExternalClaimSources(claims);
1315                
1316                if (claimSources == null) {
1317                        return null; // No external _claims_sources
1318                }
1319                
1320                Set<DistributedClaims> distributedClaimsSet = new HashSet<>();
1321                
1322                for (Map.Entry<String,JSONObject> en: claimSources.entrySet()) {
1323                        
1324                        String sourceID = en.getKey();
1325                        JSONObject sourceSpec = en.getValue();
1326        
1327                        Object endpointValue = sourceSpec.get("endpoint");
1328                        if (! (endpointValue instanceof String)) {
1329                                continue; // skip
1330                        }
1331                        
1332                        URI endpoint;
1333                        try {
1334                                endpoint = new URI((String)endpointValue);
1335                        } catch (URISyntaxException e) {
1336                                continue; // invalid URI, skip
1337                        }
1338                        
1339                        AccessToken accessToken = null;
1340                        Object accessTokenValue = sourceSpec.get("access_token");
1341                        if (accessTokenValue instanceof String) {
1342                                accessToken = new TypelessAccessToken((String)accessTokenValue);
1343                        }
1344                        
1345                        Set<String> claimNames = ExternalClaimsUtils.getExternalClaimNamesForSource(claims, sourceID);
1346                        
1347                        if (claimNames.isEmpty()) {
1348                                continue; // skip
1349                        }
1350                        
1351                        distributedClaimsSet.add(new DistributedClaims(sourceID, claimNames, endpoint, accessToken));
1352                }
1353                
1354                if (distributedClaimsSet.isEmpty()) {
1355                        return null;
1356                }
1357                
1358                return distributedClaimsSet;
1359        }
1360        
1361        
1362        /**
1363         * Parses a UserInfo claims set from the specified JSON object string.
1364         *
1365         * @param json The JSON object string to parse. Must not be
1366         *             {@code null}.
1367         *
1368         * @return The UserInfo claims set.
1369         *
1370         * @throws ParseException If parsing failed.
1371         */
1372        public static UserInfo parse(final String json)
1373                throws ParseException {
1374
1375                JSONObject jsonObject = JSONObjectUtils.parse(json);
1376
1377                try {
1378                        return new UserInfo(jsonObject);
1379
1380                } catch (IllegalArgumentException e) {
1381
1382                        throw new ParseException(e.getMessage(), e);
1383                }
1384        }
1385}