001/* 002 * oauth2-oidc-sdk 003 * 004 * Copyright 2012-2016, Connect2id Ltd and contributors. 005 * 006 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use 007 * this file except in compliance with the License. You may obtain a copy of the 008 * License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, software distributed 013 * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 014 * CONDITIONS OF ANY KIND, either express or implied. See the License for the 015 * specific language governing permissions and limitations under the License. 016 */ 017 018package com.nimbusds.oauth2.sdk.token; 019 020 021import java.util.HashSet; 022import java.util.List; 023import java.util.Map; 024import java.util.Set; 025 026import net.minidev.json.JSONObject; 027 028import com.nimbusds.oauth2.sdk.ParseException; 029import com.nimbusds.oauth2.sdk.Scope; 030import com.nimbusds.oauth2.sdk.http.HTTPRequest; 031import com.nimbusds.oauth2.sdk.util.JSONObjectUtils; 032 033 034/** 035 * The base abstract class for access tokens. Concrete extending classes should 036 * be immutable. 037 * 038 * <p>Related specifications: 039 * 040 * <ul> 041 * <li>OAuth 2.0 (RFC 6749), sections 1.4 and 5.1. 042 * </ul> 043 */ 044public abstract class AccessToken extends Token { 045 046 047 private static final long serialVersionUID = 2947643641344083799L; 048 049 050 /** 051 * The access token type. 052 */ 053 private final AccessTokenType type; 054 055 056 /** 057 * Optional lifetime, in seconds. 058 */ 059 private final long lifetime; 060 061 062 /** 063 * Optional scope. 064 */ 065 private final Scope scope; 066 067 068 /** 069 * Creates a new minimal access token with a randomly generated 256-bit 070 * (32-byte) value, Base64URL-encoded. The optional lifetime and scope 071 * are left undefined. 072 * 073 * @param type The access token type. Must not be {@code null}. 074 */ 075 public AccessToken(final AccessTokenType type) { 076 077 this(type, 32); 078 } 079 080 081 /** 082 * Creates a new minimal access token with a randomly generated value 083 * of the specified byte length, Base64URL-encoded. The optional 084 * lifetime and scope are left undefined. 085 * 086 * @param type The access token type. Must not be {@code null}. 087 * @param byteLength The byte length of the value to generate. Must be 088 * greater than one. 089 */ 090 public AccessToken(final AccessTokenType type, final int byteLength) { 091 092 this(type, byteLength, 0L, null); 093 } 094 095 096 /** 097 * Creates a new access token with a randomly generated 256-bit 098 * (32-byte) value, Base64URL-encoded. 099 * 100 * @param type The access token type. Must not be {@code null}. 101 * @param lifetime The lifetime in seconds, 0 if not specified. 102 * @param scope The scope, {@code null} if not specified. 103 */ 104 public AccessToken(final AccessTokenType type, 105 final long lifetime, 106 final Scope scope) { 107 108 this(type, 32, lifetime, scope); 109 } 110 111 112 /** 113 * Creates a new access token with a randomly generated value 114 * of the specified byte length, Base64URL-encoded, and optional 115 * lifetime and scope. 116 * 117 * @param type The access token type. Must not be {@code null}. 118 * @param byteLength The byte length of the value to generate. Must be 119 * greater than one. 120 * @param lifetime The lifetime in seconds, 0 if not specified. 121 * @param scope The scope, {@code null} if not specified. 122 */ 123 public AccessToken(final AccessTokenType type, 124 final int byteLength, 125 final long lifetime, 126 final Scope scope) { 127 128 super(byteLength); 129 130 if (type == null) 131 throw new IllegalArgumentException("The access token type must not be null"); 132 133 this.type = type; 134 135 this.lifetime = lifetime; 136 this.scope = scope; 137 } 138 139 140 /** 141 * Creates a new minimal access token with the specified value. The 142 * optional lifetime and scope are left undefined. 143 * 144 * @param type The access token type. Must not be {@code null}. 145 * @param value The access token value. Must not be {@code null} or 146 * empty string. 147 */ 148 public AccessToken(final AccessTokenType type, final String value) { 149 150 this(type, value, 0L, null); 151 } 152 153 154 /** 155 * Creates a new access token with the specified value and optional 156 * lifetime and scope. 157 * 158 * @param type The access token type. Must not be {@code null}. 159 * @param value The access token value. Must not be {@code null} or 160 * empty string. 161 * @param lifetime The lifetime in seconds, 0 if not specified. 162 * @param scope The scope, {@code null} if not specified. 163 */ 164 public AccessToken(final AccessTokenType type, 165 final String value, 166 final long lifetime, 167 final Scope scope) { 168 169 super(value); 170 171 if (type == null) 172 throw new IllegalArgumentException("The access token type must not be null"); 173 174 this.type = type; 175 176 this.lifetime = lifetime; 177 this.scope = scope; 178 } 179 180 181 /** 182 * Returns the access token type. 183 * 184 * @return The access token type. 185 */ 186 public AccessTokenType getType() { 187 188 return type; 189 } 190 191 192 /** 193 * Returns the lifetime of this access token. 194 * 195 * @return The lifetime in seconds, 0 if not specified. 196 */ 197 public long getLifetime() { 198 199 return lifetime; 200 } 201 202 203 /** 204 * Returns the scope of this access token. 205 * 206 * @return The scope, {@code null} if not specified. 207 */ 208 public Scope getScope() { 209 210 return scope; 211 } 212 213 214 @Override 215 public Set<String> getParameterNames() { 216 217 Set<String> paramNames = new HashSet<>(); 218 paramNames.add("access_token"); 219 paramNames.add("token_type"); 220 221 if (getLifetime() > 0) 222 paramNames.add("expires_in"); 223 224 if (getScope() != null) 225 paramNames.add("scope"); 226 227 return paramNames; 228 } 229 230 231 @Override 232 public JSONObject toJSONObject() { 233 234 JSONObject o = new JSONObject(); 235 236 o.put("access_token", getValue()); 237 o.put("token_type", type.toString()); 238 239 if (getLifetime() > 0) 240 o.put("expires_in", lifetime); 241 242 if (getScope() != null) 243 o.put("scope", scope.toString()); 244 245 return o; 246 } 247 248 249 @Override 250 public String toJSONString() { 251 252 return toJSONObject().toString(); 253 } 254 255 256 /** 257 * Returns the {@code Authorization} HTTP request header value for this 258 * access token. 259 * 260 * @return The {@code Authorization} header value. 261 */ 262 public abstract String toAuthorizationHeader(); 263 264 265 /** 266 * Parses an access token from a JSON object access token response. 267 * Only bearer and DPoP access tokens are supported. 268 * 269 * @param jsonObject The JSON object to parse. Must not be 270 * {@code null}. 271 * 272 * @return The access token. 273 * 274 * @throws ParseException If the JSON object couldn't be parsed to an 275 * access token. 276 */ 277 public static AccessToken parse(final JSONObject jsonObject) 278 throws ParseException { 279 280 AccessTokenType tokenType = new AccessTokenType(JSONObjectUtils.getString(jsonObject, "token_type")); 281 282 if (AccessTokenType.BEARER.equals(tokenType)) { 283 return BearerAccessToken.parse(jsonObject); 284 } else if (AccessTokenType.DPOP.equals(tokenType)){ 285 return DPoPAccessToken.parse(jsonObject); 286 } else { 287 throw new ParseException("Unsupported token_type: " + tokenType); 288 } 289 } 290 291 292 /** 293 * Parses an {@code Authorization} HTTP request header value for an 294 * access token. Only bearer access token are supported. 295 * 296 * @param header The {@code Authorization} header value to parse. Must 297 * not be {@code null}. 298 * 299 * @return The access token. 300 * 301 * @throws ParseException If the {@code Authorization} header value 302 * couldn't be parsed to an access token. 303 * 304 * @see #parse(String, AccessTokenType) 305 */ 306 @Deprecated 307 public static AccessToken parse(final String header) 308 throws ParseException { 309 310 return BearerAccessToken.parse(header); 311 } 312 313 314 /** 315 * Parses an {@code Authorization} HTTP request header value for an 316 * access token. Only bearer and DPoP access token are supported. 317 * 318 * @param header The {@code Authorization} header value to 319 * parse. Must not be {@code null}. 320 * @param preferredType The preferred (primary) access token type. 321 * Must be either {@link AccessTokenType#BEARER} 322 * or {@link AccessTokenType#DPOP} and not 323 * {@code null}. 324 * 325 * @return The access token. 326 * 327 * @throws ParseException If the {@code Authorization} header value 328 * couldn't be parsed to an access token. 329 */ 330 public static AccessToken parse(final String header, 331 final AccessTokenType preferredType) 332 throws ParseException { 333 334 if (! AccessTokenType.BEARER.equals(preferredType) && ! AccessTokenType.DPOP.equals(preferredType)) { 335 throw new IllegalArgumentException("Unsupported Authorization scheme: " + preferredType); 336 } 337 338 if (header != null && header.startsWith(AccessTokenType.BEARER.getValue()) || AccessTokenType.BEARER.equals(preferredType)) { 339 return BearerAccessToken.parse(header); 340 } else { 341 return DPoPAccessToken.parse(header); 342 } 343 } 344 345 346 /** 347 * Parses an HTTP request header value for an access token. 348 * 349 * @param request The HTTP request to parse. Must not be {@code null}. 350 * 351 * @return The access token. 352 * 353 * @throws ParseException If an access token wasn't found in the HTTP 354 * request. 355 */ 356 public static AccessToken parse(final HTTPRequest request) 357 throws ParseException { 358 359 if (request.getAuthorization() != null) { 360 361 AccessTokenType tokenType = AccessTokenUtils.determineAccessTokenTypeFromAuthorizationHeader(request.getAuthorization()); 362 363 if (AccessTokenType.BEARER.equals(tokenType)) { 364 return BearerAccessToken.parse(request.getAuthorization()); 365 } 366 367 if (AccessTokenType.DPOP.equals(tokenType)) { 368 return DPoPAccessToken.parse(request.getAuthorization()); 369 } 370 371 throw new ParseException("Couldn't determine access token type from Authorization header"); 372 } 373 374 // Try alternative token locations, form and query string are 375 // parameters are not differentiated here 376 Map<String, List<String>> params = request.getQueryParameters(); 377 return new TypelessAccessToken(AccessTokenUtils.parseValueFromQueryParameters(params)); 378 } 379}