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