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