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