001package com.nimbusds.openid.connect.provider.spi.grants;
002
003
004import java.util.List;
005import java.util.Optional;
006
007import net.jcip.annotations.Immutable;
008import net.minidev.json.JSONObject;
009
010import com.nimbusds.oauth2.sdk.ParseException;
011import com.nimbusds.oauth2.sdk.id.Audience;
012import com.nimbusds.oauth2.sdk.id.Subject;
013import com.nimbusds.oauth2.sdk.token.TokenEncoding;
014import com.nimbusds.oauth2.sdk.util.CollectionUtils;
015import com.nimbusds.oauth2.sdk.util.JSONObjectUtils;
016import com.nimbusds.openid.connect.sdk.SubjectType;
017
018
019/**
020 * Access token specification.
021 */
022@Immutable
023public class AccessTokenSpec extends TokenSpec {
024
025
026        /**
027         * The default access token specification.
028         *
029         * <ul>
030         *     <li>No explicit token lifetime is specified, to let the
031         *         Connect2id server apply the default configured lifetime for
032         *         access tokens.
033         *     <li>No explicit token audience is specified.
034         *     <li>No subject in impersonation and delegation cases is
035         *         specified.
036         *     <li>The token is self-contained (JWT-encoded) and the optional
037         *         encryption preference is not set.
038         *     <li>The access token subject type is public.
039         * </ul>
040         */
041        public static final AccessTokenSpec DEFAULT = new AccessTokenSpec();
042
043
044        /**
045         * The access token encoding.
046         */
047        private final TokenEncoding encoding;
048        
049        
050        /**
051         * The optional encryption preference for self-contained access
052         * tokens.
053         */
054        private final Optional<Boolean> encryptSelfContained;
055        
056        
057        /**
058         * The access token subject type.
059         */
060        private final SubjectType subjectType;
061
062
063        /**
064         * Creates a new default access token specification. No explicit
065         * token lifetime is specified (to let the Connect2id server apply the
066         * default configured lifetime for access tokens). No explicit token
067         * audience is specified. No subject in impersonation and delegation
068         * cases is specified. The token is self-contained (JWT-encoded) and
069         * the optional encryption preference is not set. The access token
070         * subject type is public.
071         */
072        public AccessTokenSpec() {
073
074                this(0L, null, TokenEncoding.SELF_CONTAINED, null, Optional.empty(), SubjectType.PUBLIC);
075        }
076        
077        
078        /**
079         * Creates a new access token specification. No explicit token audience
080         * is specified. No subject in impersonation and delegation cases is
081         * specified. The access token subject type is public.
082         *
083         * @param lifetime The access token lifetime, in seconds, zero if not
084         *                 specified (to let the Connect2id server apply the
085         *                 default configured lifetime for access tokens).
086         * @param encoding The access token encoding. Must not be {@code null}.
087         * @param encrypt  If {@code true} flags the access token for
088         *                 encryption. Applies to self-contained (JWT) access
089         *                 tokens only.
090         */
091        @Deprecated
092        public AccessTokenSpec(final long lifetime,
093                               final TokenEncoding encoding,
094                               final boolean encrypt) {
095                
096                this(lifetime, null, encoding, null, encrypt);
097        }
098        
099        
100        /**
101         * Creates a new access token specification. No subject in
102         * impersonation and delegation cases is specified. The access token
103         * subject type is public.
104         *
105         * @param lifetime The access token lifetime, in seconds, zero if not
106         *                 specified (to let the Connect2id server apply the
107         *                 default configured lifetime for access tokens).
108         * @param audList  Explicit list of audiences for the access token,
109         *                 {@code null} if not specified.
110         * @param encoding The access token encoding. Must not be {@code null}.
111         * @param encrypt  If {@code true} flags the access token for
112         *                 encryption. Applies to self-contained (JWT) access
113         *                 tokens only.
114         */
115        @Deprecated
116        public AccessTokenSpec(final long lifetime,
117                               final List<Audience> audList,
118                               final TokenEncoding encoding,
119                               final boolean encrypt) {
120                
121                this(lifetime, audList, encoding, null, encrypt);
122        }
123        
124        
125        /**
126         * Creates a new access token specification. The access token subject
127         * type is public.
128         *
129         * @param lifetime            The access token lifetime, in seconds,
130         *                            zero if not specified (to let the
131         *                            Connect2id server apply the default
132         *                            configured lifetime for access tokens).
133         * @param audList             Explicit list of audiences for the access
134         *                            token, {@code null} if not specified.
135         * @param encoding            The access token encoding. Must not be
136         *                            {@code null}.
137         * @param impersonatedSubject The subject in impersonation and
138         *                            delegation cases, {@code null} if not
139         *                            applicable.
140         * @param encrypt             If {@code true} flags the access token
141         *                            for encryption. Applies to self-contained
142         *                            (JWT) access tokens only.
143         */
144        @Deprecated
145        public AccessTokenSpec(final long lifetime,
146                               final List<Audience> audList,
147                               final TokenEncoding encoding,
148                               final Subject impersonatedSubject,
149                               final boolean encrypt) {
150
151                this(lifetime, audList, encoding, impersonatedSubject, Optional.of(encrypt), SubjectType.PUBLIC);
152        }
153        
154        
155        /**
156         * Creates a new access token specification.
157         *
158         * @param lifetime            The access token lifetime, in seconds,
159         *                            zero if not specified (to let the
160         *                            Connect2id server apply the default
161         *                            configured lifetime for access tokens).
162         * @param audList             Explicit list of audiences for the access
163         *                            token, {@code null} if not specified.
164         * @param encoding            The access token encoding. Must not be
165         *                            {@code null}.
166         * @param impersonatedSubject The subject in impersonation and
167         *                            delegation cases, {@code null} if not
168         *                            applicable.
169         * @param encrypt             If {@code true} flags the access token
170         *                            for encryption. Applies to self-contained
171         *                            (JWT) access tokens only.
172         * @param subjectType         The access token subject type.
173         */
174        @Deprecated
175        public AccessTokenSpec(final long lifetime,
176                               final List<Audience> audList,
177                               final TokenEncoding encoding,
178                               final Subject impersonatedSubject,
179                               final boolean encrypt,
180                               final SubjectType subjectType) {
181
182                this(lifetime, audList, encoding, impersonatedSubject, Optional.of(encrypt), subjectType);
183        }
184        
185        
186        /**
187         * Creates a new access token specification. No subject in
188         * impersonation and delegation cases is specified. The access token
189         * subject type is public.
190         *
191         * @param lifetime             The access token lifetime, in seconds,
192         *                             zero if not specified (to let the
193         *                             Connect2id server apply the default
194         *                             configured lifetime for access tokens).
195         * @param audList              Explicit list of audiences for the
196         *                             access token, {@code null} if not
197         *                             specified.
198         * @param encoding             The access token encoding. Must not be
199         *                             {@code null}.
200         * @param encryptSelfContained The optional encryption preference for
201         *                             self-contained (JWT) access tokens.
202         *                             Must not be {@code null}.
203         */
204        public AccessTokenSpec(final long lifetime,
205                               final List<Audience> audList,
206                               final TokenEncoding encoding,
207                               final Optional<Boolean> encryptSelfContained) {
208                
209                this(lifetime, audList, encoding, null, encryptSelfContained, SubjectType.PUBLIC);
210        }
211
212
213        /**
214         * Creates a new access token specification.
215         *
216         * @param lifetime             The access token lifetime, in seconds,
217         *                             zero if not specified (to let the
218         *                             Connect2id server apply the default
219         *                             configured lifetime for access tokens).
220         * @param audList              Explicit list of audiences for the
221         *                             access token, {@code null} if not
222         *                             specified.
223         * @param encoding             The access token encoding. Must not be
224         *                             {@code null}.
225         * @param impersonatedSubject  The subject in impersonation and
226         *                             delegation cases, {@code null} if not
227         *                             applicable.
228         * @param encryptSelfContained The optional encryption preference for
229         *                             self-contained (JWT) access tokens.
230         *                             Must not be {@code null}.
231         * @param subjectType          The access token subject type.
232         */
233        public AccessTokenSpec(final long lifetime,
234                               final List<Audience> audList,
235                               final TokenEncoding encoding,
236                               final Subject impersonatedSubject,
237                               final Optional<Boolean> encryptSelfContained,
238                               final SubjectType subjectType) {
239
240                super(lifetime, audList, impersonatedSubject);
241
242                if (encoding == null) {
243                        throw new IllegalArgumentException("The access token encoding must not be null");
244                }
245
246                this.encoding = encoding;
247
248                if (encryptSelfContained == null) {
249                        throw new IllegalArgumentException("The optional encrypt self-contained must not be null");
250                }
251                // Only JWT tokens may be encrypted
252                this.encryptSelfContained = TokenEncoding.SELF_CONTAINED.equals(encoding) ? encryptSelfContained : Optional.empty();
253                
254                if (subjectType == null) {
255                        throw new IllegalArgumentException("The access token subject type must not be null");
256                }
257                if (SubjectType.PAIRWISE.equals(subjectType) && CollectionUtils.isEmpty(audList)) {
258                        throw new IllegalArgumentException("The pairwise token subject type requires an explicit token audience");
259                }
260                this.subjectType = subjectType;
261        }
262
263
264        /**
265         * Returns the access token encoding.
266         *
267         * @return The access token encoding.
268         */
269        public TokenEncoding getEncoding() {
270
271                return encoding;
272        }
273
274
275        /**
276         * Returns the access token encryption flag.
277         *
278         * @return If {@code true} the access token is flagged for encryption.
279         *         Applies to self-contained access tokens only.
280         *
281         * @deprecated Use {@link #getEncryptSelfContained} instead.
282         */
283        @Deprecated
284        public boolean encrypt() {
285
286                return encryptSelfContained.orElse(false);
287        }
288        
289        
290        /**
291         * Returns the optional encryption preference for self-contained (JWT)
292         * access tokens.
293         *
294         * @return The encryption preference.
295         */
296        public Optional<Boolean> getEncryptSelfContained() {
297                
298                return encryptSelfContained;
299        }
300        
301        
302        /**
303         * Returns the access token subject type.
304         *
305         * @return The subject type.
306         */
307        public SubjectType getSubjectType() {
308                
309                return subjectType;
310        }
311        
312        
313        @Override
314        public JSONObject toJSONObject() {
315
316                JSONObject o = super.toJSONObject();
317                
318                if (getLifetime() <= 0) {
319                        // Implies not specified - remove
320                        o.remove("lifetime");
321                }
322
323                o.put("encoding", encoding.toString());
324
325                if (encoding.equals(TokenEncoding.SELF_CONTAINED) && encryptSelfContained.isPresent()) {
326                        o.put("encrypt", encryptSelfContained.get());
327                }
328                
329                o.put("sub_type", getSubjectType().toString().toUpperCase());
330
331                return o;
332        }
333
334
335        /**
336         * Parses an access token specification from the specified JSON object.
337         *
338         * @param jsonObject The JSON object. Must not be {@code null}.
339         *
340         * @return The access token specification.
341         *
342         * @throws ParseException If parsing failed.
343         */
344        public static AccessTokenSpec parse(final JSONObject jsonObject)
345                throws ParseException {
346
347                TokenSpec tokenSpec = TokenSpec.parse(jsonObject);
348                
349                // Adjust lifetime value for not specified (0)
350                long lifetime = tokenSpec.getLifetime() > 0 ? tokenSpec.getLifetime() : 0;
351
352                TokenEncoding encoding = TokenEncoding.SELF_CONTAINED;
353                
354                Optional<Boolean> encryptSelfContained = Optional.empty();
355
356                if (jsonObject.get("encoding") != null) {
357
358                        String c = JSONObjectUtils.getString(jsonObject, "encoding");
359
360                        try {
361                                encoding = TokenEncoding.valueOf(c.toUpperCase());
362
363                        } catch (IllegalArgumentException e) {
364
365                                throw new ParseException("Invalid access token encoding");
366                        }
367                }
368
369                if (encoding.equals(TokenEncoding.SELF_CONTAINED)) {
370                        if (jsonObject.get("encrypt") != null) {
371                                encryptSelfContained = Optional.of(JSONObjectUtils.getBoolean(jsonObject, "encrypt"));
372                        }
373                }
374                
375                SubjectType subjectType = JSONObjectUtils.getEnum(jsonObject, "sub_type", SubjectType.class, SubjectType.PUBLIC);
376
377                try {
378                        return new AccessTokenSpec(
379                                lifetime,
380                                tokenSpec.getAudience(),
381                                encoding,
382                                tokenSpec.getImpersonatedSubject(),
383                                encryptSelfContained,
384                                subjectType
385                        );
386                } catch (IllegalArgumentException e) {
387                        throw new ParseException(e.getMessage(), e);
388                }
389        }
390}