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 com.nimbusds.jose.jwk.JWK;
024import com.nimbusds.jwt.JWTClaimsSet;
025import com.nimbusds.oauth2.sdk.ParseException;
026import com.nimbusds.oauth2.sdk.ResponseType;
027import com.nimbusds.oauth2.sdk.id.Audience;
028import com.nimbusds.oauth2.sdk.id.Issuer;
029import com.nimbusds.oauth2.sdk.id.Subject;
030import com.nimbusds.oauth2.sdk.util.JSONObjectUtils;
031import com.nimbusds.openid.connect.sdk.Nonce;
032import net.minidev.json.JSONArray;
033import net.minidev.json.JSONObject;
034
035
036/**
037 * ID token claims set, serialisable to a JSON object.
038 *
039 * <p>Example ID token claims set:
040 *
041 * <pre>
042 * {
043 *   "iss"       : "https://server.example.com",
044 *   "sub"       : "24400320",
045 *   "aud"       : "s6BhdRkqt3",
046 *   "nonce"     : "n-0S6_WzA2Mj",
047 *   "exp"       : 1311281970,
048 *   "iat"       : 1311280970,
049 *   "auth_time" : 1311280969,
050 *   "acr"       : "urn:mace:incommon:iap:silver",
051 *   "at_hash"   : "MTIzNDU2Nzg5MDEyMzQ1Ng"
052 * }
053 * </pre>
054 *
055 * <p>Related specifications:
056 *
057 * <ul>
058 *     <li>OpenID Connect Core 1.0, section 2.
059 *     <li>OpenID Connect Front-Channel Logout 1.0, section 3 (draft 02).
060 * </ul>
061 */
062public class IDTokenClaimsSet extends CommonClaimsSet {
063
064
065        /**
066         * The expiration time claim name.
067         */
068        public static final String EXP_CLAIM_NAME = "exp";
069
070
071        /**
072         * The subject authentication time claim name.
073         */
074        public static final String AUTH_TIME_CLAIM_NAME = "auth_time";
075
076
077        /**
078         * The nonce claim name.
079         */
080        public static final String NONCE_CLAIM_NAME = "nonce";
081
082
083        /**
084         * The access token hash claim name.
085         */
086        public static final String AT_HASH_CLAIM_NAME = "at_hash";
087
088
089        /**
090         * The authorisation code hash claim name.
091         */
092        public static final String C_HASH_CLAIM_NAME = "c_hash";
093
094
095        /**
096         * The ACR claim name.
097         */
098        public static final String ACR_CLAIM_NAME = "acr";
099
100
101        /**
102         * The AMRs claim name.
103         */
104        public static final String AMR_CLAIM_NAME = "amr";
105
106
107        /**
108         * The authorised party claim name.
109         */
110        public static final String AZP_CLAIM_NAME = "azp";
111
112
113        /**
114         * The subject JWK claim name.
115         */
116        public static final String SUB_JWK_CLAIM_NAME = "sub_jwk";
117
118
119        /**
120         * The names of the standard top-level ID token claims.
121         */
122        private static final Set<String> stdClaimNames = new LinkedHashSet<>();
123
124
125        static {
126                stdClaimNames.add(ISS_CLAIM_NAME);
127                stdClaimNames.add(SUB_CLAIM_NAME);
128                stdClaimNames.add(AUD_CLAIM_NAME);
129                stdClaimNames.add(EXP_CLAIM_NAME);
130                stdClaimNames.add(IAT_CLAIM_NAME);
131                stdClaimNames.add(AUTH_TIME_CLAIM_NAME);
132                stdClaimNames.add(NONCE_CLAIM_NAME);
133                stdClaimNames.add(AT_HASH_CLAIM_NAME);
134                stdClaimNames.add(C_HASH_CLAIM_NAME);
135                stdClaimNames.add(ACR_CLAIM_NAME);
136                stdClaimNames.add(AMR_CLAIM_NAME);
137                stdClaimNames.add(AZP_CLAIM_NAME);
138                stdClaimNames.add(SUB_JWK_CLAIM_NAME);
139                stdClaimNames.add(SID_CLAIM_NAME);
140        }
141
142
143        /**
144         * Gets the names of the standard top-level ID token claims.
145         *
146         * @return The names of the standard top-level ID token claims
147         *         (read-only set).
148         */
149        public static Set<String> getStandardClaimNames() {
150
151                return Collections.unmodifiableSet(stdClaimNames);
152        }
153
154
155        /**
156         * Creates a new minimal ID token claims set. Note that the ID token
157         * may require additional claims to be present depending on the
158         * original OpenID Connect authorisation request.
159         *
160         * @param iss The issuer. Must not be {@code null}.
161         * @param sub The subject. Must not be {@code null}.
162         * @param aud The audience. Must not be {@code null}.
163         * @param exp The expiration time. Must not be {@code null}.
164         * @param iat The issue time. Must not be {@code null}.
165         */
166        public IDTokenClaimsSet(final Issuer iss,
167                                final Subject sub,
168                                final List<Audience> aud,
169                                final Date exp,
170                                final Date iat) {
171
172                setClaim(ISS_CLAIM_NAME, iss.getValue());
173                setClaim(SUB_CLAIM_NAME, sub.getValue());
174
175                JSONArray audList = new JSONArray();
176
177                for (Audience a: aud)
178                        audList.add(a.getValue());
179
180                setClaim(AUD_CLAIM_NAME, audList);
181
182                setDateClaim(EXP_CLAIM_NAME, exp);
183                setDateClaim(IAT_CLAIM_NAME, iat);
184        }
185
186
187        /**
188         * Creates a new ID token claims set from the specified JSON object.
189         *
190         * @param jsonObject The JSON object. Must be verified to represent a
191         *                   valid ID token claims set and not be {@code null}.
192         *
193         * @throws ParseException If the JSON object doesn't represent a valid
194         *                        ID token claims set.
195         */
196        private IDTokenClaimsSet(final JSONObject jsonObject)
197                throws ParseException {
198
199                super(jsonObject);
200
201                if (getStringClaim(ISS_CLAIM_NAME) == null)
202                        throw new ParseException("Missing or invalid \"iss\" claim");
203
204                if (getStringClaim(SUB_CLAIM_NAME) == null)
205                        throw new ParseException("Missing or invalid \"sub\" claim");
206
207                if (getStringClaim(AUD_CLAIM_NAME) == null && getStringListClaim(AUD_CLAIM_NAME) == null ||
208                    getStringListClaim(AUD_CLAIM_NAME) != null && getStringListClaim(AUD_CLAIM_NAME).isEmpty())
209                        throw new ParseException("Missing or invalid \"aud\" claim");
210
211                if (getDateClaim(EXP_CLAIM_NAME) == null)
212                        throw new ParseException("Missing or invalid \"exp\" claim");
213
214                if (getDateClaim(IAT_CLAIM_NAME) == null)
215                        throw new ParseException("Missing or invalid \"iat\" claim");
216        }
217
218
219        /**
220         * Creates a new ID token claims set from the specified JSON Web Token
221         * (JWT) claims set.
222         *
223         * @param jwtClaimsSet The JWT claims set. Must not be {@code null}.
224         *
225         * @throws ParseException If the JWT claims set doesn't represent a
226         *                        valid ID token claims set.
227         */
228        public IDTokenClaimsSet(final JWTClaimsSet jwtClaimsSet)
229                throws ParseException {
230
231                this(jwtClaimsSet.toJSONObject());
232        }
233
234
235        /**
236         * Checks if this ID token claims set contains all required claims for
237         * the specified OpenID Connect response type.
238         *
239         * @param responseType     The OpenID Connect response type. Must not
240         *                         be {@code null}.
241         * @param iatAuthzEndpoint Specifies the endpoint where the ID token
242         *                         was issued (required for hybrid flow).
243         *                         {@code true} if the ID token was issued at
244         *                         the authorisation endpoint, {@code false} if
245         *                         the ID token was issued at the token
246         *                         endpoint.
247         *
248         * @return {@code true} if the required claims are contained, else
249         *         {@code false}.
250         */
251        public boolean hasRequiredClaims(final ResponseType responseType, final boolean iatAuthzEndpoint) {
252
253                // Code flow
254                // See http://openid.net/specs/openid-connect-core-1_0.html#CodeIDToken
255                if (new ResponseType("code").equals(responseType)) {
256                        // nonce, c_hash and at_hash not required
257                        return true; // ok
258                }
259
260                // Implicit flow
261                // See http://openid.net/specs/openid-connect-core-1_0.html#ImplicitIDToken
262                if (new ResponseType("id_token").equals(responseType)) {
263
264                        return getNonce() != null;
265
266                }
267
268                if (new ResponseType("id_token", "token").equals(responseType)) {
269
270                        if (getNonce() == null) {
271                                // nonce required
272                                return false;
273                        }
274
275                        return getAccessTokenHash() != null;
276
277                }
278
279                // Hybrid flow
280                // See http://openid.net/specs/openid-connect-core-1_0.html#HybridIDToken
281                if (new ResponseType("code", "id_token").equals(responseType)) {
282
283                        if (getNonce() == null) {
284                                // nonce required
285                                return false;
286                        }
287
288                        if (! iatAuthzEndpoint) {
289                                // c_hash and at_hash not required when id_token issued at token endpoint
290                                // See http://openid.net/specs/openid-connect-core-1_0.html#HybridIDToken2
291                                return true;
292                        }
293
294                        return getCodeHash() != null;
295
296                }
297
298                if (new ResponseType("code", "token").equals(responseType)) {
299
300                        if (getNonce() == null) {
301                                // nonce required
302                                return false;
303                        }
304
305                        if (! iatAuthzEndpoint) {
306                                // c_hash and at_hash not required when id_token issued at token endpoint
307                                // See http://openid.net/specs/openid-connect-core-1_0.html#HybridIDToken2
308                                return true;
309                        }
310
311                        return true; // ok
312                }
313
314                if (new ResponseType("code", "id_token", "token").equals(responseType)) {
315
316                        if (getNonce() == null) {
317                                // nonce required
318                                return false;
319                        }
320
321                        if (! iatAuthzEndpoint) {
322                                // c_hash and at_hash not required when id_token issued at token endpoint
323                                // See http://openid.net/specs/openid-connect-core-1_0.html#HybridIDToken2
324                                return true;
325                        }
326
327                        if (getAccessTokenHash() == null) {
328                                // at_hash required when issued at authz endpoint
329                                return false;
330                        }
331
332                        return getCodeHash() != null;
333
334                }
335
336                throw new IllegalArgumentException("Unsupported response_type: " + responseType);
337        }
338
339
340        /**
341         * Use {@link #hasRequiredClaims(ResponseType, boolean)} instead.
342         *
343         * @param responseType The OpenID Connect response type. Must not be
344         *                     {@code null}.
345         *
346         * @return {@code true} if the required claims are contained, else
347         *         {@code false}.
348         */
349        @Deprecated
350        public boolean hasRequiredClaims(final ResponseType responseType) {
351
352                return hasRequiredClaims(responseType, true);
353        }
354
355
356        /**
357         * Gets the ID token expiration time. Corresponds to the {@code exp}
358         * claim.
359         *
360         * @return The expiration time.
361         */
362        public Date getExpirationTime() {
363
364                return getDateClaim(EXP_CLAIM_NAME);
365        }
366
367
368        /**
369         * Gets the subject authentication time. Corresponds to the
370         * {@code auth_time} claim.
371         *
372         * @return The authentication time, {@code null} if not specified or
373         *         parsing failed.
374         */
375        public Date getAuthenticationTime() {
376
377                return getDateClaim(AUTH_TIME_CLAIM_NAME);
378        }
379
380
381        /**
382         * Sets the subject authentication time. Corresponds to the
383         * {@code auth_time} claim.
384         *
385         * @param authTime The authentication time, {@code null} if not
386         *                 specified.
387         */
388        public void setAuthenticationTime(final Date authTime) {
389
390                setDateClaim(AUTH_TIME_CLAIM_NAME, authTime);
391        }
392
393
394        /**
395         * Gets the ID token nonce. Corresponds to the {@code nonce} claim.
396         *
397         * @return The nonce, {@code null} if not specified or parsing failed.
398         */
399        public Nonce getNonce() {
400
401                String value = getStringClaim(NONCE_CLAIM_NAME);
402                return value != null ? new Nonce(value) : null;
403        }
404
405
406        /**
407         * Sets the ID token nonce. Corresponds to the {@code nonce} claim.
408         *
409         * @param nonce The nonce, {@code null} if not specified.
410         */
411        public void setNonce(final Nonce nonce) {
412
413                setClaim(NONCE_CLAIM_NAME, nonce != null ? nonce.getValue() : null);
414        }
415
416
417        /**
418         * Gets the access token hash. Corresponds to the {@code at_hash}
419         * claim.
420         *
421         * @return The access token hash, {@code null} if not specified or
422         *         parsing failed.
423         */
424        public AccessTokenHash getAccessTokenHash() {
425
426                String value = getStringClaim(AT_HASH_CLAIM_NAME);
427                return value != null ? new AccessTokenHash(value) : null;
428        }
429
430
431        /**
432         * Sets the access token hash. Corresponds to the {@code at_hash}
433         * claim.
434         *
435         * @param atHash The access token hash, {@code null} if not specified.
436         */
437        public void setAccessTokenHash(final AccessTokenHash atHash) {
438
439                setClaim(AT_HASH_CLAIM_NAME, atHash != null ? atHash.getValue() : 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                return value != null ? new CodeHash(value) : null;
454        }
455
456
457        /**
458         * Sets the authorisation code hash. Corresponds to the {@code c_hash}
459         * claim.
460         *
461         * @param cHash The authorisation code hash, {@code null} if not
462         *              specified.
463         */
464        public void setCodeHash(final CodeHash cHash) {
465
466                setClaim(C_HASH_CLAIM_NAME, cHash != null ? cHash.getValue() : null);
467        }
468
469
470        /**
471         * Gets the Authentication Context Class Reference (ACR). Corresponds
472         * to the {@code acr} claim.
473         *
474         * @return The Authentication Context Class Reference (ACR),
475         *         {@code null} if not specified or parsing failed.
476         */
477        public ACR getACR() {
478
479                String value = getStringClaim(ACR_CLAIM_NAME);
480                return value != null ? new ACR(value) : null;
481        }
482
483
484        /**
485         * Sets the Authentication Context Class Reference (ACR). Corresponds
486         * to the {@code acr} claim.
487         *
488         * @param acr The Authentication Context Class Reference (ACR),
489         *            {@code null} if not specified.
490         */
491        public void setACR(final ACR acr) {
492
493                setClaim(ACR_CLAIM_NAME, acr != null ? acr.getValue() : null);
494        }
495
496
497        /**
498         * Gets the Authentication Methods References (AMRs). Corresponds to
499         * the {@code amr} claim.
500         *
501         * @return The Authentication Methods Reference (AMR) list,
502         *         {@code null} if not specified or parsing failed.
503         */
504        public List<AMR> getAMR() {
505
506                List<String> rawList = getStringListClaim(AMR_CLAIM_NAME);
507
508                if (rawList == null || rawList.isEmpty())
509                        return null;
510
511                List<AMR> amrList = new ArrayList<>(rawList.size());
512
513                for (String s: rawList)
514                        amrList.add(new AMR(s));
515
516                return amrList;
517        }
518
519
520        /**
521         * Sets the Authentication Methods References (AMRs). Corresponds to
522         * the {@code amr} claim.
523         *
524         * @param amr The Authentication Methods Reference (AMR) list,
525         *            {@code null} if not specified.
526         */
527        public void setAMR(final List<AMR> amr) {
528
529                if (amr != null) {
530
531                        List<String> amrList = new ArrayList<>(amr.size());
532
533                        for (AMR a: amr)
534                                amrList.add(a.getValue());
535
536                        setClaim(AMR_CLAIM_NAME, amrList);
537
538                } else {
539                        setClaim(AMR_CLAIM_NAME, null);
540                }
541        }
542
543
544        /**
545         * Gets the authorised party for the ID token. Corresponds to the
546         * {@code azp} claim.
547         *
548         * @return The authorised party, {@code null} if not specified or
549         *         parsing failed.
550         */
551        public AuthorizedParty getAuthorizedParty() {
552
553                String value = getStringClaim(AZP_CLAIM_NAME);
554                return value != null ? new AuthorizedParty(value) : null;
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                setClaim(AZP_CLAIM_NAME, azp != null ? azp.getValue() : null);
567        }
568
569
570        /**
571         * Gets the subject's JSON Web Key (JWK) for a self-issued OpenID
572         * Connect provider. Corresponds to the {@code sub_jwk} claim.
573         *
574         * @return The subject's JWK, {@code null} if not specified or parsing
575         *         failed.
576         */
577        public JWK getSubjectJWK() {
578
579                JSONObject jsonObject = getClaim(SUB_JWK_CLAIM_NAME, JSONObject.class);
580
581                if (jsonObject == null)
582                        return null;
583
584                try {
585                        return JWK.parse(jsonObject);
586
587                } catch (java.text.ParseException e) {
588
589                        return null;
590                }
591        }
592
593
594        /**
595         * Sets the subject's JSON Web Key (JWK) for a self-issued OpenID
596         * Connect provider. Corresponds to the {@code sub_jwk} claim.
597         *
598         * @param subJWK The subject's JWK (must be public), {@code null} if
599         *               not specified.
600         */
601        public void setSubjectJWK(final JWK subJWK) {
602
603                if (subJWK != null) {
604
605                        if (subJWK.isPrivate())
606                                throw new IllegalArgumentException("The subject's JSON Web Key (JWK) must be public");
607
608                        setClaim(SUB_JWK_CLAIM_NAME, subJWK.toJSONObject());
609
610                } else {
611                        setClaim(SUB_JWK_CLAIM_NAME, null);
612                }
613        }
614
615
616        /**
617         * Parses an ID token claims set from the specified JSON object string.
618         *
619         * @param json The JSON object string to parse. Must not be
620         *             {@code null}.
621         *
622         * @return The ID token claims set.
623         *
624         * @throws ParseException If parsing failed.
625         */
626        public static IDTokenClaimsSet parse(final String json)
627                throws ParseException {
628
629                JSONObject jsonObject = JSONObjectUtils.parse(json);
630
631                try {
632                        return new IDTokenClaimsSet(jsonObject);
633
634                } catch (IllegalArgumentException e) {
635
636                        throw new ParseException(e.getMessage(), e);
637                }
638        }
639}