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.ReadOnlyJWTClaimsSet;
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 ReadOnlyJWTClaimsSet 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 rt The OpenID Connect response type. Must not be
254         *           {@code null}.
255         *
256         * @return {@code true} if the required claims are contained, else
257         *         {@code false}.
258         */
259        public boolean hasRequiredClaims(final ResponseType rt) {
260
261                if (rt.impliesImplicitFlow() && getNonce() == null)
262                        return false;
263
264                if (rt.impliesImplicitFlow() && rt.contains(ResponseType.Value.TOKEN) && getAccessTokenHash() == null)
265                        return false;
266
267                if (rt.impliesCodeFlow() && getCodeHash() == null)
268                        return false;
269
270                return true;
271        }
272
273
274        /**
275         * Gets the ID token issuer. Corresponds to the {@code iss} claim.
276         *
277         * @return The issuer.
278         */
279        public Issuer getIssuer() {
280
281                return new Issuer(getStringClaim(ISS_CLAIM_NAME));
282        }
283
284
285        /**
286         * Gets the ID token subject. Corresponds to the {@code sub} claim.
287         *
288         * @return The subject.
289         */
290        public Subject getSubject() {
291
292                return new Subject(getStringClaim(SUB_CLAIM_NAME));
293        }
294
295
296        /**
297         * Gets the ID token audience. Corresponds to the {@code aud} claim.
298         *
299         * @return The audience.
300         */
301        public List<Audience> getAudience() {
302
303                if (getClaim(AUD_CLAIM_NAME) instanceof String) {
304                        // Special case - aud is a string
305                        return new Audience(getStringClaim(AUD_CLAIM_NAME)).toSingleAudienceList();
306                }
307
308                // General case - JSON string array
309                List<String> rawList = getStringListClaim(AUD_CLAIM_NAME);
310
311                List<Audience> audList = new ArrayList<>(rawList.size());
312
313                for (String s: rawList)
314                        audList.add(new Audience(s));
315
316                return audList;
317        }
318
319
320        /**
321         * Gets the ID token expiration time. Corresponds to the {@code exp}
322         * claim.
323         *
324         * @return The expiration time.
325         */
326        public Date getExpirationTime() {
327
328                return getDateClaim(EXP_CLAIM_NAME);
329        }
330
331
332        /**
333         * Gets the ID token issue time. Corresponds to the {@code iss} claim.
334         *
335         * @return The issue time.
336         */
337        public Date getIssueTime() {
338
339                return getDateClaim(IAT_CLAIM_NAME);
340        }
341
342
343        /**
344         * Gets the subject authentication time. Corresponds to the
345         * {@code auth_time} claim.
346         *
347         * @return The authentication time, {@code null} if not specified or
348         *         parsing failed.
349         */
350        public Date getAuthenticationTime() {
351
352                return getDateClaim(AUTH_TIME_CLAIM_NAME);
353        }
354
355
356        /**
357         * Sets the subject authentication time. Corresponds to the
358         * {@code auth_time} claim.
359         *
360         * @param authTime The authentication time, {@code null} if not
361         *                 specified.
362         */
363        public void setAuthenticationTime(final Date authTime) {
364
365                setDateClaim(AUTH_TIME_CLAIM_NAME, authTime);
366        }
367
368
369        /**
370         * Gets the ID token nonce. Corresponds to the {@code nonce} claim.
371         *
372         * @return The nonce, {@code null} if not specified or parsing failed.
373         */
374        public Nonce getNonce() {
375
376                String value = getStringClaim(NONCE_CLAIM_NAME);
377                return value != null ? new Nonce(value) : null;
378        }
379
380
381        /**
382         * Sets the ID token nonce. Corresponds to the {@code nonce} claim.
383         *
384         * @param nonce The nonce, {@code null} if not specified.
385         */
386        public void setNonce(final Nonce nonce) {
387
388                if (nonce != null)
389                        setClaim(NONCE_CLAIM_NAME, nonce.getValue());
390                else
391                        setClaim(NONCE_CLAIM_NAME, null);
392        }
393
394
395        /**
396         * Gets the access token hash. Corresponds to the {@code at_hash}
397         * claim.
398         *
399         * @return The access token hash, {@code null} if not specified or
400         *         parsing failed.
401         */
402        public AccessTokenHash getAccessTokenHash() {
403
404                String value = getStringClaim(AT_HASH_CLAIM_NAME);
405                return value != null ? new AccessTokenHash(value) : null;
406        }
407
408
409        /**
410         * Sets the access token hash. Corresponds to the {@code at_hash}
411         * claim.
412         *
413         * @param atHash The access token hash, {@code null} if not specified.
414         */
415        public void setAccessTokenHash(final AccessTokenHash atHash) {
416
417                if (atHash != null)
418                        setClaim(AT_HASH_CLAIM_NAME, atHash.getValue());
419                else
420                        setClaim(AT_HASH_CLAIM_NAME, null);
421        }
422
423
424        /**
425         * Gets the authorisation code hash. Corresponds to the {@code c_hash}
426         * claim.
427         *
428         * @return The authorisation code hash, {@code null} if not specified
429         *         or parsing failed.
430         */
431        public CodeHash getCodeHash() {
432
433                String value = getStringClaim(C_HASH_CLAIM_NAME);
434                return value != null ? new CodeHash(value) : null;
435        }
436
437
438        /**
439         * Sets the authorisation code hash. Corresponds to the {@code c_hash}
440         * claim.
441         *
442         * @param cHash The authorisation code hash, {@code null} if not
443         *              specified.
444         */
445        public void setCodeHash(final CodeHash cHash) {
446
447                if (cHash != null)
448                        setClaim(C_HASH_CLAIM_NAME, cHash.getValue());
449                else
450                        setClaim(C_HASH_CLAIM_NAME, null);
451        }
452
453
454        /**
455         * Gets the Authentication Context Class Reference (ACR). Corresponds
456         * to the {@code acr} claim.
457         *
458         * @return The Authentication Context Class Reference (ACR),
459         *         {@code null} if not specified or parsing failed.
460         */
461        public ACR getACR() {
462
463                String value = getStringClaim(ACR_CLAIM_NAME);
464                return value != null ? new ACR(value) : null;
465        }
466
467
468        /**
469         * Sets the Authentication Context Class Reference (ACR). Corresponds
470         * to the {@code acr} claim.
471         *
472         * @param acr The Authentication Context Class Reference (ACR),
473         *            {@code null} if not specified.
474         */
475        public void setACR(final ACR acr) {
476
477                if (acr != null)
478                        setClaim(ACR_CLAIM_NAME, acr.getValue());
479                else
480                        setClaim(ACR_CLAIM_NAME, null);
481        }
482
483
484        /**
485         * Gets the Authentication Methods References (AMRs). Corresponds to
486         * the {@code amr} claim.
487         *
488         * @return The Authentication Methods Reference (AMR) list,
489         *         {@code null} if not specified or parsing failed.
490         */
491        public List<AMR> getAMR() {
492
493                List<String> rawList = getStringListClaim(AMR_CLAIM_NAME);
494
495                if (rawList == null || rawList.isEmpty())
496                        return null;
497
498                List<AMR> amrList = new ArrayList<>(rawList.size());
499
500                for (String s: rawList)
501                        amrList.add(new AMR(s));
502
503                return amrList;
504        }
505
506
507        /**
508         * Sets the Authentication Methods References (AMRs). Corresponds to
509         * the {@code amr} claim.
510         *
511         * @param amr The Authentication Methods Reference (AMR) list,
512         *            {@code null} if not specified.
513         */
514        public void setAMR(final List<AMR> amr) {
515
516                if (amr != null) {
517
518                        List<String> amrList = new ArrayList<>(amr.size());
519
520                        for (AMR a: amr)
521                                amrList.add(a.getValue());
522
523                        setClaim(AMR_CLAIM_NAME, amrList);
524
525                } else {
526                        setClaim(AMR_CLAIM_NAME, null);
527                }
528        }
529
530
531        /**
532         * Gets the authorised party for the ID token. Corresponds to the
533         * {@code azp} claim.
534         *
535         * @return The authorised party, {@code null} if not specified or
536         *         parsing failed.
537         */
538        public AuthorizedParty getAuthorizedParty() {
539
540                String value = getStringClaim(AZP_CLAIM_NAME);
541                return value != null ? new AuthorizedParty(value) : null;
542        }
543
544
545        /**
546         * Sets the authorised party for the ID token. Corresponds to the
547         * {@code azp} claim.
548         *
549         * @param azp The authorised party, {@code null} if not specified.
550         */
551        public void setAuthorizedParty(final AuthorizedParty azp) {
552
553                if (azp != null)
554                        setClaim(AZP_CLAIM_NAME, azp.getValue());
555                else
556                        setClaim(AZP_CLAIM_NAME, null);
557        }
558
559
560        /**
561         * Gets the subject's JSON Web Key (JWK) for a self-issued OpenID
562         * Connect provider. Corresponds to the {@code sub_jwk} claim.
563         *
564         * @return The subject's JWK, {@code null} if not specified or parsing
565         *         failed.
566         */
567        public JWK getSubjectJWK() {
568
569                JSONObject jsonObject = getClaim(SUB_JWK_CLAIM_NAME, JSONObject.class);
570
571                if (jsonObject == null)
572                        return null;
573
574                try {
575                        return JWK.parse(jsonObject);
576
577                } catch (java.text.ParseException e) {
578
579                        return null;
580                }
581        }
582
583
584        /**
585         * Sets the subject's JSON Web Key (JWK) for a self-issued OpenID
586         * Connect provider. Corresponds to the {@code sub_jwk} claim.
587         *
588         * @param subJWK The subject's JWK (must be public), {@code null} if
589         *               not specified.
590         */
591        public void setSubjectJWK(final JWK subJWK) {
592
593                if (subJWK != null) {
594
595                        if (subJWK.isPrivate())
596                                throw new IllegalArgumentException("The subject's JSON Web Key (JWK) must be public");
597
598                        setClaim(SUB_JWK_CLAIM_NAME, subJWK.toJSONObject());
599
600                } else {
601                        setClaim(SUB_JWK_CLAIM_NAME, null);
602                }
603        }
604
605
606        /**
607         * Parses an ID token claims set from the specified JSON object string.
608         *
609         * @param json The JSON object string to parse. Must not be
610         *             {@code null}.
611         *
612         * @return The ID token claims set.
613         *
614         * @throws ParseException If parsing failed.
615         */
616        public static IDTokenClaimsSet parse(final String json)
617                throws ParseException {
618
619                JSONObject jsonObject = JSONObjectUtils.parseJSONObject(json);
620
621                try {
622                        return new IDTokenClaimsSet(jsonObject);
623
624                } catch (IllegalArgumentException e) {
625
626                        throw new ParseException(e.getMessage(), e);
627                }
628        }
629}