001/*
002 * oauth2-oidc-sdk
003 *
004 * Copyright 2012-2016, Connect2id Ltd and contributors.
005 *
006 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use
007 * this file except in compliance with the License. You may obtain a copy of the
008 * License at
009 *
010 *    http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software distributed
013 * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
014 * CONDITIONS OF ANY KIND, either express or implied. See the License for the
015 * specific language governing permissions and limitations under the License.
016 */
017
018package com.nimbusds.oauth2.sdk.token;
019
020
021import java.util.HashSet;
022import java.util.Set;
023
024import net.minidev.json.JSONObject;
025
026import com.nimbusds.oauth2.sdk.ParseException;
027import com.nimbusds.oauth2.sdk.Scope;
028import com.nimbusds.oauth2.sdk.util.JSONObjectUtils;
029
030
031/**
032 * The base abstract class for access tokens. Concrete extending classes should
033 * be immutable.
034 *
035 * <p>Related specifications:
036 *
037 * <ul>
038 *     <li>OAuth 2.0 (RFC 6749), sections 1.4 and 5.1.
039 * </ul>
040 */
041public abstract class AccessToken extends Token {
042        
043        
044        private static final long serialVersionUID = 2947643641344083799L;
045        
046        
047        /**
048         * The access token type.
049         */
050        private final AccessTokenType type;
051        
052        
053        /**
054         * Optional lifetime, in seconds.
055         */
056        private final long lifetime;
057        
058        
059        /**
060         * Optional scope.
061         */
062        private final Scope scope;
063
064
065        /**
066         * Creates a new minimal access token with a randomly generated 256-bit 
067         * (32-byte) value, Base64URL-encoded. The optional lifetime and scope 
068         * are left undefined.
069         *
070         * @param type The access token type. Must not be {@code null}.
071         */
072        public AccessToken(final AccessTokenType type) {
073        
074                this(type, 32);
075        }
076
077
078        /**
079         * Creates a new minimal access token with a randomly generated value 
080         * of the specified byte length, Base64URL-encoded. The optional 
081         * lifetime and scope are left undefined.
082         *
083         * @param type       The access token type. Must not be {@code null}.
084         * @param byteLength The byte length of the value to generate. Must be
085         *                   greater than one.
086         */
087        public AccessToken(final AccessTokenType type, final int byteLength) {
088        
089                this(type, byteLength, 0L, null);
090        }
091
092
093        /**
094         * Creates a new access token with a randomly generated 256-bit 
095         * (32-byte) value, Base64URL-encoded.
096         *
097         * @param type     The access token type. Must not be {@code null}.
098         * @param lifetime The lifetime in seconds, 0 if not specified.
099         * @param scope    The scope, {@code null} if not specified.
100         */
101        public AccessToken(final AccessTokenType type,
102                           final long lifetime, 
103                           final Scope scope) {
104        
105                this(type, 32, lifetime, scope);
106        }
107
108
109        /**
110         * Creates a new access token with a randomly generated value 
111         * of the specified byte length, Base64URL-encoded, and optional 
112         * lifetime and scope.
113         *
114         * @param type       The access token type. Must not be {@code null}.
115         * @param byteLength The byte length of the value to generate. Must be
116         *                   greater than one.
117         * @param lifetime   The lifetime in seconds, 0 if not specified.
118         * @param scope      The scope, {@code null} if not specified.
119         */
120        public AccessToken(final AccessTokenType type, 
121                           final int byteLength, 
122                           final long lifetime, 
123                           final Scope scope) {
124        
125                super(byteLength);
126
127                if (type == null)
128                        throw new IllegalArgumentException("The access token type must not be null");
129
130                this.type = type;
131
132                this.lifetime = lifetime;
133                this.scope = scope;
134        }
135        
136        
137        /**
138         * Creates a new minimal access token with the specified value. The 
139         * optional lifetime and scope are left undefined.
140         *
141         * @param type  The access token type. Must not be {@code null}.
142         * @param value The access token value. Must not be {@code null} or
143         *              empty string.
144         */
145        public AccessToken(final AccessTokenType type, final String value) {
146        
147                this(type, value, 0L, null);
148        }
149        
150        
151        /**
152         * Creates a new access token with the specified value and optional 
153         * lifetime and scope.
154         *
155         * @param type     The access token type. Must not be {@code null}.
156         * @param value    The access token value. Must not be {@code null} or
157         *                 empty string.
158         * @param lifetime The lifetime in seconds, 0 if not specified.
159         * @param scope    The scope, {@code null} if not specified.
160         */
161        public AccessToken(final AccessTokenType type, 
162                           final String value, 
163                           final long lifetime, 
164                           final Scope scope) {
165        
166                super(value);
167
168                if (type == null)
169                        throw new IllegalArgumentException("The access token type must not be null");
170
171                this.type = type;
172
173                this.lifetime = lifetime;
174                this.scope = scope;
175        }
176
177
178        /**
179         * Returns the access token type.
180         *
181         * @return The access token type.
182         */
183        public AccessTokenType getType() {
184
185                return type;
186        }
187
188        
189        /**
190         * Returns the lifetime of this access token.
191         *
192         * @return The lifetime in seconds, 0 if not specified.
193         */
194        public long getLifetime() {
195        
196                return lifetime;
197        }
198        
199        
200        /**
201         * Returns the scope of this access token.
202         *
203         * @return The scope, {@code null} if not specified.
204         */
205        public Scope getScope() {
206        
207                return scope;
208        }
209
210
211        @Override
212        public Set<String> getParameterNames() {
213
214                Set<String> paramNames = new HashSet<>();
215                paramNames.add("access_token");
216                paramNames.add("token_type");
217
218                if (getLifetime() > 0)
219                        paramNames.add("expires_in");
220
221                if (getScope() != null)
222                        paramNames.add("scope");
223
224                return paramNames;
225        }
226
227
228        @Override
229        public JSONObject toJSONObject() {
230
231                JSONObject o = new JSONObject();
232
233                o.put("access_token", getValue());
234                o.put("token_type", type.toString());
235                
236                if (getLifetime() > 0)
237                        o.put("expires_in", lifetime);
238
239                if (getScope() != null)
240                        o.put("scope", scope.toString());
241                
242                return o;
243        }
244
245
246        @Override
247        public String toJSONString() {
248
249                return toJSONObject().toString();
250        }
251        
252        
253        /**
254         * Returns the {@code Authorization} HTTP request header value for this
255         * access token.
256         *
257         * @return The {@code Authorization} header value.
258         */
259        public abstract String toAuthorizationHeader();
260
261
262        /**
263         * Parses an access token from a JSON object access token response.
264         * Only bearer and DPoP access tokens are supported.
265         *
266         * @param jsonObject The JSON object to parse. Must not be 
267         *                   {@code null}.
268         *
269         * @return The access token.
270         *
271         * @throws ParseException If the JSON object couldn't be parsed to an
272         *                        access token.
273         */
274        public static AccessToken parse(final JSONObject jsonObject)
275                throws ParseException {
276
277                AccessTokenType tokenType = new AccessTokenType(JSONObjectUtils.getString(jsonObject, "token_type"));
278                
279                if (AccessTokenType.BEARER.equals(tokenType)) {
280                        return BearerAccessToken.parse(jsonObject);
281                } else if (AccessTokenType.DPOP.equals(tokenType)){
282                        return DPoPAccessToken.parse(jsonObject);
283                } else {
284                        throw new ParseException("Unsupported token_type: " + tokenType);
285                }
286        }
287        
288        
289        /**
290         * Parses an {@code Authorization} HTTP request header value for an 
291         * access token. Only bearer access token are supported.
292         *
293         * @param header The {@code Authorization} header value to parse. Must 
294         *               not be {@code null}.
295         *
296         * @return The access token.
297         *
298         * @throws ParseException If the {@code Authorization} header value 
299         *                        couldn't be parsed to an access token.
300         *
301         * @see #parse(String, AccessTokenType)
302         */
303        @Deprecated
304        public static AccessToken parse(final String header)
305                throws ParseException {
306        
307                return BearerAccessToken.parse(header);
308        }
309        
310        
311        /**
312         * Parses an {@code Authorization} HTTP request header value for an
313         * access token. Only bearer and DPoP access token are supported.
314         *
315         * @param header        The {@code Authorization} header value to
316         *                      parse. Must not be {@code null}.
317         * @param preferredType The preferred (primary) access token type.
318         *                      Must be either {@link AccessTokenType#BEARER}
319         *                      or {@link AccessTokenType#DPOP} and not
320         *                      {@code null}.
321         *
322         * @return The access token.
323         *
324         * @throws ParseException If the {@code Authorization} header value
325         *                        couldn't be parsed to an access token.
326         */
327        public static AccessToken parse(final String header,
328                                        final AccessTokenType preferredType)
329                throws ParseException {
330        
331                if (! AccessTokenType.BEARER.equals(preferredType) && ! AccessTokenType.DPOP.equals(preferredType)) {
332                        throw new IllegalArgumentException("Unsupported Authorization scheme: " + preferredType);
333                }
334                
335                if (header != null && header.startsWith(AccessTokenType.BEARER.getValue()) || AccessTokenType.BEARER.equals(preferredType)) {
336                        return BearerAccessToken.parse(header);
337                } else {
338                        return DPoPAccessToken.parse(header);
339                }
340        }
341}