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;
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. Must not
223         *               be {@code null}.
224         *
225         * @return The bearer access token.
226         *
227         * @throws ParseException If the HTTP Authorization header value 
228         *                        couldn't be parsed to a bearer access token.
229         */
230        public static BearerAccessToken parse(final String header)
231                throws ParseException {
232        
233                String[] parts = header.split("\\s", 2);
234        
235                if (parts.length != 2)
236                        throw new ParseException("Invalid HTTP Authorization header value");
237                
238                if (! parts[0].equals("Bearer"))
239                        throw new ParseException("Token type must be \"Bearer\"");
240                
241                try {
242                        return new BearerAccessToken(parts[1]);
243                        
244                } catch (IllegalArgumentException e) {
245                
246                        throw new ParseException(e.getMessage());
247                }
248        }
249        
250        
251        /**
252         * Parses an HTTP request for a bearer access token.
253         * 
254         * @param request The HTTP request to parse. Must be GET or POST, and
255         *                not {@code null}.
256         * 
257         * @return The bearer access token.
258         * 
259         * @throws ParseException If a bearer access token wasn't found in the
260         *                        HTTP request.
261         */
262        public static BearerAccessToken parse(final HTTPRequest request)
263                throws ParseException {
264                
265                if (request.getMethod().equals(HTTPRequest.Method.GET)) {
266                        
267                        String authzHeader = request.getAuthorization();
268                                
269                        if (authzHeader != null) {
270                                
271                                return parse(authzHeader);
272                        }
273                        
274                        Map<String,String> params = request.getQueryParameters(); 
275                        
276                        if (params.get("access_token") != null) {
277                                
278                                return parse(params.get("access_token"));
279                        }
280                        
281                        throw new ParseException("Missing Bearer access token");
282                        
283                } else if (request.getMethod().equals(HTTPRequest.Method.POST)) {
284
285                        ContentTypeUtils.ensureContentType(CommonContentTypes.APPLICATION_URLENCODED, request.getContentType());
286                        
287                        Map<String,String> params = request.getQueryParameters(); 
288                        
289                        if (params.get("access_token") != null) {
290                                
291                                return new BearerAccessToken(params.get("access_token"));
292                        }
293                        
294                        throw new ParseException("Missing Bearer access token");
295                        
296                } else {
297                        throw new ParseException("Unexpected HTTP method: " + request.getMethod());
298                }
299        }
300}