001package com.nimbusds.openid.connect.sdk.claims;
002
003
004import java.util.*;
005
006import net.minidev.json.JSONObject;
007
008import com.nimbusds.jose.jwk.JWK;
009import com.nimbusds.jwt.JWTClaimsSet;
010
011import com.nimbusds.oauth2.sdk.ParseException;
012import com.nimbusds.oauth2.sdk.ResponseType;
013import com.nimbusds.oauth2.sdk.id.Audience;
014import com.nimbusds.oauth2.sdk.id.Issuer;
015import com.nimbusds.oauth2.sdk.id.Subject;
016import com.nimbusds.oauth2.sdk.util.JSONObjectUtils;
017
018import com.nimbusds.openid.connect.sdk.Nonce;
019
020
021/**
022 * ID token claims set, serialisable to a JSON object.
023 *
024 * <p>Example ID token claims set:
025 *
026 * <pre>
027 * {
028 *   "iss"       : "https://server.example.com",
029 *   "sub"       : "24400320",
030 *   "aud"       : "s6BhdRkqt3",
031 *   "nonce"     : "n-0S6_WzA2Mj",
032 *   "exp"       : 1311281970,
033 *   "iat"       : 1311280970,
034 *   "auth_time" : 1311280969,
035 *   "acr"       : "urn:mace:incommon:iap:silver",
036 *   "at_hash"   : "MTIzNDU2Nzg5MDEyMzQ1Ng"
037 * }
038 * </pre>
039 *
040 * <p>Related specifications:
041 *
042 * <ul>
043 *     <li>OpenID Connect Messages 1.0, section 2.1.2.1.
044 * </ul>
045 *
046 * @author Vladimir Dzhuvinov
047 */
048public class IDTokenClaimsSet extends ClaimsSet {
049
050
051        /**
052         * The issuer claim name.
053         */
054        public static final String ISS_CLAIM_NAME = "iss";
055
056
057        /**
058         * The subject claim name.
059         */
060        public static final String SUB_CLAIM_NAME = "sub";
061
062
063        /**
064         * The audience claim name.
065         */
066        public static final String AUD_CLAIM_NAME = "aud";
067
068
069        /**
070         * The expiration time claim name.
071         */
072        public static final String EXP_CLAIM_NAME = "exp";
073
074
075        /**
076         * The issue time claim name.
077         */
078        public static final String IAT_CLAIM_NAME = "iat";
079
080
081        /**
082         * The subject authentication time claim name.
083         */
084        public static final String AUTH_TIME_CLAIM_NAME = "auth_time";
085
086
087        /**
088         * The nonce claim name.
089         */
090        public static final String NONCE_CLAIM_NAME = "nonce";
091
092
093        /**
094         * The access token hash claim name.
095         */
096        public static final String AT_HASH_CLAIM_NAME = "at_hash";
097
098
099        /**
100         * The authorisation code hash claim name.
101         */
102        public static final String C_HASH_CLAIM_NAME = "c_hash";
103
104
105        /**
106         * The ACR claim name.
107         */
108        public static final String ACR_CLAIM_NAME = "acr";
109
110
111        /**
112         * The AMRs claim name.
113         */
114        public static final String AMR_CLAIM_NAME = "amr";
115
116
117        /**
118         * The authorised party claim name.
119         */
120        public static final String AZP_CLAIM_NAME = "azp";
121
122
123        /**
124         * The subject JWK claim name.
125         */
126        public static final String SUB_JWK_CLAIM_NAME = "sub_jwk";
127
128
129        /**
130         * The names of the standard top-level ID token claims.
131         */
132        private static final Set<String> stdClaimNames = new LinkedHashSet<String>();
133
134
135        static {
136                stdClaimNames.add(ISS_CLAIM_NAME);
137                stdClaimNames.add(SUB_CLAIM_NAME);
138                stdClaimNames.add(AUD_CLAIM_NAME);
139                stdClaimNames.add(EXP_CLAIM_NAME);
140                stdClaimNames.add(IAT_CLAIM_NAME);
141                stdClaimNames.add(AUTH_TIME_CLAIM_NAME);
142                stdClaimNames.add(NONCE_CLAIM_NAME);
143                stdClaimNames.add(AT_HASH_CLAIM_NAME);
144                stdClaimNames.add(C_HASH_CLAIM_NAME);
145                stdClaimNames.add(ACR_CLAIM_NAME);
146                stdClaimNames.add(AMR_CLAIM_NAME);
147                stdClaimNames.add(AZP_CLAIM_NAME);
148                stdClaimNames.add(SUB_JWK_CLAIM_NAME);
149        }
150
151
152        /**
153         * Gets the names of the standard top-level ID token claims.
154         *
155         * @return The names of the standard top-level ID token claims
156         *         (read-only set).
157         */
158        public static Set<String> getStandardClaimNames() {
159
160                return Collections.unmodifiableSet(stdClaimNames);
161        }
162
163
164        /**
165         * Creates a new minimal ID token claims set. Note that the ID token
166         * may require additional claims to be present depending on the
167         * original OpenID Connect authorisation request.
168         *
169         * @param iss The issuer. Must not be {@code null}.
170         * @param sub The subject. Must not be {@code null}.
171         * @param aud The audience. Must not be {@code null}.
172         * @param exp The expiration time. Must not be {@code null}.
173         * @param iat The issue time. Must not be {@code null}.
174         */
175        public IDTokenClaimsSet(final Issuer iss,
176                                final Subject sub,
177                                final List<Audience> aud,
178                                final Date exp,
179                                final Date iat) {
180
181                setClaim(ISS_CLAIM_NAME, iss.getValue());
182                setClaim(SUB_CLAIM_NAME, sub.getValue());
183
184                List<String> audList = new ArrayList<String>(aud.size());
185
186                for (Audience a: aud)
187                        audList.add(a.getValue());
188
189                setClaim(AUD_CLAIM_NAME, audList);
190
191                setDateClaim(EXP_CLAIM_NAME, exp);
192                setDateClaim(IAT_CLAIM_NAME, iat);
193        }
194
195
196        /**
197         * Creates a new ID token claims set from the specified JSON object.
198         *
199         * @param jsonObject The JSON object. Must not be {@code null}.
200         *
201         * @throws IllegalArgumentException If the JSON object doesn't contain
202         *                                  the minimally required issuer
203         *                                  {@code iss}, subject {@code sub},
204         *                                  audience list {@code aud},
205         *                                  expiration date {@code exp} and
206         *                                  issue date {@code iat} claims.
207         */
208        public IDTokenClaimsSet(final JSONObject jsonObject) {
209
210                super(jsonObject);
211
212                if (getStringClaim(ISS_CLAIM_NAME) == null)
213                        throw new IllegalArgumentException("Missing or invalid \"iss\" claim");
214
215                if (getStringClaim(SUB_CLAIM_NAME) == null)
216                        throw new IllegalArgumentException("Missing or invalid \"sub\" claim");
217
218                if (getStringListClaim(AUD_CLAIM_NAME) == null)
219                        throw new IllegalArgumentException("Missing or invalid \"aud\" claim");
220
221                if (getDateClaim(EXP_CLAIM_NAME) == null)
222                        throw new IllegalArgumentException("Missing or invalid \"exp\" claim");
223
224                if (getDateClaim(IAT_CLAIM_NAME) == null)
225                        throw new IllegalArgumentException("Missing or invalid \"iat\" claim");
226        }
227
228
229        /**
230         * Creates a new ID token claims set from the specified JSON Web Token
231         * (JWT) claims set.
232         *
233         * @param jwtClaimsSet The JWT claims set. Must not be {@code null}.
234         *
235         * @throws IllegalArgumentException If the JWT claims set doesn't
236         *                                  contain the minimally required
237         *                                  issuer {@code iss}, subject
238         *                                  {@code sub}, audience list
239         *                                  {@code aud}, expiration date
240         *                                  {@code exp} and issue date
241         *                                  {@code iat} claims.
242         */
243        public IDTokenClaimsSet(final JWTClaimsSet jwtClaimsSet) {
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.impliesImplicitFlow() && rt.contains(ResponseType.Value.CODE) && 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                List<String> rawList = getStringListClaim(AUD_CLAIM_NAME);
304
305                if (rawList == null || rawList.isEmpty())
306                        return null;
307
308                List<Audience> audList = new ArrayList<Audience>(rawList.size());
309
310                for (String s: rawList)
311                        audList.add(new Audience(s));
312
313                return audList;
314        }
315
316
317        /**
318         * Gets the ID token expiration time. Corresponds to the {@code exp}
319         * claim.
320         *
321         * @return The expiration time.
322         */
323        public Date getExpirationTime() {
324
325                return getDateClaim(EXP_CLAIM_NAME);
326        }
327
328
329        /**
330         * Gets the ID token issue time. Corresponds to the {@code iss} claim.
331         *
332         * @return The issue time.
333         */
334        public Date getIssueTime() {
335
336                return getDateClaim(IAT_CLAIM_NAME);
337        }
338
339
340        /**
341         * Gets the subject authentication time. Corresponds to the
342         * {@code auth_time} claim.
343         *
344         * @return The authentication time, {@code null} if not specified.
345         */
346        public Date getAuthenticationTime() {
347
348                return getDateClaim(AUTH_TIME_CLAIM_NAME);
349        }
350
351
352        /**
353         * Sets the subject authentication time. Corresponds to the
354         * {@code auth_time} claim.
355         *
356         * @param authTime The authentication time, {@code null} if not
357         *                 specified.
358         */
359        public void setAuthenticationTime(final Date authTime) {
360
361                setDateClaim(AUTH_TIME_CLAIM_NAME, authTime);
362        }
363
364
365        /**
366         * Gets the ID token nonce. Corresponds to the {@code nonce} claim.
367         *
368         * @return The nonce, {@code null} if not specified.
369         */
370        public Nonce getNonce() {
371
372                String value = getStringClaim(NONCE_CLAIM_NAME);
373
374                if (value == null)
375                        return null;
376
377                return new Nonce(value);
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.
400         */
401        public AccessTokenHash getAccessTokenHash() {
402
403                String value = getStringClaim(AT_HASH_CLAIM_NAME);
404
405                if (value == null)
406                        return null;
407
408                return new AccessTokenHash(value);
409        }
410
411
412        /**
413         * Sets the access token hash. Corresponds to the {@code at_hash}
414         * claim.
415         *
416         * @param atHash The access token hash, {@code null} if not specified.
417         */
418        public void setAccessTokenHash(final AccessTokenHash atHash) {
419
420                if (atHash != null)
421                        setClaim(AT_HASH_CLAIM_NAME, atHash.getValue());
422                else
423                        setClaim(AT_HASH_CLAIM_NAME, null);
424        }
425
426
427        /**
428         * Gets the authorisation code hash. Corresponds to the {@code c_hash}
429         * claim.
430         *
431         * @return The authorisation code hash, {@code null} if not specified.
432         */
433        public CodeHash getCodeHash() {
434
435                String value = getStringClaim(C_HASH_CLAIM_NAME);
436
437                if (value == null)
438                        return null;
439
440                return new CodeHash(value);
441        }
442
443
444        /**
445         * Sets the authorisation code hash. Corresponds to the {@code c_hash}
446         * claim.
447         *
448         * @param cHash The authorisation code hash, {@code null} if not
449         *              specified.
450         */
451        public void setCodeHash(final CodeHash cHash) {
452
453                if (cHash != null)
454                        setClaim(C_HASH_CLAIM_NAME, cHash.getValue());
455                else
456                        setClaim(C_HASH_CLAIM_NAME, null);
457        }
458
459
460        /**
461         * Gets the Authentication Context Class Reference (ACR). Corresponds
462         * to the {@code acr} claim.
463         *
464         * @return The Authentication Context Class Reference (ACR),
465         *         {@code null} if not specified.
466         */
467        public ACR getACR() {
468
469                String value = getStringClaim(ACR_CLAIM_NAME);
470
471                if (value == null)
472                        return null;
473
474                return new ACR(value);
475        }
476
477
478        /**
479         * Sets the Authentication Context Class Reference (ACR). Corresponds
480         * to the {@code acr} claim.
481         *
482         * @param acr The Authentication Context Class Reference (ACR),
483         *            {@code null} if not specified.
484         */
485        public void setACR(final ACR acr) {
486
487                if (acr != null)
488                        setClaim(ACR_CLAIM_NAME, acr.getValue());
489                else
490                        setClaim(ACR_CLAIM_NAME, null);
491        }
492
493
494        /**
495         * Gets the Authentication Methods References (AMRs). Corresponds to
496         * the {@code amr} claim.
497         *
498         * @return The Authentication Methods Reference (AMR) list,
499         *         {@code null} if not specified.
500         */
501        public List<AMR> getAMR() {
502
503                List<String> rawList = getStringListClaim(AMR_CLAIM_NAME);
504
505                if (rawList == null || rawList.isEmpty())
506                        return null;
507
508                List<AMR> amrList = new ArrayList<AMR>(rawList.size());
509
510                for (String s: rawList)
511                        amrList.add(new AMR(s));
512
513                return amrList;
514        }
515
516
517        /**
518         * Sets the Authentication Methods References (AMRs). Corresponds to
519         * the {@code amr} claim.
520         *
521         * @param amr The Authentication Methods Reference (AMR) list,
522         *            {@code null} if not specified.
523         */
524        public void setAMR(final List<AMR> amr) {
525
526                if (amr != null) {
527
528                        List<String> amrList = new ArrayList<String>(amr.size());
529
530                        for (AMR a: amr)
531                                amrList.add(a.getValue());
532
533                        setClaim(AMR_CLAIM_NAME, amrList);
534
535                } else {
536                        setClaim(AMR_CLAIM_NAME, null);
537                }
538        }
539
540
541        /**
542         * Gets the authorised party for the ID token. Corresponds to the
543         * {@code azp} claim.
544         *
545         * @return The authorised party, {@code null} if not specified.
546         */
547        public AuthorizedParty getAuthorizedParty() {
548
549                String value = getStringClaim(AZP_CLAIM_NAME);
550
551                if (value == null)
552                        return null;
553
554                return new AuthorizedParty(value);
555        }
556
557
558        /**
559         * Sets the authorised party for the ID token. Corresponds to the
560         * {@code azp} claim.
561         *
562         * @param azp The authorised party, {@code null} if not specified.
563         */
564        public void setAuthorizedParty(final AuthorizedParty azp) {
565
566                if (azp != null)
567                        setClaim(AZP_CLAIM_NAME, azp.getValue());
568                else
569                        setClaim(AZP_CLAIM_NAME, null);
570        }
571
572
573        /**
574         * Gets the subject's JSON Web Key (JWK) for a self-issued OpenID
575         * Connect provider. Corresponds to the {@code sub_jwk} claim.
576         *
577         * @return The subject's JWK, {@code null} if not specified.
578         */
579        public JWK getSubjectJWK() {
580
581                JSONObject jsonObject = getClaim(SUB_JWK_CLAIM_NAME, JSONObject.class);
582
583                if (jsonObject == null)
584                        return null;
585
586                try {
587                        return JWK.parse(jsonObject);
588
589                } catch (java.text.ParseException e) {
590
591                        return null;
592                }
593        }
594
595
596        /**
597         * Sets the subject's JSON Web Key (JWK) for a self-issued OpenID
598         * Connect provider. Corresponds to the {@code sub_jwk} claim.
599         *
600         * @param subJWK The subject's JWK (must be public), {@code null} if
601         *               not specified.
602         */
603        public void setSubjectJWK(final JWK subJWK) {
604
605                if (subJWK != null) {
606
607                        if (subJWK.isPrivate())
608                                throw new IllegalArgumentException("The subject's JSON Web Key (JWK) must be public");
609
610                        setClaim(SUB_JWK_CLAIM_NAME, subJWK.toJSONObject());
611
612                } else {
613                        setClaim(SUB_JWK_CLAIM_NAME, null);
614                }
615        }
616
617
618        /**
619         * Parses an ID token claims set from the specified JSON object string.
620         *
621         * @param json The JSON object string to parse. Must not be
622         *             {@code null}.
623         *
624         * @return The ID token claims set.
625         *
626         * @throws ParseException If parsing failed.
627         */
628        public static IDTokenClaimsSet parse(final String json)
629                throws ParseException {
630
631                JSONObject jsonObject = JSONObjectUtils.parseJSONObject(json);
632
633                try {
634                        return new IDTokenClaimsSet(jsonObject);
635
636                } catch (IllegalArgumentException e) {
637
638                        throw new ParseException(e.getMessage(), e);
639                }
640        }
641}