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