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.List;
022import java.util.Map;
023
024import com.nimbusds.oauth2.sdk.ParseException;
025import com.nimbusds.oauth2.sdk.Scope;
026import com.nimbusds.oauth2.sdk.http.HTTPRequest;
027import com.nimbusds.oauth2.sdk.util.JSONObjectUtils;
028import com.nimbusds.oauth2.sdk.util.MultivaluedMapUtils;
029import com.nimbusds.oauth2.sdk.util.StringUtils;
030import net.jcip.annotations.Immutable;
031import net.minidev.json.JSONObject;
032
033
034/**
035 * Bearer access token.
036 *
037 * <p>Example bearer access token serialised to JSON:
038 *
039 * <pre>
040 * {
041 *   "access_token" : "2YotnFZFEjr1zCsicMWpAA",
042 *   "token_type"   : "bearer",
043 *   "expires_in"   : 3600,
044 *   "scope"        : "read write"
045 * }
046 * </pre>
047 *
048 * <p>The above example token serialised to a HTTP Authorization header:
049 *
050 * <pre>
051 * Authorization: Bearer 2YotnFZFEjr1zCsicMWpAA
052 * </pre>
053 *
054 * <p>Related specifications:
055 *
056 * <ul>
057 *     <li>OAuth 2.0 (RFC 6749), sections 1.4 and 5.1.
058 *     <li>OAuth 2.0 Bearer Token Usage (RFC 6750).
059 * </ul>
060 */
061@Immutable
062public class BearerAccessToken extends AccessToken {
063
064        
065        /**
066         * Creates a new minimal bearer access token with a randomly generated 
067         * 256-bit (32-byte) value, Base64URL-encoded. The optional lifetime 
068         * and scope are left undefined.
069         */
070        public BearerAccessToken() {
071        
072                this(32);
073        }       
074
075
076        /**
077         * Creates a new minimal bearer access token with a randomly generated 
078         * value of the specified byte length, Base64URL-encoded. The optional 
079         * lifetime and scope are left undefined.
080         *
081         * @param byteLength The byte length of the value to generate. Must be
082         *                   greater than one.
083         */
084        public BearerAccessToken(final int byteLength) {
085        
086                this(byteLength, 0L, null);
087        }
088
089
090        /**
091         * Creates a new bearer access token with a randomly generated 256-bit 
092         * (32-byte) value, Base64URL-encoded.
093         *
094         * @param lifetime The lifetime in seconds, 0 if not specified.
095         * @param scope    The scope, {@code null} if not specified.
096         */
097        public BearerAccessToken(final long lifetime, final Scope scope) {
098        
099                this(32, lifetime, scope);
100        }
101
102
103        /**
104         * Creates a new bearer access token with a randomly generated value of 
105         * the specified byte length, Base64URL-encoded.
106         *
107         * @param byteLength The byte length of the value to generate. Must be
108         *                   greater than one.
109         * @param lifetime   The lifetime in seconds, 0 if not specified.
110         * @param scope      The scope, {@code null} if not specified.
111         */
112        public BearerAccessToken(final int byteLength, final long lifetime, final Scope scope) {
113        
114                super(AccessTokenType.BEARER, byteLength, lifetime, scope);
115        }
116        
117        
118        /**
119         * Creates a new minimal bearer access token with the specified value.
120         * The optional lifetime and scope are left undefined.
121         *
122         * @param value The access token value. Must not be {@code null} or
123         *              empty string.
124         */
125        public BearerAccessToken(final String value) {
126        
127                this(value, 0L, null);
128        }
129        
130        
131        /**
132         * Creates a new bearer access token with the specified value and 
133         * optional lifetime and scope.
134         *
135         * @param value    The access token value. Must not be {@code null} or
136         *                 empty string.
137         * @param lifetime The lifetime in seconds, 0 if not specified.
138         * @param scope    The scope, {@code null} if not specified.
139         */
140        public BearerAccessToken(final String value, final long lifetime, final Scope scope) {
141        
142                super(AccessTokenType.BEARER, value, lifetime, scope);
143        }
144        
145        
146        /**
147         * Returns the HTTP Authorization header value for this bearer access 
148         * token.
149         *
150         * <p>Example:
151         *
152         * <pre>
153         * Authorization: Bearer eyJhbGciOiJIUzI1NiJ9
154         * </pre>
155         *
156         * @return The HTTP Authorization header.
157         */
158        @Override
159        public String toAuthorizationHeader(){
160        
161                return "Bearer " + getValue();
162        }
163        
164        
165        @Override
166        public boolean equals(final Object object) {
167        
168                return object instanceof BearerAccessToken &&
169                       this.toString().equals(object.toString());
170        }
171
172
173        /**
174         * Parses a bearer access token from a JSON object access token 
175         * response.
176         *
177         * @param jsonObject The JSON object to parse. Must not be 
178         *                   {@code null}.
179         *
180         * @return The bearer access token.
181         *
182         * @throws ParseException If the JSON object couldn't be parsed to a
183         *                        bearer access token.
184         */
185        public static BearerAccessToken parse(final JSONObject jsonObject)
186                throws ParseException {
187
188                // Parse and verify type
189                AccessTokenType tokenType = new AccessTokenType(JSONObjectUtils.getString(jsonObject, "token_type"));
190                
191                if (! tokenType.equals(AccessTokenType.BEARER))
192                        throw new ParseException("Token type must be \"Bearer\"");
193                
194
195                // Parse value
196                String accessTokenValue = JSONObjectUtils.getString(jsonObject, "access_token");
197                
198
199                // Parse lifetime
200                long lifetime = 0;
201                
202                if (jsonObject.containsKey("expires_in")) {
203
204                        // Lifetime can be a JSON number or string
205
206                        if (jsonObject.get("expires_in") instanceof Number) {
207
208                                lifetime = JSONObjectUtils.getLong(jsonObject, "expires_in");
209                        }
210                        else {
211                                String lifetimeStr = JSONObjectUtils.getString(jsonObject, "expires_in");
212
213                                try {
214                                        lifetime = new Long(lifetimeStr);
215
216                                } catch (NumberFormatException e) {
217
218                                        throw new ParseException("Invalid \"expires_in\" parameter, must be integer");
219                                }
220                        }
221                }
222
223
224                // Parse scope
225                Scope scope = null;
226
227                if (jsonObject.containsKey("scope"))
228                        scope = Scope.parse(JSONObjectUtils.getString(jsonObject, "scope"));
229
230
231                return new BearerAccessToken(accessTokenValue, lifetime, scope);
232        }
233        
234        
235        /**
236         * Parses an HTTP Authorization header for a bearer access token.
237         *
238         * @param header The HTTP Authorization header value to parse. May be
239         *               {@code null} if the header is missing, in which case
240         *               an exception will be thrown.
241         *
242         * @return The bearer access token.
243         *
244         * @throws ParseException If the HTTP Authorization header value 
245         *                        couldn't be parsed to a bearer access token.
246         */
247        public static BearerAccessToken parse(final String header)
248                throws ParseException {
249
250                if (StringUtils.isBlank(header))
251                        throw new ParseException("Missing HTTP Authorization header", BearerTokenError.MISSING_TOKEN);
252        
253                String[] parts = header.split("\\s", 2);
254        
255                if (parts.length != 2)
256                        throw new ParseException("Invalid HTTP Authorization header value", BearerTokenError.INVALID_REQUEST);
257                
258                if (! parts[0].equals("Bearer"))
259                        throw new ParseException("Token type must be \"Bearer\"", BearerTokenError.INVALID_REQUEST);
260                
261                try {
262                        return new BearerAccessToken(parts[1]);
263                        
264                } catch (IllegalArgumentException e) {
265                
266                        throw new ParseException(e.getMessage(), BearerTokenError.INVALID_REQUEST);
267                }
268        }
269        
270        
271        /**
272         * Parses a query or form parameters map for a bearer access token.
273         *
274         * @param parameters The query parameters. Must not be {@code null}.
275         *
276         * @return The bearer access token.
277         *
278         * @throws ParseException If a bearer access token wasn't found in the
279         *                        parameters.
280         */
281        public static BearerAccessToken parse(final Map<String,List<String>> parameters)
282                throws ParseException {
283                
284                if (! parameters.containsKey("access_token")) {
285                        throw new ParseException("Missing access token parameter", BearerTokenError.MISSING_TOKEN);
286                }
287                
288                String accessTokenValue = MultivaluedMapUtils.getFirstValue(parameters, "access_token");
289                
290                if (StringUtils.isBlank(accessTokenValue)) {
291                        throw new ParseException("Blank / empty access token", BearerTokenError.INVALID_REQUEST);
292                }
293                
294                return new BearerAccessToken(accessTokenValue);
295        }
296        
297        
298        
299        /**
300         * Parses an HTTP request for a bearer access token.
301         * 
302         * @param request The HTTP request to parse. Must not be {@code null}.
303         * 
304         * @return The bearer access token.
305         * 
306         * @throws ParseException If a bearer access token wasn't found in the
307         *                        HTTP request.
308         */
309        public static BearerAccessToken parse(final HTTPRequest request)
310                throws ParseException {
311
312                // See http://tools.ietf.org/html/rfc6750#section-2
313
314                String authzHeader = request.getAuthorization();
315
316                if (authzHeader != null) {
317
318                        return parse(authzHeader);
319                }
320
321                // Try alternative token locations, form and query string are
322                // parameters are not differentiated here
323
324                Map<String,List<String>> params = request.getQueryParameters();
325                        
326                return parse(params);
327        }
328}