001package com.nimbusds.jose;
002
003
004import java.text.ParseException;
005import java.util.Collections;
006import java.util.HashMap;
007import java.util.HashSet;
008import java.util.Map;
009import java.util.Set;
010
011import net.jcip.annotations.Immutable;
012
013import net.minidev.json.JSONObject;
014
015import com.nimbusds.jose.util.Base64URL;
016import com.nimbusds.jose.util.JSONObjectUtils;
017
018
019/**
020 * Unsecured ({@code alg=none}) JOSE header. This class is immutable.
021 *
022 * <p>Supports all {@link #getRegisteredParameterNames registered header
023 * parameters} of the unsecured JOSE object specification:
024 *
025 * <ul>
026 *     <li>alg (set to {@link Algorithm#NONE "none"}).
027 *     <li>typ
028 *     <li>cty
029 *     <li>crit
030 * </ul>
031 *
032 * <p>The header may also carry {@link #getCustomParams custom parameters};
033 * these will be serialised and parsed along the registered ones.
034 *
035 * <p>Example:
036 *
037 * <pre>
038 * {
039 *   "alg" : "none"
040 * }
041 * </pre>
042 *
043 * @author Vladimir Dzhuvinov
044 * @version 2014-08-20
045 */
046@Immutable
047public final class PlainHeader extends Header {
048
049
050        private static final long serialVersionUID = 1L;
051
052
053        /**
054         * The registered parameter names.
055         */
056        private static final Set<String> REGISTERED_PARAMETER_NAMES;
057
058
059        /**
060         * Initialises the registered parameter name set.
061         */
062        static {
063                Set<String> p = new HashSet<>();
064
065                p.add("alg");
066                p.add("typ");
067                p.add("cty");
068                p.add("crit");
069
070                REGISTERED_PARAMETER_NAMES = Collections.unmodifiableSet(p);
071        }
072
073
074        /**
075         * Builder for constructing unsecured (plain) headers.
076         *
077         * <p>Example usage:
078         *
079         * <pre>
080         * PlainHeader header = new PlainHeader.Builder().
081         *                      contentType("text/plain").
082         *                      customParam("exp", new Date().getTime()).
083         *                      build();
084         * </pre>
085         */
086        public static class Builder {
087
088
089                /**
090                 * The JOSE object type.
091                 */
092                private JOSEObjectType typ;
093
094
095                /**
096                 * The content type.
097                 */
098                private String cty;
099
100
101                /**
102                 * The critical headers.
103                 */
104                private Set<String> crit;
105
106
107                /**
108                 * Custom header parameters.
109                 */
110                private Map<String,Object> customParams;
111
112
113                /**
114                 * The parsed Base64URL.
115                 */
116                private Base64URL parsedBase64URL;
117
118
119                /**
120                 * Creates a new unsecured (plain) header builder.
121                 */
122                public Builder() {
123
124                }
125
126
127                /**
128                 * Creates a new unsecured (plain) header builder with the
129                 * parameters from the specified header.
130                 *
131                 * @param plainHeader The unsecured header to use. Must not be
132                 *                    {@code null}.
133                 */
134                public Builder(final PlainHeader plainHeader) {
135
136                        typ = plainHeader.getType();
137                        cty = plainHeader.getContentType();
138                        crit = plainHeader.getCriticalParams();
139                        customParams = plainHeader.getCustomParams();
140                }
141
142
143                /**
144                 * Sets the type ({@code typ}) parameter.
145                 *
146                 * @param typ The type parameter, {@code null} if not
147                 *            specified.
148                 *
149                 * @return This builder.
150                 */
151                public Builder type(final JOSEObjectType typ) {
152
153                        this.typ = typ;
154                        return this;
155                }
156
157
158                /**
159                 * Sets the content type ({@code cty}) parameter.
160                 *
161                 * @param cty The content type parameter, {@code null} if not
162                 *            specified.
163                 *
164                 * @return This builder.
165                 */
166                public Builder contentType(final String cty) {
167
168                        this.cty = cty;
169                        return this;
170                }
171
172
173                /**
174                 * Sets the critical header parameters ({@code crit})
175                 * parameter.
176                 *
177                 * @param crit The names of the critical header parameters,
178                 *             empty set or {@code null} if none.
179                 *
180                 * @return This builder.
181                 */
182                public Builder criticalParams(final Set<String> crit) {
183
184                        this.crit = crit;
185                        return this;
186                }
187
188
189                /**
190                 * Sets a custom (non-registered) parameter.
191                 *
192                 * @param name  The name of the custom parameter. Must not
193                 *              match a registered parameter name and must not
194                 *              be {@code null}.
195                 * @param value The value of the custom parameter, should map
196                 *              to a valid JSON entity, {@code null} if not
197                 *              specified.
198                 *
199                 * @return This builder.
200                 *
201                 * @throws IllegalArgumentException If the specified parameter
202                 *                                  name matches a registered
203                 *                                  parameter name.
204                 */
205                public Builder customParam(final String name, final Object value) {
206
207                        if (getRegisteredParameterNames().contains(name)) {
208                                throw new IllegalArgumentException("The parameter name \"" + name + "\" matches a registered name");
209                        }
210
211                        if (customParams == null) {
212                                customParams = new HashMap<>();
213                        }
214
215                        customParams.put(name, value);
216
217                        return this;
218                }
219
220
221                /**
222                 * Sets the custom (non-registered) parameters. The values must
223                 * be serialisable to a JSON entity, otherwise will be ignored.
224                 *
225                 * @param customParameters The custom parameters, empty map or
226                 *                         {@code null} if none.
227                 *
228                 * @return This builder.
229                 */
230                public Builder customParams(final Map<String, Object> customParameters) {
231
232                        this.customParams = customParameters;
233                        return this;
234                }
235
236
237                /**
238                 * Sets the parsed Base64URL.
239                 *
240                 * @param base64URL The parsed Base64URL, {@code null} if the
241                 *                  header is created from scratch.
242                 *
243                 * @return This builder.
244                 */
245                public Builder parsedBase64URL(final Base64URL base64URL) {
246
247                        this.parsedBase64URL = base64URL;
248                        return this;
249                }
250
251
252                /**
253                 * Builds a new unsecured (plain) header.
254                 *
255                 * @return The unsecured header.
256                 */
257                public PlainHeader build() {
258
259                        return new PlainHeader(typ, cty, crit, customParams, parsedBase64URL);
260                }
261        }
262
263
264        /**
265         * Creates a new minimal unsecured (plain) header with algorithm
266         * {@link Algorithm#NONE none}.
267         */
268        public PlainHeader() {
269
270                this(null, null, null, null, null);
271        }
272
273
274        /**
275         * Creates a new unsecured (plain) header with algorithm
276         * {@link Algorithm#NONE none}.
277         *
278         * @param typ             The type ({@code typ}) parameter,
279         *                        {@code null} if not specified.
280         * @param cty             The content type ({@code cty}) parameter,
281         *                        {@code null} if not specified.
282         * @param crit            The names of the critical header
283         *                        ({@code crit}) parameters, empty set or
284         *                        {@code null} if none.
285         * @param customParams    The custom parameters, empty map or
286         *                        {@code null} if none.
287         * @param parsedBase64URL The parsed Base64URL, {@code null} if the
288         *                        header is created from scratch.
289         */
290        public PlainHeader(final JOSEObjectType typ,
291                           final String cty,
292                           final Set<String> crit,
293                           final Map<String, Object> customParams,
294                           final Base64URL parsedBase64URL) {
295
296                super(Algorithm.NONE, typ, cty, crit, customParams, parsedBase64URL);
297        }
298
299
300        /**
301         * Deep copy constructor.
302         *
303         * @param plainHeader The unsecured header to copy. Must not be
304         *                    {@code null}.
305         */
306        public PlainHeader(final PlainHeader plainHeader) {
307
308                this(
309                        plainHeader.getType(),
310                        plainHeader.getContentType(),
311                        plainHeader.getCriticalParams(),
312                        plainHeader.getCustomParams(),
313                        plainHeader.getParsedBase64URL()
314                );
315        }
316
317
318        /**
319         * Gets the registered parameter names for unsecured headers.
320         *
321         * @return The registered parameter names, as an unmodifiable set.
322         */
323        public static Set<String> getRegisteredParameterNames() {
324
325                return REGISTERED_PARAMETER_NAMES;
326        }
327
328
329        /**
330         * Gets the algorithm ({@code alg}) parameter.
331         *
332         * @return {@link Algorithm#NONE}.
333         */
334        @Override
335        public Algorithm getAlgorithm() {
336
337                return Algorithm.NONE;
338        }
339
340
341        /**
342         * Parses an unsecured header from the specified JSON object.
343         *
344         * @param jsonObject The JSON object to parse. Must not be {@code null}.
345         *
346         * @return The unsecured header.
347         *
348         * @throws ParseException If the specified JSON object doesn't
349         *                        represent a valid unsecured header.
350         */
351        public static PlainHeader parse(final JSONObject jsonObject)
352                throws ParseException {
353
354                return parse(jsonObject, null);
355        }
356
357
358        /**
359         * Parses an unsecured header from the specified JSON object.
360         *
361         * @param jsonObject      The JSON object to parse. Must not be
362         *                        {@code null}.
363         * @param parsedBase64URL The original parsed Base64URL, {@code null}
364         *                        if not applicable.
365         *
366         * @return The unsecured header.
367         *
368         * @throws ParseException If the specified JSON object doesn't
369         *                        represent a valid unsecured header.
370         */
371        public static PlainHeader parse(final JSONObject jsonObject,
372                                        final Base64URL parsedBase64URL)
373                throws ParseException {
374
375                // Get the "alg" parameter
376                Algorithm alg = Header.parseAlgorithm(jsonObject);
377
378                if (alg != Algorithm.NONE) {
379                        throw new ParseException("The algorithm \"alg\" header parameter must be \"none\"", 0);
380                }
381
382                PlainHeader.Builder header = new Builder().parsedBase64URL(parsedBase64URL);
383
384                // Parse optional + custom parameters
385                for(final String name: jsonObject.keySet()) {
386
387
388                        
389                        if("alg".equals(name)) {
390                                // skip
391                        } else if("typ".equals(name)) {
392                                header = header.type(new JOSEObjectType(JSONObjectUtils.getString(jsonObject, name)));
393                        } else if("cty".equals(name)) {
394                                header = header.contentType(JSONObjectUtils.getString(jsonObject, name));
395                        } else if("crit".equals(name)) {
396                                header = header.criticalParams(new HashSet<>(JSONObjectUtils.getStringList(jsonObject, name)));
397                        } else {
398                                header = header.customParam(name, jsonObject.get(name));
399                        }
400                }
401
402                return header.build();
403        }
404
405
406        /**
407         * Parses an unsecured header from the specified JSON string.
408         *
409         * @param jsonString The JSON string to parse. Must not be
410         *                   {@code null}.
411         *
412         * @return The unsecured header.
413         *
414         * @throws ParseException If the specified JSON string doesn't
415         *                        represent a valid unsecured header.
416         */
417        public static PlainHeader parse(final String jsonString)
418                throws ParseException {
419
420                return parse(jsonString, null);
421        }
422
423
424        /**
425         * Parses an unsecured header from the specified JSON string.
426         *
427         * @param jsonString      The JSON string to parse. Must not be
428         *                        {@code null}.
429         * @param parsedBase64URL The original parsed Base64URL, {@code null}
430         *                        if not applicable.
431         *
432         * @return The unsecured header.
433         *
434         * @throws ParseException If the specified JSON string doesn't 
435         *                        represent a valid unsecured header.
436         */
437        public static PlainHeader parse(final String jsonString,
438                                        final Base64URL parsedBase64URL)
439                throws ParseException {
440
441                return parse(JSONObjectUtils.parseJSONObject(jsonString), parsedBase64URL);
442        }
443
444
445        /**
446         * Parses an unsecured header from the specified Base64URL.
447         *
448         * @param base64URL The Base64URL to parse. Must not be {@code null}.
449         *
450         * @return The unsecured header.
451         *
452         * @throws ParseException If the specified Base64URL doesn't represent
453         *                        a valid unsecured header.
454         */
455        public static PlainHeader parse(final Base64URL base64URL)
456                throws ParseException {
457
458                return parse(base64URL.decodeToString(), base64URL);
459        }
460}