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