001    package com.nimbusds.jwt;
002    
003    
004    import java.text.ParseException;
005    
006    import java.util.Arrays;
007    import java.util.Collections;
008    import java.util.HashMap;
009    import java.util.HashSet;
010    import java.util.Map;
011    import java.util.Set;
012    
013    import net.minidev.json.JSONArray;
014    import net.minidev.json.JSONObject;
015    
016    import com.nimbusds.jose.util.JSONObjectUtils;
017    
018    
019    /**
020     * JSON Web Token (JWT) claims set.
021     *
022     * <p>Supports all {@link #getReservedNames reserved claims} of the JWT 
023     * specification:
024     *
025     * <ul>
026     *     <li>iss - Issuer
027     *     <li>sub - Subject
028     *     <li>aud - Audience
029     *     <li>exp - Expiration Time
030     *     <li>nbf - Not Before
031     *     <li>iat - Issued At
032     *     <li>jti - JWT ID
033     *     <li>typ - Type
034     * </ul>
035     *
036     * <p>The set may also carry {@link #setCustomClaims custom claims}; these will 
037     * be serialised and parsed along the reserved ones.
038     *
039     * @author Vladimir Dzhuvinov
040     * @version $version$ (2013-01-15)
041     */
042    public class JWTClaimsSet implements ReadOnlyJWTClaimsSet {
043    
044    
045            /**
046             * The reserved claim names.
047             */
048            private static final Set<String> RESERVED_CLAIM_NAMES;
049            
050            
051            /**
052             * Initialises the reserved claim name set.
053             */
054            static {
055                    Set<String> n = new HashSet<String>();
056                    
057                    n.add("iss");
058                    n.add("sub");
059                    n.add("aud");
060                    n.add("exp");
061                    n.add("nbf");
062                    n.add("iat");
063                    n.add("jti");
064                    n.add("typ");
065                    
066                    RESERVED_CLAIM_NAMES = Collections.unmodifiableSet(n);
067            }
068    
069    
070            /**
071             * The issuer claim.
072             */
073            private String iss = null;
074    
075    
076            /**
077             * The subject claim.
078             */
079            private String sub = null;
080    
081    
082            /**
083             * The audience claim.
084             */
085            private String[] aud = null;
086            
087            
088            /**
089             * The expiration time claim.
090             */
091            private long exp = -1l;
092            
093            
094            /**
095             * The not-before claim.
096             */
097            private long nbf = -1l;
098            
099            
100            /**
101             * The issued-at claim.
102             */
103            private long iat = -1l;
104            
105            
106            /**
107             * The JWT ID claim.
108             */
109            private String jti = null;
110            
111            
112            /**
113             * The type claim.
114             */
115            private String typ = null;
116            
117            
118            /**
119             * Custom claims.
120             */
121            private Map<String,Object> customClaims = new HashMap<String,Object>();
122            
123            
124            /**
125             * Creates a new empty JWT claims set.
126             */
127            public JWTClaimsSet() {
128            
129                    // Nothing to do
130            }
131            
132            
133            /**
134             * Gets the reserved JWT claim names.
135             *
136             * @return The reserved claim names, as an unmodifiable set.
137             */
138            public static Set<String> getReservedNames() {
139            
140                    return RESERVED_CLAIM_NAMES;
141            }
142    
143    
144            @Override
145            public String getIssuerClaim() {
146            
147                    return iss;
148            }
149            
150            
151            /**
152             * Sets the issuer ({@code iss}) claim.
153             *
154             * @param iss The issuer claim, {@code null} if not specified.
155             */
156            public void setIssuerClaim(final String iss) {
157            
158                    this.iss = iss;
159            }
160    
161    
162            @Override
163            public String getSubjectClaim() {
164            
165                    return sub;
166            }
167            
168            
169            /**
170             * Sets the subject ({@code sub}) claim.
171             *
172             * @param sub The subject claim, {@code null} if not specified.
173             */
174            public void setSubjectClaim(final String sub) {
175            
176                    this.sub = sub;
177            }
178            
179            
180            @Override
181            public String[] getAudienceClaim() {
182            
183                    return aud;
184            }
185            
186            
187            /**
188             * Sets the audience ({@code aud}) clam.
189             *
190             * @param aud The audience claim, {@code null} if not specified.
191             */
192            public void setAudienceClaim(final String[] aud) {
193            
194                    this.aud = aud;
195            }
196            
197            
198            @Override
199            public long getExpirationTimeClaim() {
200            
201                    return exp;
202            }
203            
204            
205            /**
206             * Sets the expiration time ({@code exp}) claim.
207             *
208             * @param exp The expiration time, -1 if not specified.
209             */
210            public void setExpirationTimeClaim(final long exp) {
211            
212                    this.exp = exp;
213            }
214            
215            
216            @Override
217            public long getNotBeforeClaim() {
218            
219                    return nbf;
220            }
221            
222            
223            /**
224             * Sets the not-before ({@code nbf}) claim.
225             *
226             * @param nbf The not-before claim, -1 if not specified.
227             */
228            public void setNotBeforeClaim(final long nbf) {
229            
230                    this.nbf = nbf;
231            }
232            
233            
234            @Override
235            public long getIssuedAtClaim() {
236            
237                    return iat;
238            }
239            
240            
241            /**
242             * Sets the issued-at ({@code iat}) claim.
243             *
244             * @param iat The issued-at claim, -1 if not specified.
245             */
246            public void setIssuedAtClaim(final long iat) {
247            
248                    this.iat = iat;
249            }
250            
251            
252            @Override
253            public String getJWTIDClaim() {
254            
255                    return jti;
256            }
257            
258            
259            /**
260             * Sets the JWT ID ({@code jti}) claim.
261             *
262             * @param jti The JWT ID claim, {@code null} if not specified.
263             */
264            public void setJWTIDClaim(final String jti) {
265            
266                    this.jti = jti;
267            }
268            
269            
270            @Override
271            public String getTypeClaim() {
272            
273                    return typ;
274            }
275            
276            
277            /**
278             * Sets the type ({@code typ}) claim.
279             *
280             * @param typ The type claim, {@code null} if not specified.
281             */
282            public void setTypeClaim(final String typ) {
283            
284                    this.typ = typ;
285            }
286            
287            
288            @Override
289            public Object getCustomClaim(final String name) {
290            
291                    return customClaims.get(name);
292            }
293            
294            
295            /**
296             * Sets a custom (non-reserved) claim.
297             *
298             * @param name  The name of the custom claim. Must not be {@code null}.
299             * @param value The value of the custom claim, should map to a valid 
300             *              JSON entity, {@code null} if not specified.
301             *
302             * @throws IllegalArgumentException If the specified custom claim name
303             *                                  matches a reserved claim name.
304             */
305            public void setCustomClaim(final String name, final Object value) {
306            
307                    if (getReservedNames().contains(name))
308                            throw new IllegalArgumentException("The claim name \"" + name + "\" matches a reserved name");
309                    
310                    customClaims.put(name, value);
311            }
312            
313            
314            @Override 
315            public Map<String,Object> getCustomClaims() {
316            
317                    return Collections.unmodifiableMap(customClaims);
318            }
319            
320            
321            /**
322             * Sets the custom (non-reserved) claims. The values must be 
323             * serialisable to a JSON entity, otherwise will be ignored.
324             *
325             * @param customClaims The custom claims, empty map or {@code null} if
326             *                     none.
327             */
328            public void setCustomClaims(final Map<String,Object> customClaims) {
329            
330                    if (customClaims == null)
331                            return;
332                    
333                    this.customClaims = customClaims;
334            }
335            
336            
337            @Override
338            public JSONObject toJSONObject() {
339            
340                    JSONObject o = new JSONObject(customClaims);
341    
342                    if (iss != null)
343                            o.put("iss", iss);
344    
345                    if (sub != null)
346                            o.put("sub", sub);
347                    
348                    if (aud != null) {
349                            JSONArray audArray = new JSONArray();
350                            audArray.addAll(Arrays.asList(aud));
351                            o.put("aud", audArray);
352                    }
353                    
354                    if (exp > -1)
355                            o.put("exp", exp);
356                    
357                    if (nbf > -1)
358                            o.put("nbf", nbf);
359                            
360                    if (iat > -1)
361                            o.put("iat", iat);
362                    
363                    if (jti != null)
364                            o.put("jti", jti);
365                    
366                    if (typ != null)
367                            o.put("typ", typ);
368                    
369                    return o;
370            }
371            
372            
373            /**
374             * Parses a JSON Web Token (JWT) claims set from the specified
375             * JSON object representation.
376             *
377             * @param json The JSON object to parse. Must not be {@code null}.
378             *
379             * @return The JWT claims set.
380             *
381             * @throws ParseException If the specified JSON object doesn't represent
382             *                        a valid JWT claims set.
383             */
384            public static JWTClaimsSet parse(final JSONObject json)
385                    throws ParseException {
386            
387                    JWTClaimsSet cs = new JWTClaimsSet();
388            
389                    // Parse reserved + custom params
390                    for (final String name: json.keySet()) {
391    
392                            if (name.equals("iss")) {
393    
394                                    cs.setIssuerClaim(JSONObjectUtils.getString(json, "iss"));
395                            }
396                            else if (name.equals("sub")) {
397    
398                                    cs.setSubjectClaim(JSONObjectUtils.getString(json, "sub"));
399                            }
400                            else if (name.equals("aud")) {
401    
402                                    Object audValue = json.get("aud");
403    
404                                    if (audValue != null && audValue instanceof String) {
405                                            String[] singleAud = {JSONObjectUtils.getString(json, "aud")};
406                                            cs.setAudienceClaim(singleAud);
407                                    }
408                                    else {
409                                            cs.setAudienceClaim(JSONObjectUtils.getStringArray(json, "aud"));
410                                    }
411                            }
412                            else if (name.equals("exp")) {
413                                    
414                                    cs.setExpirationTimeClaim(JSONObjectUtils.getLong(json, "exp"));
415                            }
416                            else if (name.equals("nbf")) {
417                                    
418                                    cs.setNotBeforeClaim(JSONObjectUtils.getLong(json, "nbf"));
419                            }
420                            else if (name.equals("iat")) {
421                                    
422                                    cs.setIssuedAtClaim(JSONObjectUtils.getLong(json, "iat"));
423                            }
424                            else if (name.equals("jti")) {
425                                    
426                                    cs.setJWTIDClaim(JSONObjectUtils.getString(json, "jti"));
427                            }
428                            else if (name.equals("typ")) {
429    
430                                    cs.setTypeClaim(JSONObjectUtils.getString(json, "typ"));
431                            }
432                            else {
433                                    cs.setCustomClaim(name, json.get(name));
434                            }
435                    }
436                    
437                    return cs;
438            }
439    }