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