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