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