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 instanceof BearerAccessToken &&
152                       this.toString().equals(object.toString());
153        }
154
155
156        /**
157         * Parses a bearer access token from a JSON object access token 
158         * response.
159         *
160         * @param jsonObject The JSON object to parse. Must not be 
161         *                   {@code null}.
162         *
163         * @return The bearer access token.
164         *
165         * @throws ParseException If the JSON object couldn't be parsed to a
166         *                        bearer access token.
167         */
168        public static BearerAccessToken parse(final JSONObject jsonObject)
169                throws ParseException {
170
171                // Parse and verify type
172                AccessTokenType tokenType = new AccessTokenType(JSONObjectUtils.getString(jsonObject, "token_type"));
173                
174                if (! tokenType.equals(AccessTokenType.BEARER))
175                        throw new ParseException("Token type must be \"Bearer\"");
176                
177
178                // Parse value
179                String accessTokenValue = JSONObjectUtils.getString(jsonObject, "access_token");
180                
181
182                // Parse lifetime
183                long lifetime = 0;
184                
185                if (jsonObject.containsKey("expires_in")) {
186
187                        // Lifetime can be a JSON number or string
188
189                        if (jsonObject.get("expires_in") instanceof Number) {
190
191                                lifetime = JSONObjectUtils.getLong(jsonObject, "expires_in");
192                        }
193                        else {
194                                String lifetimeStr = JSONObjectUtils.getString(jsonObject, "expires_in");
195
196                                try {
197                                        lifetime = new Long(lifetimeStr);
198
199                                } catch (NumberFormatException e) {
200
201                                        throw new ParseException("Invalid \"expires_in\" parameter, must be integer");
202                                }
203                        }
204                }
205
206
207                // Parse scope
208                Scope scope = null;
209
210                if (jsonObject.containsKey("scope"))
211                        scope = Scope.parse(JSONObjectUtils.getString(jsonObject, "scope"));
212
213
214                return new BearerAccessToken(accessTokenValue, lifetime, scope);
215        }
216        
217        
218        /**
219         * Parses an HTTP Authorization header for a bearer access token.
220         *
221         * @param header The HTTP Authorization header value to parse. Must not
222         *               be {@code null}.
223         *
224         * @return The bearer access token.
225         *
226         * @throws ParseException If the HTTP Authorization header value 
227         *                        couldn't be parsed to a bearer access token.
228         */
229        public static BearerAccessToken parse(final String header)
230                throws ParseException {
231        
232                String[] parts = header.split("\\s", 2);
233        
234                if (parts.length != 2)
235                        throw new ParseException("Invalid HTTP Authorization header value");
236                
237                if (! parts[0].equals("Bearer"))
238                        throw new ParseException("Token type must be \"Bearer\"");
239                
240                try {
241                        return new BearerAccessToken(parts[1]);
242                        
243                } catch (IllegalArgumentException e) {
244                
245                        throw new ParseException(e.getMessage());
246                }
247        }
248        
249        
250        /**
251         * Parses an HTTP request for a bearer access token.
252         * 
253         * @param request The HTTP request to parse. Must be GET or POST, and
254         *                not {@code null}.
255         * 
256         * @return The bearer access token.
257         * 
258         * @throws ParseException If a bearer access token wasn't found in the
259         *                        HTTP request.
260         */
261        public static BearerAccessToken parse(final HTTPRequest request)
262                throws ParseException {
263                
264                if (request.getMethod().equals(HTTPRequest.Method.GET)) {
265                        
266                        String authzHeader = request.getAuthorization();
267                                
268                        if (authzHeader != null) {
269                                
270                                return parse(authzHeader);
271                        }
272                        
273                        Map<String,String> params = request.getQueryParameters(); 
274                        
275                        if (params.get("access_token") != null) {
276                                
277                                return parse(params.get("access_token"));
278                        }
279                        
280                        throw new ParseException("Missing Bearer access token");
281                        
282                } else if (request.getMethod().equals(HTTPRequest.Method.POST)) {
283                        
284                        Map<String,String> params = request.getQueryParameters(); 
285                        
286                        if (params.get("access_token") != null) {
287                                
288                                return parse(params.get("access_token"));
289                        }
290                        
291                        throw new ParseException("Missing Bearer access token");
292                        
293                } else {
294                        throw new ParseException("Unexpected HTTP method: " + request.getMethod());
295                }
296        }
297}