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.id.ClientID; 034import com.nimbusds.oauth2.sdk.token.AccessToken; 035import com.nimbusds.oauth2.sdk.token.RefreshToken; 036import com.nimbusds.oauth2.sdk.token.Token; 037import com.nimbusds.oauth2.sdk.token.TypelessAccessToken; 038import com.nimbusds.oauth2.sdk.util.StringUtils; 039import com.nimbusds.oauth2.sdk.util.URLUtils; 040import net.jcip.annotations.Immutable; 041import net.minidev.json.JSONObject; 042 043 044/** 045 * Token revocation request. Used to revoke an issued access or refresh token. 046 * 047 * <p>Example token revocation request for a confidential client: 048 * 049 * <pre> 050 * POST /revoke HTTP/1.1 051 * Host: server.example.com 052 * Content-Type: application/x-www-form-urlencoded 053 * Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW 054 * 055 * token=45ghiukldjahdnhzdauz&token_type_hint=refresh_token 056 * </pre> 057 * 058 * <p>Example token revocation request for a public client: 059 * 060 * <pre> 061 * POST /revoke HTTP/1.1 062 * Host: server.example.com 063 * Content-Type: application/x-www-form-urlencoded 064 * 065 * token=45ghiukldjahdnhzdauz&token_type_hint=refresh_token&client_id=123456 066 * </pre> 067 * 068 * <p>Related specifications: 069 * 070 * <ul> 071 * <li>OAuth 2.0 Token Revocation (RFC 7009), section 2.1. 072 * </ul> 073 */ 074@Immutable 075public final class TokenRevocationRequest extends AbstractOptionallyIdentifiedRequest { 076 077 078 /** 079 * The token to revoke. 080 */ 081 private final Token token; 082 083 084 /** 085 * Creates a new token revocation request for a confidential client. 086 * 087 * @param uri The URI of the token revocation endpoint. May be 088 * {@code null} if the {@link #toHTTPRequest} method 089 * will not be used. 090 * @param clientAuth The client authentication. Must not be 091 * {@code null}. 092 * @param token The access or refresh token to revoke. Must not be 093 * {@code null}. 094 */ 095 public TokenRevocationRequest(final URI uri, 096 final ClientAuthentication clientAuth, 097 final Token token) { 098 099 super(uri, clientAuth); 100 101 if (clientAuth == null) { 102 throw new IllegalArgumentException("The client authentication must not be null"); 103 } 104 105 if (token == null) 106 throw new IllegalArgumentException("The token must not be null"); 107 108 this.token = token; 109 } 110 111 112 /** 113 * Creates a new token revocation request for a public client. 114 * 115 * @param uri The URI of the token revocation endpoint. May be 116 * {@code null} if the {@link #toHTTPRequest} method 117 * will not be used. 118 * @param clientID The client ID. Must not be {@code null}. 119 * @param token The access or refresh token to revoke. Must not be 120 * {@code null}. 121 */ 122 public TokenRevocationRequest(final URI uri, 123 final ClientID clientID, 124 final Token token) { 125 126 super(uri, clientID); 127 128 if (clientID == null) { 129 throw new IllegalArgumentException("The client ID must not be null"); 130 } 131 132 if (token == null) 133 throw new IllegalArgumentException("The token must not be null"); 134 135 this.token = token; 136 } 137 138 139 /** 140 * Returns the token to revoke. The {@code instanceof} operator can be 141 * used to infer the token type. If it's neither 142 * {@link com.nimbusds.oauth2.sdk.token.AccessToken} nor 143 * {@link com.nimbusds.oauth2.sdk.token.RefreshToken} the 144 * {@code token_type_hint} has not been provided as part of the token 145 * revocation request. 146 * 147 * @return The token. 148 */ 149 public Token getToken() { 150 151 return token; 152 } 153 154 155 @Override 156 public HTTPRequest toHTTPRequest() { 157 158 if (getEndpointURI() == null) 159 throw new SerializeException("The endpoint URI is not specified"); 160 161 URL url; 162 163 try { 164 url = getEndpointURI().toURL(); 165 166 } catch (MalformedURLException e) { 167 168 throw new SerializeException(e.getMessage(), e); 169 } 170 171 HTTPRequest httpRequest = new HTTPRequest(HTTPRequest.Method.POST, url); 172 httpRequest.setContentType(CommonContentTypes.APPLICATION_URLENCODED); 173 174 Map<String,String> params = new HashMap<>(); 175 176 if (getClientID() != null) { 177 // public client 178 params.put("client_id", getClientID().getValue()); 179 } 180 181 params.put("token", token.getValue()); 182 183 if (token instanceof AccessToken) { 184 params.put("token_type_hint", "access_token"); 185 } else if (token instanceof RefreshToken) { 186 params.put("token_type_hint", "refresh_token"); 187 } 188 189 httpRequest.setQuery(URLUtils.serializeParameters(params)); 190 191 if (getClientAuthentication() != null) { 192 // confidential client 193 getClientAuthentication().applyTo(httpRequest); 194 } 195 196 return httpRequest; 197 } 198 199 200 /** 201 * Parses a token revocation request from the specified HTTP request. 202 * 203 * @param httpRequest The HTTP request. Must not be {@code null}. 204 * 205 * @return The token revocation request. 206 * 207 * @throws ParseException If the HTTP request couldn't be parsed to a 208 * token revocation request. 209 */ 210 public static TokenRevocationRequest parse(final HTTPRequest httpRequest) 211 throws ParseException { 212 213 // Only HTTP POST accepted 214 httpRequest.ensureMethod(HTTPRequest.Method.POST); 215 httpRequest.ensureContentType(CommonContentTypes.APPLICATION_URLENCODED); 216 217 Map<String,String> params = httpRequest.getQueryParameters(); 218 219 final String tokenValue = params.get("token"); 220 221 if (tokenValue == null || tokenValue.isEmpty()) { 222 throw new ParseException("Missing required token parameter"); 223 } 224 225 // Detect the token type 226 Token token = null; 227 228 final String tokenTypeHint = params.get("token_type_hint"); 229 230 if (tokenTypeHint == null) { 231 232 // Can be both access or refresh token 233 token = new Token() { 234 235 @Override 236 public String getValue() { 237 238 return tokenValue; 239 } 240 241 @Override 242 public Set<String> getParameterNames() { 243 244 return Collections.emptySet(); 245 } 246 247 @Override 248 public JSONObject toJSONObject() { 249 250 return new JSONObject(); 251 } 252 253 @Override 254 public boolean equals(final Object other) { 255 256 return other instanceof Token && other.toString().equals(tokenValue); 257 } 258 }; 259 260 } else if (tokenTypeHint.equals("access_token")) { 261 262 token = new TypelessAccessToken(tokenValue); 263 264 } else if (tokenTypeHint.equals("refresh_token")) { 265 266 token = new RefreshToken(tokenValue); 267 } 268 269 URI uri; 270 271 try { 272 uri = httpRequest.getURL().toURI(); 273 274 } catch (URISyntaxException e) { 275 276 throw new ParseException(e.getMessage(), e); 277 } 278 279 // Parse client auth 280 ClientAuthentication clientAuth = ClientAuthentication.parse(httpRequest); 281 282 if (clientAuth != null) { 283 return new TokenRevocationRequest(uri, clientAuth, token); 284 } 285 286 // Public client 287 final String clientIDString = params.get("client_id"); 288 289 if (StringUtils.isBlank(clientIDString)) { 290 throw new ParseException("Invalid token revocation request: No client authentication or client_id parameter found"); 291 } 292 293 return new TokenRevocationRequest(uri, new ClientID(clientIDString), token); 294 } 295}