001package com.nimbusds.oauth2.sdk.token;
002
003
004import net.jcip.annotations.Immutable;
005
006import net.minidev.json.JSONObject;
007
008import com.nimbusds.oauth2.sdk.ParseException;
009import com.nimbusds.oauth2.sdk.Scope;
010import com.nimbusds.oauth2.sdk.http.HTTPRequest;
011import com.nimbusds.oauth2.sdk.util.JSONObjectUtils;
012import java.util.Map;
013
014
015/**
016 * Bearer access token. This class is immutable.
017 *
018 * <p>Example bearer access token serialised to JSON:
019 *
020 * <pre>
021 * {
022 *   "access_token" : "2YotnFZFEjr1zCsicMWpAA",
023 *   "token_type"   : "bearer",
024 *   "expires_in"   : 3600,
025 *   "scope"        : "read write"
026 * }
027 * </pre>
028 *
029 * <p>The above example token serialised to a HTTP Authorization header:
030 *
031 * <pre>
032 * Authorization: Bearer 2YotnFZFEjr1zCsicMWpAA
033 * </pre>
034 *
035 * <p>Related specifications:
036 *
037 * <ul>
038 *     <li>OAuth 2.0 (RFC 6749), sections 1.4 and 5.1.
039 *     <li>OAuth 2.0 Bearer Token Usage (RFC 6750).
040 * </ul>
041 *
042 * @author Vladimir Dzhuvinov
043 */
044@Immutable
045public final class BearerAccessToken extends AccessToken {
046
047        
048        /**
049         * Creates a new minimal bearer access token with a randomly generated 
050         * 256-bit (32-byte) value, Base64URL-encoded. The optional lifetime 
051         * and scope are left undefined.
052         */
053        public BearerAccessToken() {
054        
055                this(32);
056        }       
057
058
059        /**
060         * Creates a new minimal bearer access token with a randomly generated 
061         * value of the specified byte length, Base64URL-encoded. The optional 
062         * lifetime and scope are left undefined.
063         *
064         * @param byteLength The byte length of the value to generate. Must be
065         *                   greater than one.
066         */
067        public BearerAccessToken(final int byteLength) {
068        
069                this(byteLength, 0l, null);
070        }
071
072
073        /**
074         * Creates a new bearer access token with a randomly generated 256-bit 
075         * (32-byte) value, Base64URL-encoded.
076         *
077         * @param lifetime The lifetime in seconds, 0 if not specified.
078         * @param scope    The scope, {@code null} if not specified.
079         */
080        public BearerAccessToken(final long lifetime, final Scope scope) {
081        
082                this(32, lifetime, scope);
083        }
084
085
086        /**
087         * Creates a new bearer access token with a randomly generated value of 
088         * the specified byte length, Base64URL-encoded.
089         *
090         * @param byteLength The byte length of the value to generate. Must be
091         *                   greater than one.
092         * @param lifetime   The lifetime in seconds, 0 if not specified.
093         * @param scope      The scope, {@code null} if not specified.
094         */
095        public BearerAccessToken(final int byteLength, final long lifetime, final Scope scope) {
096        
097                super(AccessTokenType.BEARER, byteLength, lifetime, scope);
098        }
099        
100        
101        /**
102         * Creates a new minimal bearer access token with the specified value.
103         * The optional lifetime and scope are left undefined.
104         *
105         * @param value The access token value. Must not be {@code null} or
106         *              empty string.
107         */
108        public BearerAccessToken(final String value) {
109        
110                this(value, 0l, null);
111        }
112        
113        
114        /**
115         * Creates a new bearer access token with the specified value and 
116         * optional lifetime and scope.
117         *
118         * @param value    The access token value. Must not be {@code null} or
119         *                 empty string.
120         * @param lifetime The lifetime in seconds, 0 if not specified.
121         * @param scope    The scope, {@code null} if not specified.
122         */
123        public BearerAccessToken(final String value, final long lifetime, final Scope scope) {
124        
125                super(AccessTokenType.BEARER, value, lifetime, scope);
126        }
127        
128        
129        /**
130         * Returns the HTTP Authorization header value for this bearer access 
131         * token.
132         *
133         * <p>Example:
134         *
135         * <pre>
136         * Authorization: Bearer eyJhbGciOiJIUzI1NiJ9
137         * </pre>
138         *
139         * @return The HTTP Authorization header.
140         */
141        @Override
142        public String toAuthorizationHeader(){
143        
144                return "Bearer " + getValue();
145        }
146        
147        
148        @Override
149        public boolean equals(final Object object) {
150        
151                return object != null && 
152                       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                        Map<String,String> params = request.getQueryParameters(); 
286                        
287                        if (params.get("access_token") != null) {
288                                
289                                return parse(params.get("access_token"));
290                        }
291                        
292                        throw new ParseException("Missing Bearer access token");
293                        
294                } else {
295                        throw new ParseException("Unexpected HTTP method: " + request.getMethod());
296                }
297        }
298}