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