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