001package com.nimbusds.oauth2.sdk; 002 003 004import java.net.MalformedURLException; 005import java.net.URI; 006import java.net.URISyntaxException; 007import java.net.URL; 008import java.util.Collections; 009import java.util.HashMap; 010import java.util.Map; 011import java.util.Set; 012 013import net.jcip.annotations.Immutable; 014 015import net.minidev.json.JSONObject; 016 017import com.nimbusds.oauth2.sdk.auth.ClientAuthentication; 018import com.nimbusds.oauth2.sdk.http.CommonContentTypes; 019import com.nimbusds.oauth2.sdk.http.HTTPRequest; 020import com.nimbusds.oauth2.sdk.token.AccessToken; 021import com.nimbusds.oauth2.sdk.token.RefreshToken; 022import com.nimbusds.oauth2.sdk.token.Token; 023import com.nimbusds.oauth2.sdk.token.TypelessAccessToken; 024import com.nimbusds.oauth2.sdk.util.URLUtils; 025 026 027/** 028 * Token revocation request. Used to revoke an issued access or refresh token. 029 * 030 * <p>Example token revocation request with client authentication: 031 * 032 * <pre> 033 * POST /revoke HTTP/1.1 034 * Host: server.example.com 035 * Content-Type: application/x-www-form-urlencoded 036 * Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW 037 * 038 * token=45ghiukldjahdnhzdauz&token_type_hint=refresh_token 039 * </pre> 040 * 041 * <p>Related specifications: 042 * 043 * <ul> 044 * <li>OAuth 2.0 Token Revocation (RFC 7009), section 2.1. 045 * </ul> 046 */ 047@Immutable 048public final class TokenRevocationRequest extends AbstractRequest { 049 050 051 /** 052 * The client authentication, {@code null} if none. 053 */ 054 private final ClientAuthentication clientAuth; 055 056 057 /** 058 * The token to revoke. 059 */ 060 private final Token token; 061 062 063 /** 064 * 065 * @param uri The URI of the token revocation endpoint. May be 066 * {@code null} if the {@link #toHTTPRequest} method 067 * will not be used. 068 * @param clientAuth The client authentication, {@code null} if none. 069 * @param token The access or refresh token to revoke. Must not be 070 * {@code null}. 071 */ 072 public TokenRevocationRequest(final URI uri, 073 final ClientAuthentication clientAuth, 074 final Token token) { 075 076 super(uri); 077 078 this.clientAuth = clientAuth; 079 080 if (token == null) 081 throw new IllegalArgumentException("The token must not be null"); 082 083 this.token = token; 084 } 085 086 087 /** 088 * Gets the client authentication. 089 * 090 * @return The client authentication, {@code null} if none. 091 */ 092 public ClientAuthentication getClientAuthentication() { 093 094 return clientAuth; 095 } 096 097 098 /** 099 * Returns the token to revoke. The {@code instanceof} operator can be 100 * used to infer the token type. If it's neither 101 * {@link com.nimbusds.oauth2.sdk.token.AccessToken} nor 102 * {@link com.nimbusds.oauth2.sdk.token.RefreshToken} the 103 * {@code token_type_hint} has not be provided as part of the token 104 * revocation request. 105 * 106 * @return The token. 107 */ 108 public Token getToken() { 109 110 return token; 111 } 112 113 114 @Override 115 public HTTPRequest toHTTPRequest() { 116 117 if (getEndpointURI() == null) 118 throw new SerializeException("The endpoint URI is not specified"); 119 120 URL url; 121 122 try { 123 url = getEndpointURI().toURL(); 124 125 } catch (MalformedURLException e) { 126 127 throw new SerializeException(e.getMessage(), e); 128 } 129 130 HTTPRequest httpRequest = new HTTPRequest(HTTPRequest.Method.POST, url); 131 httpRequest.setContentType(CommonContentTypes.APPLICATION_URLENCODED); 132 133 Map<String,String> params = new HashMap<>(); 134 params.put("token", token.getValue()); 135 136 if (token instanceof AccessToken) { 137 params.put("token_type_hint", "access_token"); 138 } else if (token instanceof RefreshToken) { 139 params.put("token_type_hint", "refresh_token"); 140 } 141 142 httpRequest.setQuery(URLUtils.serializeParameters(params)); 143 144 if (getClientAuthentication() != null) 145 getClientAuthentication().applyTo(httpRequest); 146 147 return httpRequest; 148 } 149 150 151 /** 152 * Parses a token revocation request from the specified HTTP request. 153 * 154 * @param httpRequest The HTTP request. Must not be {@code null}. 155 * 156 * @return The token revocation request. 157 * 158 * @throws ParseException If the HTTP request couldn't be parsed to a 159 * token revocation request. 160 */ 161 public static TokenRevocationRequest parse(final HTTPRequest httpRequest) 162 throws ParseException { 163 164 // Only HTTP POST accepted 165 httpRequest.ensureMethod(HTTPRequest.Method.POST); 166 httpRequest.ensureContentType(CommonContentTypes.APPLICATION_URLENCODED); 167 168 Map<String,String> params = httpRequest.getQueryParameters(); 169 170 final String tokenValue = params.get("token"); 171 172 if (tokenValue == null || tokenValue.isEmpty()) { 173 throw new ParseException("Missing required token parameter"); 174 } 175 176 // Detect the token type 177 Token token = null; 178 179 final String tokenTypeHint = params.get("token_type_hint"); 180 181 if (tokenTypeHint == null) { 182 183 // Can be both access or refresh token 184 token = new Token() { 185 186 @Override 187 public String getValue() { 188 189 return tokenValue; 190 } 191 192 @Override 193 public Set<String> getParameterNames() { 194 195 return Collections.emptySet(); 196 } 197 198 @Override 199 public JSONObject toJSONObject() { 200 201 return new JSONObject(); 202 } 203 204 @Override 205 public boolean equals(final Object other) { 206 207 return other instanceof Token && other.toString().equals(tokenValue); 208 } 209 }; 210 211 } else if (tokenTypeHint.equals("access_token")) { 212 213 token = new TypelessAccessToken(tokenValue); 214 215 } else if (tokenTypeHint.equals("refresh_token")) { 216 217 token = new RefreshToken(tokenValue); 218 } 219 220 221 // Parse client auth 222 ClientAuthentication clientAuth = ClientAuthentication.parse(httpRequest); 223 224 URI uri; 225 226 try { 227 uri = httpRequest.getURL().toURI(); 228 229 } catch (URISyntaxException e) { 230 231 throw new ParseException(e.getMessage(), e); 232 } 233 234 return new TokenRevocationRequest(uri, clientAuth, token); 235 } 236}