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