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