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                        if (getNonce() == null) {
279                                // nonce required
280                                return false;
281                        }
282
283                        return true; // ok
284                }
285
286                if (new ResponseType("id_token", "token").equals(responseType)) {
287
288                        if (getNonce() == null) {
289                                // nonce required
290                                return false;
291                        }
292
293                        if (getAccessTokenHash() == null) {
294                                // at_hash required
295                                return false;
296                        }
297
298                        return true; // ok
299                }
300
301                // Hybrid flow
302                // See http://openid.net/specs/openid-connect-core-1_0.html#HybridIDToken
303                if (new ResponseType("code", "id_token").equals(responseType)) {
304
305                        if (getNonce() == null) {
306                                // nonce required
307                                return false;
308                        }
309
310                        if (! iatAuthzEndpoint) {
311                                // c_hash and at_hash not required when id_token issued at token endpoint
312                                // See http://openid.net/specs/openid-connect-core-1_0.html#HybridIDToken2
313                                return true;
314                        }
315
316                        if (getCodeHash() == null) {
317                                // c_hash required when issued at authz endpoint
318                                return false;
319                        }
320
321                        return true; // ok
322                }
323
324                if (new ResponseType("code", "token").equals(responseType)) {
325
326                        if (getNonce() == null) {
327                                // nonce required
328                                return false;
329                        }
330
331                        if (! iatAuthzEndpoint) {
332                                // c_hash and at_hash not required when id_token issued at token endpoint
333                                // See http://openid.net/specs/openid-connect-core-1_0.html#HybridIDToken2
334                                return true;
335                        }
336
337                        return true; // ok
338                }
339
340                if (new ResponseType("code", "id_token", "token").equals(responseType)) {
341
342                        if (getNonce() == null) {
343                                // nonce required
344                                return false;
345                        }
346
347                        if (! iatAuthzEndpoint) {
348                                // c_hash and at_hash not required when id_token issued at token endpoint
349                                // See http://openid.net/specs/openid-connect-core-1_0.html#HybridIDToken2
350                                return true;
351                        }
352
353                        if (getAccessTokenHash() == null) {
354                                // at_hash required when issued at authz endpoint
355                                return false;
356                        }
357
358                        if (getCodeHash() == null) {
359                                // c_hash required when issued at authz endpoint
360                                return false;
361                        }
362
363                        return true; // ok
364                }
365
366                throw new IllegalArgumentException("Unsupported response_type: " + responseType);
367        }
368
369
370        /**
371         * Use {@link #hasRequiredClaims(ResponseType, boolean)} instead.
372         *
373         * @param responseType The OpenID Connect response type. Must not be
374         *                     {@code null}.
375         *
376         * @return {@code true} if the required claims are contained, else
377         *         {@code false}.
378         */
379        @Deprecated
380        public boolean hasRequiredClaims(final ResponseType responseType) {
381
382                return hasRequiredClaims(responseType, true);
383        }
384
385
386        /**
387         * Gets the ID token issuer. Corresponds to the {@code iss} claim.
388         *
389         * @return The issuer.
390         */
391        public Issuer getIssuer() {
392
393                return new Issuer(getStringClaim(ISS_CLAIM_NAME));
394        }
395
396
397        /**
398         * Gets the ID token subject. Corresponds to the {@code sub} claim.
399         *
400         * @return The subject.
401         */
402        public Subject getSubject() {
403
404                return new Subject(getStringClaim(SUB_CLAIM_NAME));
405        }
406
407
408        /**
409         * Gets the ID token audience. Corresponds to the {@code aud} claim.
410         *
411         * @return The audience.
412         */
413        public List<Audience> getAudience() {
414
415                if (getClaim(AUD_CLAIM_NAME) instanceof String) {
416                        // Special case - aud is a string
417                        return new Audience(getStringClaim(AUD_CLAIM_NAME)).toSingleAudienceList();
418                }
419
420                // General case - JSON string array
421                List<String> rawList = getStringListClaim(AUD_CLAIM_NAME);
422
423                List<Audience> audList = new ArrayList<>(rawList.size());
424
425                for (String s: rawList)
426                        audList.add(new Audience(s));
427
428                return audList;
429        }
430
431
432        /**
433         * Gets the ID token expiration time. Corresponds to the {@code exp}
434         * claim.
435         *
436         * @return The expiration time.
437         */
438        public Date getExpirationTime() {
439
440                return getDateClaim(EXP_CLAIM_NAME);
441        }
442
443
444        /**
445         * Gets the ID token issue time. Corresponds to the {@code iss} claim.
446         *
447         * @return The issue time.
448         */
449        public Date getIssueTime() {
450
451                return getDateClaim(IAT_CLAIM_NAME);
452        }
453
454
455        /**
456         * Gets the subject authentication time. Corresponds to the
457         * {@code auth_time} claim.
458         *
459         * @return The authentication time, {@code null} if not specified or
460         *         parsing failed.
461         */
462        public Date getAuthenticationTime() {
463
464                return getDateClaim(AUTH_TIME_CLAIM_NAME);
465        }
466
467
468        /**
469         * Sets the subject authentication time. Corresponds to the
470         * {@code auth_time} claim.
471         *
472         * @param authTime The authentication time, {@code null} if not
473         *                 specified.
474         */
475        public void setAuthenticationTime(final Date authTime) {
476
477                setDateClaim(AUTH_TIME_CLAIM_NAME, authTime);
478        }
479
480
481        /**
482         * Gets the ID token nonce. Corresponds to the {@code nonce} claim.
483         *
484         * @return The nonce, {@code null} if not specified or parsing failed.
485         */
486        public Nonce getNonce() {
487
488                String value = getStringClaim(NONCE_CLAIM_NAME);
489                return value != null ? new Nonce(value) : null;
490        }
491
492
493        /**
494         * Sets the ID token nonce. Corresponds to the {@code nonce} claim.
495         *
496         * @param nonce The nonce, {@code null} if not specified.
497         */
498        public void setNonce(final Nonce nonce) {
499
500                if (nonce != null)
501                        setClaim(NONCE_CLAIM_NAME, nonce.getValue());
502                else
503                        setClaim(NONCE_CLAIM_NAME, null);
504        }
505
506
507        /**
508         * Gets the access token hash. Corresponds to the {@code at_hash}
509         * claim.
510         *
511         * @return The access token hash, {@code null} if not specified or
512         *         parsing failed.
513         */
514        public AccessTokenHash getAccessTokenHash() {
515
516                String value = getStringClaim(AT_HASH_CLAIM_NAME);
517                return value != null ? new AccessTokenHash(value) : null;
518        }
519
520
521        /**
522         * Sets the access token hash. Corresponds to the {@code at_hash}
523         * claim.
524         *
525         * @param atHash The access token hash, {@code null} if not specified.
526         */
527        public void setAccessTokenHash(final AccessTokenHash atHash) {
528
529                if (atHash != null)
530                        setClaim(AT_HASH_CLAIM_NAME, atHash.getValue());
531                else
532                        setClaim(AT_HASH_CLAIM_NAME, null);
533        }
534
535
536        /**
537         * Gets the authorisation code hash. Corresponds to the {@code c_hash}
538         * claim.
539         *
540         * @return The authorisation code hash, {@code null} if not specified
541         *         or parsing failed.
542         */
543        public CodeHash getCodeHash() {
544
545                String value = getStringClaim(C_HASH_CLAIM_NAME);
546                return value != null ? new CodeHash(value) : null;
547        }
548
549
550        /**
551         * Sets the authorisation code hash. Corresponds to the {@code c_hash}
552         * claim.
553         *
554         * @param cHash The authorisation code hash, {@code null} if not
555         *              specified.
556         */
557        public void setCodeHash(final CodeHash cHash) {
558
559                if (cHash != null)
560                        setClaim(C_HASH_CLAIM_NAME, cHash.getValue());
561                else
562                        setClaim(C_HASH_CLAIM_NAME, null);
563        }
564
565
566        /**
567         * Gets the Authentication Context Class Reference (ACR). Corresponds
568         * to the {@code acr} claim.
569         *
570         * @return The Authentication Context Class Reference (ACR),
571         *         {@code null} if not specified or parsing failed.
572         */
573        public ACR getACR() {
574
575                String value = getStringClaim(ACR_CLAIM_NAME);
576                return value != null ? new ACR(value) : null;
577        }
578
579
580        /**
581         * Sets the Authentication Context Class Reference (ACR). Corresponds
582         * to the {@code acr} claim.
583         *
584         * @param acr The Authentication Context Class Reference (ACR),
585         *            {@code null} if not specified.
586         */
587        public void setACR(final ACR acr) {
588
589                if (acr != null)
590                        setClaim(ACR_CLAIM_NAME, acr.getValue());
591                else
592                        setClaim(ACR_CLAIM_NAME, null);
593        }
594
595
596        /**
597         * Gets the Authentication Methods References (AMRs). Corresponds to
598         * the {@code amr} claim.
599         *
600         * @return The Authentication Methods Reference (AMR) list,
601         *         {@code null} if not specified or parsing failed.
602         */
603        public List<AMR> getAMR() {
604
605                List<String> rawList = getStringListClaim(AMR_CLAIM_NAME);
606
607                if (rawList == null || rawList.isEmpty())
608                        return null;
609
610                List<AMR> amrList = new ArrayList<>(rawList.size());
611
612                for (String s: rawList)
613                        amrList.add(new AMR(s));
614
615                return amrList;
616        }
617
618
619        /**
620         * Sets the Authentication Methods References (AMRs). Corresponds to
621         * the {@code amr} claim.
622         *
623         * @param amr The Authentication Methods Reference (AMR) list,
624         *            {@code null} if not specified.
625         */
626        public void setAMR(final List<AMR> amr) {
627
628                if (amr != null) {
629
630                        List<String> amrList = new ArrayList<>(amr.size());
631
632                        for (AMR a: amr)
633                                amrList.add(a.getValue());
634
635                        setClaim(AMR_CLAIM_NAME, amrList);
636
637                } else {
638                        setClaim(AMR_CLAIM_NAME, null);
639                }
640        }
641
642
643        /**
644         * Gets the authorised party for the ID token. Corresponds to the
645         * {@code azp} claim.
646         *
647         * @return The authorised party, {@code null} if not specified or
648         *         parsing failed.
649         */
650        public AuthorizedParty getAuthorizedParty() {
651
652                String value = getStringClaim(AZP_CLAIM_NAME);
653                return value != null ? new AuthorizedParty(value) : null;
654        }
655
656
657        /**
658         * Sets the authorised party for the ID token. Corresponds to the
659         * {@code azp} claim.
660         *
661         * @param azp The authorised party, {@code null} if not specified.
662         */
663        public void setAuthorizedParty(final AuthorizedParty azp) {
664
665                if (azp != null)
666                        setClaim(AZP_CLAIM_NAME, azp.getValue());
667                else
668                        setClaim(AZP_CLAIM_NAME, null);
669        }
670
671
672        /**
673         * Gets the subject's JSON Web Key (JWK) for a self-issued OpenID
674         * Connect provider. Corresponds to the {@code sub_jwk} claim.
675         *
676         * @return The subject's JWK, {@code null} if not specified or parsing
677         *         failed.
678         */
679        public JWK getSubjectJWK() {
680
681                JSONObject jsonObject = getClaim(SUB_JWK_CLAIM_NAME, JSONObject.class);
682
683                if (jsonObject == null)
684                        return null;
685
686                try {
687                        return JWK.parse(jsonObject);
688
689                } catch (java.text.ParseException e) {
690
691                        return null;
692                }
693        }
694
695
696        /**
697         * Sets the subject's JSON Web Key (JWK) for a self-issued OpenID
698         * Connect provider. Corresponds to the {@code sub_jwk} claim.
699         *
700         * @param subJWK The subject's JWK (must be public), {@code null} if
701         *               not specified.
702         */
703        public void setSubjectJWK(final JWK subJWK) {
704
705                if (subJWK != null) {
706
707                        if (subJWK.isPrivate())
708                                throw new IllegalArgumentException("The subject's JSON Web Key (JWK) must be public");
709
710                        setClaim(SUB_JWK_CLAIM_NAME, subJWK.toJSONObject());
711
712                } else {
713                        setClaim(SUB_JWK_CLAIM_NAME, null);
714                }
715        }
716
717
718        /**
719         * Parses an ID token claims set from the specified JSON object string.
720         *
721         * @param json The JSON object string to parse. Must not be
722         *             {@code null}.
723         *
724         * @return The ID token claims set.
725         *
726         * @throws ParseException If parsing failed.
727         */
728        public static IDTokenClaimsSet parse(final String json)
729                throws ParseException {
730
731                JSONObject jsonObject = JSONObjectUtils.parse(json);
732
733                try {
734                        return new IDTokenClaimsSet(jsonObject);
735
736                } catch (IllegalArgumentException e) {
737
738                        throw new ParseException(e.getMessage(), e);
739                }
740        }
741}