001package com.nimbusds.openid.connect.sdk.claims;
002
003
004import java.util.*;
005
006import net.minidev.json.JSONArray;
007import net.minidev.json.JSONObject;
008
009import com.nimbusds.jose.jwk.JWK;
010import com.nimbusds.jwt.JWTClaimsSet;
011
012import com.nimbusds.oauth2.sdk.ParseException;
013import com.nimbusds.oauth2.sdk.ResponseType;
014import com.nimbusds.oauth2.sdk.id.Audience;
015import com.nimbusds.oauth2.sdk.id.Issuer;
016import com.nimbusds.oauth2.sdk.id.Subject;
017import com.nimbusds.oauth2.sdk.util.JSONObjectUtils;
018
019import com.nimbusds.openid.connect.sdk.Nonce;
020
021
022/**
023 * ID token claims set, serialisable to a JSON object.
024 *
025 * <p>Example ID token claims set:
026 *
027 * <pre>
028 * {
029 *   "iss"       : "https://server.example.com",
030 *   "sub"       : "24400320",
031 *   "aud"       : "s6BhdRkqt3",
032 *   "nonce"     : "n-0S6_WzA2Mj",
033 *   "exp"       : 1311281970,
034 *   "iat"       : 1311280970,
035 *   "auth_time" : 1311280969,
036 *   "acr"       : "urn:mace:incommon:iap:silver",
037 *   "at_hash"   : "MTIzNDU2Nzg5MDEyMzQ1Ng"
038 * }
039 * </pre>
040 *
041 * <p>Related specifications:
042 *
043 * <ul>
044 *     <li>OpenID Connect Core 1.0, section 2.
045 * </ul>
046 */
047public class IDTokenClaimsSet extends ClaimsSet {
048
049
050        /**
051         * The issuer claim name.
052         */
053        public static final String ISS_CLAIM_NAME = "iss";
054
055
056        /**
057         * The subject claim name.
058         */
059        public static final String SUB_CLAIM_NAME = "sub";
060
061
062        /**
063         * The audience claim name.
064         */
065        public static final String AUD_CLAIM_NAME = "aud";
066
067
068        /**
069         * The expiration time claim name.
070         */
071        public static final String EXP_CLAIM_NAME = "exp";
072
073
074        /**
075         * The issue time claim name.
076         */
077        public static final String IAT_CLAIM_NAME = "iat";
078
079
080        /**
081         * The subject authentication time claim name.
082         */
083        public static final String AUTH_TIME_CLAIM_NAME = "auth_time";
084
085
086        /**
087         * The nonce claim name.
088         */
089        public static final String NONCE_CLAIM_NAME = "nonce";
090
091
092        /**
093         * The access token hash claim name.
094         */
095        public static final String AT_HASH_CLAIM_NAME = "at_hash";
096
097
098        /**
099         * The authorisation code hash claim name.
100         */
101        public static final String C_HASH_CLAIM_NAME = "c_hash";
102
103
104        /**
105         * The ACR claim name.
106         */
107        public static final String ACR_CLAIM_NAME = "acr";
108
109
110        /**
111         * The AMRs claim name.
112         */
113        public static final String AMR_CLAIM_NAME = "amr";
114
115
116        /**
117         * The authorised party claim name.
118         */
119        public static final String AZP_CLAIM_NAME = "azp";
120
121
122        /**
123         * The subject JWK claim name.
124         */
125        public static final String SUB_JWK_CLAIM_NAME = "sub_jwk";
126
127
128        /**
129         * The names of the standard top-level ID token claims.
130         */
131        private static final Set<String> stdClaimNames = new LinkedHashSet<>();
132
133
134        static {
135                stdClaimNames.add(ISS_CLAIM_NAME);
136                stdClaimNames.add(SUB_CLAIM_NAME);
137                stdClaimNames.add(AUD_CLAIM_NAME);
138                stdClaimNames.add(EXP_CLAIM_NAME);
139                stdClaimNames.add(IAT_CLAIM_NAME);
140                stdClaimNames.add(AUTH_TIME_CLAIM_NAME);
141                stdClaimNames.add(NONCE_CLAIM_NAME);
142                stdClaimNames.add(AT_HASH_CLAIM_NAME);
143                stdClaimNames.add(C_HASH_CLAIM_NAME);
144                stdClaimNames.add(ACR_CLAIM_NAME);
145                stdClaimNames.add(AMR_CLAIM_NAME);
146                stdClaimNames.add(AZP_CLAIM_NAME);
147                stdClaimNames.add(SUB_JWK_CLAIM_NAME);
148        }
149
150
151        /**
152         * Gets the names of the standard top-level ID token claims.
153         *
154         * @return The names of the standard top-level ID token claims
155         *         (read-only set).
156         */
157        public static Set<String> getStandardClaimNames() {
158
159                return Collections.unmodifiableSet(stdClaimNames);
160        }
161
162
163        /**
164         * Creates a new minimal ID token claims set. Note that the ID token
165         * may require additional claims to be present depending on the
166         * original OpenID Connect authorisation request.
167         *
168         * @param iss The issuer. Must not be {@code null}.
169         * @param sub The subject. Must not be {@code null}.
170         * @param aud The audience. Must not be {@code null}.
171         * @param exp The expiration time. Must not be {@code null}.
172         * @param iat The issue time. Must not be {@code null}.
173         */
174        public IDTokenClaimsSet(final Issuer iss,
175                                final Subject sub,
176                                final List<Audience> aud,
177                                final Date exp,
178                                final Date iat) {
179
180                setClaim(ISS_CLAIM_NAME, iss.getValue());
181                setClaim(SUB_CLAIM_NAME, sub.getValue());
182
183                JSONArray audList = new JSONArray();
184
185                for (Audience a: aud)
186                        audList.add(a.getValue());
187
188                setClaim(AUD_CLAIM_NAME, audList);
189
190                setDateClaim(EXP_CLAIM_NAME, exp);
191                setDateClaim(IAT_CLAIM_NAME, iat);
192        }
193
194
195        /**
196         * Creates a new ID token claims set from the specified JSON object.
197         *
198         * @param jsonObject The JSON object. Must be verified to represent a
199         *                   valid ID token claims set and not {@code null}.
200         *
201         * @throws ParseException If the JSON object doesn't contain the
202         *                        minimally required issuer {@code iss},
203         *                        subject {@code sub}, audience list
204         *                        {@code aud}, expiration date {@code exp} and
205         *                        issue date {@code iat} claims.
206         */
207        private IDTokenClaimsSet(final JSONObject jsonObject)
208                throws ParseException {
209
210                super(jsonObject);
211
212                if (getStringClaim(ISS_CLAIM_NAME) == null)
213                        throw new ParseException("Missing or invalid \"iss\" claim");
214
215                if (getStringClaim(SUB_CLAIM_NAME) == null)
216                        throw new ParseException("Missing or invalid \"sub\" claim");
217
218                if (getStringClaim(AUD_CLAIM_NAME) == null && getStringListClaim(AUD_CLAIM_NAME) == null ||
219                    getStringListClaim(AUD_CLAIM_NAME) != null && getStringListClaim(AUD_CLAIM_NAME).isEmpty())
220                        throw new ParseException("Missing or invalid \"aud\" claim");
221
222                if (getDateClaim(EXP_CLAIM_NAME) == null)
223                        throw new ParseException("Missing or invalid \"exp\" claim");
224
225                if (getDateClaim(IAT_CLAIM_NAME) == null)
226                        throw new ParseException("Missing or invalid \"iat\" claim");
227        }
228
229
230        /**
231         * Creates a new ID token claims set from the specified JSON Web Token
232         * (JWT) claims set.
233         *
234         * @param jwtClaimsSet The JWT claims set. Must not be {@code null}.
235         *
236         * @throws ParseException If the JSON object doesn't contain the
237         *                        minimally required issuer {@code iss},
238         *                        subject {@code sub}, audience list
239         *                        {@code aud}, expiration date {@code exp} and
240         *                        issue date {@code iat} claims.
241         */
242        public IDTokenClaimsSet(final JWTClaimsSet jwtClaimsSet)
243                throws ParseException {
244
245                this(jwtClaimsSet.toJSONObject());
246        }
247
248
249        /**
250         * Checks if this ID token claims set contains all required claims for
251         * the specified OpenID Connect response type.
252         *
253         * @param responseType     The OpenID Connect response type. Must not
254         *                         be {@code null}.
255         * @param iatAuthzEndpoint Specifies the endpoint where the ID token
256         *                         was issued (required for hybrid flow).
257         *                         {@code true} if the ID token was issued at
258         *                         the authorisation endpoint, {@code false} if
259         *                         the ID token was issued at the token
260         *                         endpoint.
261         *
262         * @return {@code true} if the required claims are contained, else
263         *         {@code false}.
264         */
265        public boolean hasRequiredClaims(final ResponseType responseType, final boolean iatAuthzEndpoint) {
266
267                // Code flow
268                // See http://openid.net/specs/openid-connect-core-1_0.html#CodeIDToken
269                if (new ResponseType("code").equals(responseType)) {
270                        // nonce, c_hash and at_hash not required
271                        return true; // ok
272                }
273
274                // Implicit flow
275                // See http://openid.net/specs/openid-connect-core-1_0.html#ImplicitIDToken
276                if (new ResponseType("id_token").equals(responseType)) {
277
278                        return getNonce() != null;
279
280                }
281
282                if (new ResponseType("id_token", "token").equals(responseType)) {
283
284                        if (getNonce() == null) {
285                                // nonce required
286                                return false;
287                        }
288
289                        return getAccessTokenHash() != null;
290
291                }
292
293                // Hybrid flow
294                // See http://openid.net/specs/openid-connect-core-1_0.html#HybridIDToken
295                if (new ResponseType("code", "id_token").equals(responseType)) {
296
297                        if (getNonce() == null) {
298                                // nonce required
299                                return false;
300                        }
301
302                        if (! iatAuthzEndpoint) {
303                                // c_hash and at_hash not required when id_token issued at token endpoint
304                                // See http://openid.net/specs/openid-connect-core-1_0.html#HybridIDToken2
305                                return true;
306                        }
307
308                        return getCodeHash() != null;
309
310                }
311
312                if (new ResponseType("code", "token").equals(responseType)) {
313
314                        if (getNonce() == null) {
315                                // nonce required
316                                return false;
317                        }
318
319                        if (! iatAuthzEndpoint) {
320                                // c_hash and at_hash not required when id_token issued at token endpoint
321                                // See http://openid.net/specs/openid-connect-core-1_0.html#HybridIDToken2
322                                return true;
323                        }
324
325                        return true; // ok
326                }
327
328                if (new ResponseType("code", "id_token", "token").equals(responseType)) {
329
330                        if (getNonce() == null) {
331                                // nonce required
332                                return false;
333                        }
334
335                        if (! iatAuthzEndpoint) {
336                                // c_hash and at_hash not required when id_token issued at token endpoint
337                                // See http://openid.net/specs/openid-connect-core-1_0.html#HybridIDToken2
338                                return true;
339                        }
340
341                        if (getAccessTokenHash() == null) {
342                                // at_hash required when issued at authz endpoint
343                                return false;
344                        }
345
346                        return getCodeHash() != null;
347
348                }
349
350                throw new IllegalArgumentException("Unsupported response_type: " + responseType);
351        }
352
353
354        /**
355         * Use {@link #hasRequiredClaims(ResponseType, boolean)} instead.
356         *
357         * @param responseType The OpenID Connect response type. Must not be
358         *                     {@code null}.
359         *
360         * @return {@code true} if the required claims are contained, else
361         *         {@code false}.
362         */
363        @Deprecated
364        public boolean hasRequiredClaims(final ResponseType responseType) {
365
366                return hasRequiredClaims(responseType, true);
367        }
368
369
370        /**
371         * Gets the ID token issuer. Corresponds to the {@code iss} claim.
372         *
373         * @return The issuer.
374         */
375        public Issuer getIssuer() {
376
377                return new Issuer(getStringClaim(ISS_CLAIM_NAME));
378        }
379
380
381        /**
382         * Gets the ID token subject. Corresponds to the {@code sub} claim.
383         *
384         * @return The subject.
385         */
386        public Subject getSubject() {
387
388                return new Subject(getStringClaim(SUB_CLAIM_NAME));
389        }
390
391
392        /**
393         * Gets the ID token audience. Corresponds to the {@code aud} claim.
394         *
395         * @return The audience.
396         */
397        public List<Audience> getAudience() {
398
399                if (getClaim(AUD_CLAIM_NAME) instanceof String) {
400                        // Special case - aud is a string
401                        return new Audience(getStringClaim(AUD_CLAIM_NAME)).toSingleAudienceList();
402                }
403
404                // General case - JSON string array
405                List<String> rawList = getStringListClaim(AUD_CLAIM_NAME);
406
407                List<Audience> audList = new ArrayList<>(rawList.size());
408
409                for (String s: rawList)
410                        audList.add(new Audience(s));
411
412                return audList;
413        }
414
415
416        /**
417         * Gets the ID token expiration time. Corresponds to the {@code exp}
418         * claim.
419         *
420         * @return The expiration time.
421         */
422        public Date getExpirationTime() {
423
424                return getDateClaim(EXP_CLAIM_NAME);
425        }
426
427
428        /**
429         * Gets the ID token issue time. Corresponds to the {@code iss} claim.
430         *
431         * @return The issue time.
432         */
433        public Date getIssueTime() {
434
435                return getDateClaim(IAT_CLAIM_NAME);
436        }
437
438
439        /**
440         * Gets the subject authentication time. Corresponds to the
441         * {@code auth_time} claim.
442         *
443         * @return The authentication time, {@code null} if not specified or
444         *         parsing failed.
445         */
446        public Date getAuthenticationTime() {
447
448                return getDateClaim(AUTH_TIME_CLAIM_NAME);
449        }
450
451
452        /**
453         * Sets the subject authentication time. Corresponds to the
454         * {@code auth_time} claim.
455         *
456         * @param authTime The authentication time, {@code null} if not
457         *                 specified.
458         */
459        public void setAuthenticationTime(final Date authTime) {
460
461                setDateClaim(AUTH_TIME_CLAIM_NAME, authTime);
462        }
463
464
465        /**
466         * Gets the ID token nonce. Corresponds to the {@code nonce} claim.
467         *
468         * @return The nonce, {@code null} if not specified or parsing failed.
469         */
470        public Nonce getNonce() {
471
472                String value = getStringClaim(NONCE_CLAIM_NAME);
473                return value != null ? new Nonce(value) : null;
474        }
475
476
477        /**
478         * Sets the ID token nonce. Corresponds to the {@code nonce} claim.
479         *
480         * @param nonce The nonce, {@code null} if not specified.
481         */
482        public void setNonce(final Nonce nonce) {
483
484                if (nonce != null)
485                        setClaim(NONCE_CLAIM_NAME, nonce.getValue());
486                else
487                        setClaim(NONCE_CLAIM_NAME, null);
488        }
489
490
491        /**
492         * Gets the access token hash. Corresponds to the {@code at_hash}
493         * claim.
494         *
495         * @return The access token hash, {@code null} if not specified or
496         *         parsing failed.
497         */
498        public AccessTokenHash getAccessTokenHash() {
499
500                String value = getStringClaim(AT_HASH_CLAIM_NAME);
501                return value != null ? new AccessTokenHash(value) : null;
502        }
503
504
505        /**
506         * Sets the access token hash. Corresponds to the {@code at_hash}
507         * claim.
508         *
509         * @param atHash The access token hash, {@code null} if not specified.
510         */
511        public void setAccessTokenHash(final AccessTokenHash atHash) {
512
513                if (atHash != null)
514                        setClaim(AT_HASH_CLAIM_NAME, atHash.getValue());
515                else
516                        setClaim(AT_HASH_CLAIM_NAME, null);
517        }
518
519
520        /**
521         * Gets the authorisation code hash. Corresponds to the {@code c_hash}
522         * claim.
523         *
524         * @return The authorisation code hash, {@code null} if not specified
525         *         or parsing failed.
526         */
527        public CodeHash getCodeHash() {
528
529                String value = getStringClaim(C_HASH_CLAIM_NAME);
530                return value != null ? new CodeHash(value) : null;
531        }
532
533
534        /**
535         * Sets the authorisation code hash. Corresponds to the {@code c_hash}
536         * claim.
537         *
538         * @param cHash The authorisation code hash, {@code null} if not
539         *              specified.
540         */
541        public void setCodeHash(final CodeHash cHash) {
542
543                if (cHash != null)
544                        setClaim(C_HASH_CLAIM_NAME, cHash.getValue());
545                else
546                        setClaim(C_HASH_CLAIM_NAME, null);
547        }
548
549
550        /**
551         * Gets the Authentication Context Class Reference (ACR). Corresponds
552         * to the {@code acr} claim.
553         *
554         * @return The Authentication Context Class Reference (ACR),
555         *         {@code null} if not specified or parsing failed.
556         */
557        public ACR getACR() {
558
559                String value = getStringClaim(ACR_CLAIM_NAME);
560                return value != null ? new ACR(value) : null;
561        }
562
563
564        /**
565         * Sets the Authentication Context Class Reference (ACR). Corresponds
566         * to the {@code acr} claim.
567         *
568         * @param acr The Authentication Context Class Reference (ACR),
569         *            {@code null} if not specified.
570         */
571        public void setACR(final ACR acr) {
572
573                if (acr != null)
574                        setClaim(ACR_CLAIM_NAME, acr.getValue());
575                else
576                        setClaim(ACR_CLAIM_NAME, null);
577        }
578
579
580        /**
581         * Gets the Authentication Methods References (AMRs). Corresponds to
582         * the {@code amr} claim.
583         *
584         * @return The Authentication Methods Reference (AMR) list,
585         *         {@code null} if not specified or parsing failed.
586         */
587        public List<AMR> getAMR() {
588
589                List<String> rawList = getStringListClaim(AMR_CLAIM_NAME);
590
591                if (rawList == null || rawList.isEmpty())
592                        return null;
593
594                List<AMR> amrList = new ArrayList<>(rawList.size());
595
596                for (String s: rawList)
597                        amrList.add(new AMR(s));
598
599                return amrList;
600        }
601
602
603        /**
604         * Sets the Authentication Methods References (AMRs). Corresponds to
605         * the {@code amr} claim.
606         *
607         * @param amr The Authentication Methods Reference (AMR) list,
608         *            {@code null} if not specified.
609         */
610        public void setAMR(final List<AMR> amr) {
611
612                if (amr != null) {
613
614                        List<String> amrList = new ArrayList<>(amr.size());
615
616                        for (AMR a: amr)
617                                amrList.add(a.getValue());
618
619                        setClaim(AMR_CLAIM_NAME, amrList);
620
621                } else {
622                        setClaim(AMR_CLAIM_NAME, null);
623                }
624        }
625
626
627        /**
628         * Gets the authorised party for the ID token. Corresponds to the
629         * {@code azp} claim.
630         *
631         * @return The authorised party, {@code null} if not specified or
632         *         parsing failed.
633         */
634        public AuthorizedParty getAuthorizedParty() {
635
636                String value = getStringClaim(AZP_CLAIM_NAME);
637                return value != null ? new AuthorizedParty(value) : null;
638        }
639
640
641        /**
642         * Sets the authorised party for the ID token. Corresponds to the
643         * {@code azp} claim.
644         *
645         * @param azp The authorised party, {@code null} if not specified.
646         */
647        public void setAuthorizedParty(final AuthorizedParty azp) {
648
649                if (azp != null)
650                        setClaim(AZP_CLAIM_NAME, azp.getValue());
651                else
652                        setClaim(AZP_CLAIM_NAME, null);
653        }
654
655
656        /**
657         * Gets the subject's JSON Web Key (JWK) for a self-issued OpenID
658         * Connect provider. Corresponds to the {@code sub_jwk} claim.
659         *
660         * @return The subject's JWK, {@code null} if not specified or parsing
661         *         failed.
662         */
663        public JWK getSubjectJWK() {
664
665                JSONObject jsonObject = getClaim(SUB_JWK_CLAIM_NAME, JSONObject.class);
666
667                if (jsonObject == null)
668                        return null;
669
670                try {
671                        return JWK.parse(jsonObject);
672
673                } catch (java.text.ParseException e) {
674
675                        return null;
676                }
677        }
678
679
680        /**
681         * Sets the subject's JSON Web Key (JWK) for a self-issued OpenID
682         * Connect provider. Corresponds to the {@code sub_jwk} claim.
683         *
684         * @param subJWK The subject's JWK (must be public), {@code null} if
685         *               not specified.
686         */
687        public void setSubjectJWK(final JWK subJWK) {
688
689                if (subJWK != null) {
690
691                        if (subJWK.isPrivate())
692                                throw new IllegalArgumentException("The subject's JSON Web Key (JWK) must be public");
693
694                        setClaim(SUB_JWK_CLAIM_NAME, subJWK.toJSONObject());
695
696                } else {
697                        setClaim(SUB_JWK_CLAIM_NAME, null);
698                }
699        }
700
701
702        /**
703         * Parses an ID token claims set from the specified JSON object string.
704         *
705         * @param json The JSON object string to parse. Must not be
706         *             {@code null}.
707         *
708         * @return The ID token claims set.
709         *
710         * @throws ParseException If parsing failed.
711         */
712        public static IDTokenClaimsSet parse(final String json)
713                throws ParseException {
714
715                JSONObject jsonObject = JSONObjectUtils.parse(json);
716
717                try {
718                        return new IDTokenClaimsSet(jsonObject);
719
720                } catch (IllegalArgumentException e) {
721
722                        throw new ParseException(e.getMessage(), e);
723                }
724        }
725}