001/*
002 * The contents of this file are subject to the terms of the Common Development and
003 * Distribution License (the License). You may not use this file except in compliance with the
004 * License.
005 *
006 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
007 * specific language governing permission and limitations under the License.
008 *
009 * When distributing Covered Software, include this CDDL Header Notice in each file and include
010 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
011 * Header, with the fields enclosed by brackets [] replaced by your own identifying
012 * information: "Portions copyright [year] [name of copyright owner]".
013 *
014 * Copyright 2013-2015 ForgeRock AS.
015 */
016
017package org.forgerock.json.jose.jwt;
018
019import static org.forgerock.json.jose.jwt.JwtClaimsSetKey.AUD;
020import static org.forgerock.json.jose.jwt.JwtClaimsSetKey.EXP;
021import static org.forgerock.json.jose.jwt.JwtClaimsSetKey.IAT;
022import static org.forgerock.json.jose.jwt.JwtClaimsSetKey.ISS;
023import static org.forgerock.json.jose.jwt.JwtClaimsSetKey.JTI;
024import static org.forgerock.json.jose.jwt.JwtClaimsSetKey.NBF;
025import static org.forgerock.json.jose.jwt.JwtClaimsSetKey.SUB;
026import static org.forgerock.json.jose.jwt.JwtClaimsSetKey.TYP;
027import static org.forgerock.json.jose.jwt.JwtClaimsSetKey.getClaimSetKey;
028
029import java.net.URI;
030import java.util.ArrayList;
031import java.util.Date;
032import java.util.List;
033import java.util.Map;
034
035import org.forgerock.json.jose.utils.IntDate;
036import org.forgerock.json.jose.utils.StringOrURI;
037
038/**
039 * An implementation that holds a JWT's Claims Set.
040 * <p>
041 * Provides methods to set claims for all the reserved claim names as well as custom claims.
042 *
043 * @since 2.0.0
044 */
045public class JwtClaimsSet extends JWObject implements Payload {
046
047    /**
048     * Constructs a new, empty JwtClaimsSet.
049     */
050    public JwtClaimsSet() {
051    }
052
053    /**
054     * Constructs a new JwtClaimsSet, with its claims set to the contents of the given Map.
055     *
056     * @param claims A Map containing the claims to be set in the Claims Set.
057     */
058    public JwtClaimsSet(Map<String, Object> claims) {
059        setClaims(claims);
060    }
061
062    /**
063     * Gets the type of the contents of the Claims Set.
064     * <p>
065     * The values used for this claim SHOULD come from the same value space as the JWT header parameter "typ",
066     * with the same rules applying.
067     *
068     * @param type The Claims Set content type.
069     */
070    public void setType(String type) {
071        put(TYP.value(), type);
072    }
073
074    /**
075     * Gets the type of the contents of the Claims Set.
076     * <p>
077     * The values used for this claim SHOULD come from the same value space as the JWT header parameter "typ",
078     * with the same rules applying.
079     *
080     * @return The Claims Set content type.
081     */
082    public String getType() {
083        return get(TYP.value()).asString();
084    }
085
086    /**
087     * Sets the unique ID of the JWT.
088     *
089     * @param jwtId The JWT's ID.
090     */
091    public void setJwtId(String jwtId) {
092        put(JTI.value(), jwtId);
093    }
094
095    /**
096     * Gets the unique ID of the JWT.
097     *
098     * @return The JWT's ID.
099     */
100    public String getJwtId() {
101        return get(JTI.value()).asString();
102    }
103
104    /**
105     * Sets the issuer this JWT was issued by.
106     * <p>
107     * The given issuer can be any arbitrary string without any ":" characters, if the string does contain a ":"
108     * character then it must be a valid URI.
109     *
110     * @param issuer The JWT's issuer.
111     */
112    public void setIssuer(String issuer) {
113        StringOrURI.validateStringOrURI(issuer);
114        put(ISS.value(), issuer);
115    }
116
117    /**
118     * Sets the issuer this JWT was issued by.
119     *
120     * @param issuer The JWT's issuer.
121     */
122    public void setIssuer(URI issuer) {
123        put(ISS.value(), issuer.toString());
124    }
125
126    /**
127     *
128     * Gets the issuer this JWT was issued by.
129     *
130     * @return The JWT's issuer.
131     */
132    public String getIssuer() {
133        return get(ISS.value()).asString();
134    }
135
136    /**
137     * Sets the subject this JWT is issued to.
138     * <p>
139     * The given subject can be any arbitrary string without any ":" characters, if the string does contain a ":"
140     * character then it must be a valid URI.
141     *
142     * @param subject The JWT's principal.
143     * @see #setSubject(java.net.URI)
144     */
145    public void setSubject(String subject) {
146        StringOrURI.validateStringOrURI(subject);
147        put(SUB.value(), subject);
148    }
149
150    /**
151     * Sets the subject this JWT is issued to.
152     *
153     * @param subject The JWT's principal.
154     * @see #setSubject(String)
155     */
156    public void setSubject(URI subject) {
157        put(SUB.value(), subject.toString());
158    }
159
160    /**
161     * Gets the subject this JWT is issued to.
162     *
163     * @return The JWT's principal.
164     */
165    public String getSubject() {
166        return get(SUB.value()).asString();
167    }
168
169    /**
170     * Adds an entry to the JWT's intended audience list, in the Claims Set.
171     * <p>
172     * The given audience can be any arbitrary string without any ":" characters, if the string does contain a ":"
173     * character then it must be a valid URI.
174     *
175     * @param audience The JWT's intended audience.
176     * @see #addAudience(java.net.URI)
177     */
178    public void addAudience(String audience) {
179        StringOrURI.validateStringOrURI(audience);
180        getAudienceNullCheck().add(audience);
181    }
182
183    /**
184     * Adds an entry to the JWT's intended audience list, in the Claims Set.
185     *
186     * @param audience The JWT's intended audience.
187     * @see #addAudience(String)
188     */
189    public void addAudience(URI audience) {
190        getAudienceNullCheck().add(audience.toString());
191    }
192
193    /**
194     * Gets the JWT's intended audience list from the Claims Set.
195     * <p>If audience claim has not been set before, the method will create the list to contain the audience
196     * claim and set it in the Claims Set.
197     *
198     * @return The list of the JWT's intended audience.
199     */
200    private List<String> getAudienceNullCheck() {
201        List<String> audienceList = getAudience();
202        if (audienceList == null) {
203            audienceList = new ArrayList<>();
204            put(AUD.value(), audienceList);
205        }
206        return audienceList;
207    }
208
209    /**
210     * Gets the intended audience for the JWT from the Claims Set.
211     *
212     * @return The JWT's intended audience.
213     */
214    public List<String> getAudience() {
215        return get(AUD.value()).asList(String.class);
216    }
217
218    /**
219     * Sets the time the JWT was issued at, in the Claims Set.
220     * <p>
221     * The given date will be converted into an {@link IntDate} to be stored in the JWT Claims Set.
222     *
223     * @param issuedAtTime The JWT's issued at time.
224     */
225    public void setIssuedAtTime(Date issuedAtTime) {
226        put(IAT.value(), IntDate.toIntDate(issuedAtTime));
227    }
228
229    /**
230     * Sets the time the JWT was issued at, in the Claims Set.
231     * <p>
232     * This method takes a long representation of the number of <strong>seconds</strong> have passed since epoch.
233     *
234     * @param issuedAtTime The JWT's issued at time as a long in seconds.
235     * @see #setIssuedAtTime(java.util.Date)
236     */
237    private void setIssuedAtTime(long issuedAtTime) {
238        put(IAT.value(), issuedAtTime);
239    }
240
241    /**
242     * Gets the time the JWT was issued at, from the Claims Set.
243     *
244     * @return The JWT's issued at time.
245     */
246    public Date getIssuedAtTime() {
247        return IntDate.fromIntDate(get(IAT.value()).asLong());
248    }
249
250    /**
251     * Sets the time the JWT is not allowed to be processed before, in the Claims Set.
252     * <p>
253     * The given date will be converted into an {@link IntDate} to be stored in the JWT Claims Set.
254     *
255     * @param notBeforeTime The JWT's not before time.
256     */
257    public void setNotBeforeTime(Date notBeforeTime) {
258        put(NBF.value(), IntDate.toIntDate(notBeforeTime));
259    }
260
261    /**
262     * Sets the time the JWT is not allowed to be processed before, in the Claims Set.
263     * <p>
264     * The method takes a long representation of the number of <strong>seconds</strong> have passed since epoch.
265     *
266     * @param notBeforeTime The JWT's not before time.
267     * @see #setNotBeforeTime(java.util.Date)
268     */
269    private void setNotBeforeTime(long notBeforeTime) {
270        put(NBF.value(), notBeforeTime);
271    }
272
273    /**
274     * Gets the time the JWT is not allowed to be processed before, from the Claims Set.
275     *
276     * @return The JWT's not before time.
277     */
278    public Date getNotBeforeTime() {
279        return IntDate.fromIntDate(get(NBF.value()).asLong());
280    }
281
282    /**
283     * Sets the expiration time of the JWT in the Claims Set.
284     * <p>
285     * The given date will be converted into an {@link IntDate} to be stored in the JWT Claims Set.
286     *
287     * @param expirationTime The JWT's expiration time.
288     */
289    public void setExpirationTime(Date expirationTime) {
290        put(EXP.value(), IntDate.toIntDate(expirationTime));
291    }
292
293    /**
294     * Sets the expiration time of the JWT in the Claims Set.
295     * <p>
296     * This method takes a long representation of the number of <strong>seconds</strong> have passed since epoch.
297     *
298     * @param expirationTime The JWT's expiration time as a long in seconds.
299     * @see #setExpirationTime(java.util.Date)
300     */
301    private void setExpirationTime(long expirationTime) {
302        put(EXP.value(), expirationTime);
303    }
304
305    /**
306     * Gets the expiration time of the JWT from the Claims Set.
307     *
308     * @return The JWT's expiration time.
309     */
310    public Date getExpirationTime() {
311        return IntDate.fromIntDate(get(EXP.value()).asLong());
312    }
313
314    /**
315     * Sets a claim with the specified name and value.
316     * <p>
317     * If the key matches one of the reserved claim names, then the relevant <tt>set</tt> method is called to set that
318     * claim with the specified name and value.
319     *
320     * @param key The claim name.
321     * @param value The claim value.
322     */
323    public void setClaim(String key, Object value) {
324
325        JwtClaimsSetKey claimsSetKey = getClaimSetKey(key.toUpperCase());
326
327        switch (claimsSetKey) {
328        case TYP: {
329            checkValueIsOfType(value, String.class);
330            setType((String) value);
331            break;
332        }
333        case JTI: {
334            checkValueIsOfType(value, String.class);
335            setJwtId((String) value);
336            break;
337        }
338        case ISS: {
339            if (isValueOfType(value, URI.class)) {
340                setIssuer((URI) value);
341            } else {
342                checkValueIsOfType(value, String.class);
343                setIssuer((String) value);
344            }
345            break;
346        }
347        case SUB: {
348            if (isValueOfType(value, URI.class)) {
349                setSubject((URI) value);
350            } else {
351                checkValueIsOfType(value, String.class);
352                setSubject((String) value);
353            }
354            break;
355        }
356        case AUD: {
357            if (isValueOfType(value, List.class)) {
358                List<?> audienceList = (List<?>) value;
359                for (Object audience : audienceList) {
360                    if (isValueOfType(audience, URI.class)) {
361                        addAudience((URI) audience);
362                    } else {
363                        checkValueIsOfType(audience, String.class);
364                        addAudience((String) audience);
365                    }
366                }
367            } else {
368                if (isValueOfType(value, URI.class)) {
369                    addAudience((URI) value);
370                } else {
371                    checkValueIsOfType(value, String.class);
372                    addAudience((String) value);
373                }
374            }
375            break;
376        }
377        case IAT: {
378            if (isValueOfType(value, Number.class)) {
379                setIssuedAtTime(((Number) value).longValue());
380            } else {
381                checkValueIsOfType(value, Date.class);
382                setIssuedAtTime((Date) value);
383            }
384            break;
385        }
386        case NBF: {
387            if (isValueOfType(value, Number.class)) {
388                setNotBeforeTime(((Number) value).longValue());
389            } else {
390                checkValueIsOfType(value, Date.class);
391                setNotBeforeTime((Date) value);
392            }
393            break;
394        }
395        case EXP: {
396            if (isValueOfType(value, Number.class)) {
397                setExpirationTime(((Number) value).longValue());
398            } else {
399                checkValueIsOfType(value, Date.class);
400                setExpirationTime((Date) value);
401            }
402            break;
403        }
404        default: {
405            put(key, value);
406        }
407        }
408    }
409
410    /**
411     * Sets claims using the values contained in the specified map.
412     *
413     * @param claims The Map to use to set the claims.
414     */
415    public void setClaims(Map<String, Object> claims) {
416        for (String key : claims.keySet()) {
417            setClaim(key, claims.get(key));
418        }
419    }
420
421    /**
422     * Gets a claim value for the specified key.
423     * <p>
424     * If the key matches one of the reserved claim names, then the relevant <tt>get</tt> method is called to get that
425     * claim value.
426     *
427     * @param key The claim name.
428     * @return The value stored against the claim name.
429     */
430    public Object getClaim(String key) {
431
432        JwtClaimsSetKey claimsSetKey = getClaimSetKey(key.toUpperCase());
433
434        Object value;
435
436        switch (claimsSetKey) {
437        case TYP: {
438            value = getType();
439            break;
440        }
441        case JTI: {
442            value = getJwtId();
443            break;
444        }
445        case ISS: {
446            value = getIssuer();
447            break;
448        }
449        case SUB: {
450            value = getSubject();
451            break;
452        }
453        case AUD: {
454            value = getAudience();
455            break;
456        }
457        case IAT: {
458            value = getIssuedAtTime();
459            break;
460        }
461        case NBF: {
462            value = getNotBeforeTime();
463            break;
464        }
465        case EXP: {
466            value = getExpirationTime();
467            break;
468        }
469        default: {
470            value = get(key).getObject();
471        }
472        }
473
474        return value;
475    }
476
477    /**
478     * Gets a claim value for the specified claim name and then casts it to the specified type.
479     *
480     * @param key The claim name.
481     * @param clazz The class of the required type.
482     * @param <T> The required type for the claim value.
483     * @return The value stored against the claim name.
484     * @see #getClaim(String)
485     */
486    public <T> T getClaim(String key, Class<T> clazz) {
487        return clazz.cast(getClaim(key));
488    }
489
490    /**
491     * Builds the JWT's Claims Set into a <code>String</code> representation of a JSON object.
492     *
493     * @return A JSON string.
494     */
495    public String build() {
496        return toString();
497    }
498}