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