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; 019 020 021import com.nimbusds.common.contenttype.ContentType; 022import com.nimbusds.oauth2.sdk.auth.ClientAuthentication; 023import com.nimbusds.oauth2.sdk.http.HTTPRequest; 024import com.nimbusds.oauth2.sdk.token.AccessToken; 025import com.nimbusds.oauth2.sdk.token.RefreshToken; 026import com.nimbusds.oauth2.sdk.token.Token; 027import com.nimbusds.oauth2.sdk.token.TypelessAccessToken; 028import com.nimbusds.oauth2.sdk.util.MultivaluedMapUtils; 029import com.nimbusds.oauth2.sdk.util.StringUtils; 030import com.nimbusds.oauth2.sdk.util.URLUtils; 031import net.jcip.annotations.Immutable; 032import net.minidev.json.JSONObject; 033 034import java.net.URI; 035import java.util.*; 036 037 038/** 039 * Token introspection request. Used by a protected resource to obtain the 040 * authorisation for a submitted access token. May also be used by clients to 041 * query a refresh token. 042 * 043 * <p>The protected resource may be required to authenticate itself to the 044 * token introspection endpoint with a standard client 045 * {@link ClientAuthentication authentication method}, such as 046 * {@link com.nimbusds.oauth2.sdk.auth.ClientSecretBasic client_secret_basic}, 047 * or with a dedicated {@link AccessToken access token}. 048 * 049 * <p>Example token introspection request, where the protected resource 050 * authenticates itself with a secret (the token type is also hinted): 051 * 052 * <pre> 053 * POST /introspect HTTP/1.1 054 * Host: server.example.com 055 * Accept: application/json 056 * Content-Type: application/x-www-form-urlencoded 057 * Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW 058 * 059 * token=mF_9.B5f-4.1JqM&token_type_hint=access_token 060 * </pre> 061 * 062 * <p>Example token introspection request, where the protected resource 063 * authenticates itself with a bearer token: 064 * 065 * <pre> 066 * POST /introspect HTTP/1.1 067 * Host: server.example.com 068 * Accept: application/json 069 * Content-Type: application/x-www-form-urlencoded 070 * Authorization: Bearer 23410913-abewfq.123483 071 * 072 * token=2YotnFZFEjr1zCsicMWpAA 073 * </pre> 074 * 075 * <p>Related specifications: 076 * 077 * <ul> 078 * <li>OAuth 2.0 Token Introspection (RFC 7662). 079 * </ul> 080 */ 081@Immutable 082public class TokenIntrospectionRequest extends AbstractOptionallyAuthenticatedRequest { 083 084 085 /** 086 * The token to introspect. 087 */ 088 private final Token token; 089 090 091 /** 092 * Optional access token to authorise the submitter. 093 */ 094 private final AccessToken clientAuthz; 095 096 097 /** 098 * Optional additional parameters. 099 */ 100 private final Map<String,List<String>> customParams; 101 102 103 /** 104 * Creates a new token introspection request. The request submitter is 105 * not authenticated. 106 * 107 * @param uri The URI of the token introspection endpoint. May be 108 * {@code null} if the {@link #toHTTPRequest} method will 109 * not be used. 110 * @param token The access or refresh token to introspect. Must not be 111 * {@code null}. 112 */ 113 public TokenIntrospectionRequest(final URI uri, 114 final Token token) { 115 116 this(uri, token, null); 117 } 118 119 120 /** 121 * Creates a new token introspection request. The request submitter is 122 * not authenticated. 123 * 124 * @param uri The URI of the token introspection endpoint. May 125 * be {@code null} if the {@link #toHTTPRequest} 126 * method will not be used. 127 * @param token The access or refresh token to introspect. Must 128 * not be {@code null}. 129 * @param customParams Optional custom parameters, {@code null} if 130 * none. 131 */ 132 public TokenIntrospectionRequest(final URI uri, 133 final Token token, 134 final Map<String,List<String>> customParams) { 135 136 super(uri, null); 137 138 if (token == null) 139 throw new IllegalArgumentException("The token must not be null"); 140 141 this.token = token; 142 this.clientAuthz = null; 143 this.customParams = customParams != null ? customParams : Collections.<String,List<String>>emptyMap(); 144 } 145 146 147 /** 148 * Creates a new token introspection request. The request submitter may 149 * authenticate with a secret or private key JWT assertion. 150 * 151 * @param uri The URI of the token introspection endpoint. May 152 * be {@code null} if the {@link #toHTTPRequest} 153 * method will not be used. 154 * @param clientAuth The client authentication, {@code null} if none. 155 * @param token The access or refresh token to introspect. Must 156 * not be {@code null}. 157 */ 158 public TokenIntrospectionRequest(final URI uri, 159 final ClientAuthentication clientAuth, 160 final Token token) { 161 162 this(uri, clientAuth, token, null); 163 } 164 165 166 /** 167 * Creates a new token introspection request. The request submitter may 168 * authenticate with a secret or private key JWT assertion. 169 * 170 * @param uri The URI of the token introspection endpoint. May 171 * be {@code null} if the {@link #toHTTPRequest} 172 * method will not be used. 173 * @param clientAuth The client authentication, {@code null} if none. 174 * @param token The access or refresh token to introspect. Must 175 * not be {@code null}. 176 * @param customParams Optional custom parameters, {@code null} if 177 * none. 178 */ 179 public TokenIntrospectionRequest(final URI uri, 180 final ClientAuthentication clientAuth, 181 final Token token, 182 final Map<String,List<String>> customParams) { 183 184 super(uri, clientAuth); 185 186 if (token == null) 187 throw new IllegalArgumentException("The token must not be null"); 188 189 this.token = token; 190 this.clientAuthz = null; 191 this.customParams = customParams != null ? customParams : Collections.<String,List<String>>emptyMap(); 192 } 193 194 195 /** 196 * Creates a new token introspection request. The request submitter may 197 * authorise itself with an access token. 198 * 199 * @param uri The URI of the token introspection endpoint. May 200 * be {@code null} if the {@link #toHTTPRequest} 201 * method will not be used. 202 * @param clientAuthz The client authorisation, {@code null} if none. 203 * @param token The access or refresh token to introspect. Must 204 * not be {@code null}. 205 */ 206 public TokenIntrospectionRequest(final URI uri, 207 final AccessToken clientAuthz, 208 final Token token) { 209 210 this(uri, clientAuthz, token, null); 211 } 212 213 214 /** 215 * Creates a new token introspection request. The request submitter may 216 * authorise itself with an access token. 217 * 218 * @param uri The URI of the token introspection endpoint. May 219 * be {@code null} if the {@link #toHTTPRequest} 220 * method will not be used. 221 * @param clientAuthz The client authorisation, {@code null} if none. 222 * @param token The access or refresh token to introspect. Must 223 * not be {@code null}. 224 * @param customParams Optional custom parameters, {@code null} if 225 * none. 226 */ 227 public TokenIntrospectionRequest(final URI uri, 228 final AccessToken clientAuthz, 229 final Token token, 230 final Map<String,List<String>> customParams) { 231 232 super(uri, null); 233 234 if (token == null) 235 throw new IllegalArgumentException("The token must not be null"); 236 237 this.token = token; 238 this.clientAuthz = clientAuthz; 239 this.customParams = customParams != null ? customParams : Collections.<String,List<String>>emptyMap(); 240 } 241 242 243 /** 244 * Returns the client authorisation. 245 * 246 * @return The client authorisation as an access token, {@code null} if 247 * none. 248 */ 249 public AccessToken getClientAuthorization() { 250 251 return clientAuthz; 252 } 253 254 255 /** 256 * Returns the token to introspect. The {@code instanceof} operator can 257 * be used to infer the token type. If it's neither 258 * {@link com.nimbusds.oauth2.sdk.token.AccessToken} nor 259 * {@link com.nimbusds.oauth2.sdk.token.RefreshToken} the 260 * {@code token_type_hint} has not been provided as part of the token 261 * revocation request. 262 * 263 * @return The token. 264 */ 265 public Token getToken() { 266 267 return token; 268 } 269 270 271 /** 272 * Returns the custom request parameters. 273 * 274 * @return The custom request parameters, empty map if none. 275 */ 276 public Map<String,List<String>> getCustomParameters() { 277 278 return customParams; 279 } 280 281 282 @Override 283 public HTTPRequest toHTTPRequest() { 284 285 if (getEndpointURI() == null) 286 throw new SerializeException("The endpoint URI is not specified"); 287 288 HTTPRequest httpRequest = new HTTPRequest(HTTPRequest.Method.POST, getEndpointURI()); 289 httpRequest.setEntityContentType(ContentType.APPLICATION_URLENCODED); 290 291 Map<String,List<String>> params = new HashMap<>(); 292 params.put("token", Collections.singletonList(token.getValue())); 293 294 if (token instanceof AccessToken) { 295 params.put("token_type_hint", Collections.singletonList("access_token")); 296 } else if (token instanceof RefreshToken) { 297 params.put("token_type_hint", Collections.singletonList("refresh_token")); 298 } 299 300 params.putAll(customParams); 301 302 httpRequest.setBody(URLUtils.serializeParameters(params)); 303 304 if (getClientAuthentication() != null) 305 getClientAuthentication().applyTo(httpRequest); 306 307 if (clientAuthz != null) 308 httpRequest.setAuthorization(clientAuthz.toAuthorizationHeader()); 309 310 return httpRequest; 311 } 312 313 314 /** 315 * Parses a token introspection request from the specified HTTP 316 * request. 317 * 318 * @param httpRequest The HTTP request. Must not be {@code null}. 319 * 320 * @return The token introspection request. 321 * 322 * @throws ParseException If the HTTP request couldn't be parsed to a 323 * token introspection request. 324 */ 325 public static TokenIntrospectionRequest parse(final HTTPRequest httpRequest) 326 throws ParseException { 327 328 // Only HTTP POST accepted 329 httpRequest.ensureMethod(HTTPRequest.Method.POST); 330 httpRequest.ensureEntityContentType(ContentType.APPLICATION_URLENCODED); 331 332 Map<String,List<String>> params = httpRequest.getBodyAsFormParameters(); 333 334 final String tokenValue = MultivaluedMapUtils.removeAndReturnFirstValue(params, "token"); 335 336 if (StringUtils.isBlank(tokenValue)) { 337 throw new ParseException("Missing required token parameter"); 338 } 339 340 // Detect the token type 341 Token token = null; 342 343 final String tokenTypeHint = MultivaluedMapUtils.removeAndReturnFirstValue(params, "token_type_hint"); 344 345 if (tokenTypeHint == null) { 346 347 // Can be both access or refresh token 348 token = new Token() { 349 350 private static final long serialVersionUID = 8491102820261331059L; 351 352 353 @Override 354 public String getValue() { 355 356 return tokenValue; 357 } 358 359 @Override 360 public Set<String> getParameterNames() { 361 362 return Collections.emptySet(); 363 } 364 365 @Override 366 public JSONObject toJSONObject() { 367 368 return new JSONObject(); 369 } 370 371 @Override 372 public boolean equals(final Object other) { 373 374 return other instanceof Token && other.toString().equals(tokenValue); 375 } 376 377 @Override 378 public int hashCode() { 379 return Objects.hashCode(tokenValue); 380 } 381 }; 382 383 } else if (tokenTypeHint.equals("access_token")) { 384 385 token = new TypelessAccessToken(tokenValue); 386 387 } else if (tokenTypeHint.equals("refresh_token")) { 388 389 token = new RefreshToken(tokenValue); 390 } 391 392 // Important: auth methods mutually exclusive! 393 394 // Parse optional client auth 395 ClientAuthentication clientAuth = ClientAuthentication.parse(httpRequest); 396 397 // Parse optional client authz (token) 398 AccessToken clientAuthz = null; 399 400 if (clientAuth == null && httpRequest.getAuthorization() != null) { 401 clientAuthz = AccessToken.parse(httpRequest.getAuthorization()); 402 } 403 404 URI uri = httpRequest.getURI(); 405 406 if (clientAuthz != null) { 407 return new TokenIntrospectionRequest(uri, clientAuthz, token, params); 408 } else { 409 return new TokenIntrospectionRequest(uri, clientAuth, token, params); 410 } 411 } 412}