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