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 AbstractOptionallyAuthenticatedRequest { 049 050 051 /** 052 * The token to revoke. 053 */ 054 private final Token token; 055 056 057 /** 058 * Creates a new token revocation request. 059 * 060 * @param uri The URI of the token revocation endpoint. May be 061 * {@code null} if the {@link #toHTTPRequest} method 062 * will not be used. 063 * @param clientAuth The client authentication, {@code null} if none. 064 * @param token The access or refresh token to revoke. Must not be 065 * {@code null}. 066 */ 067 public TokenRevocationRequest(final URI uri, 068 final ClientAuthentication clientAuth, 069 final Token token) { 070 071 super(uri, clientAuth); 072 073 if (token == null) 074 throw new IllegalArgumentException("The token must not be null"); 075 076 this.token = token; 077 } 078 079 080 /** 081 * Returns the token to revoke. The {@code instanceof} operator can be 082 * used to infer the token type. If it's neither 083 * {@link com.nimbusds.oauth2.sdk.token.AccessToken} nor 084 * {@link com.nimbusds.oauth2.sdk.token.RefreshToken} the 085 * {@code token_type_hint} has not been provided as part of the token 086 * revocation request. 087 * 088 * @return The token. 089 */ 090 public Token getToken() { 091 092 return token; 093 } 094 095 096 @Override 097 public HTTPRequest toHTTPRequest() { 098 099 if (getEndpointURI() == null) 100 throw new SerializeException("The endpoint URI is not specified"); 101 102 URL url; 103 104 try { 105 url = getEndpointURI().toURL(); 106 107 } catch (MalformedURLException e) { 108 109 throw new SerializeException(e.getMessage(), e); 110 } 111 112 HTTPRequest httpRequest = new HTTPRequest(HTTPRequest.Method.POST, url); 113 httpRequest.setContentType(CommonContentTypes.APPLICATION_URLENCODED); 114 115 Map<String,String> params = new HashMap<>(); 116 params.put("token", token.getValue()); 117 118 if (token instanceof AccessToken) { 119 params.put("token_type_hint", "access_token"); 120 } else if (token instanceof RefreshToken) { 121 params.put("token_type_hint", "refresh_token"); 122 } 123 124 httpRequest.setQuery(URLUtils.serializeParameters(params)); 125 126 if (getClientAuthentication() != null) 127 getClientAuthentication().applyTo(httpRequest); 128 129 return httpRequest; 130 } 131 132 133 /** 134 * Parses a token revocation request from the specified HTTP request. 135 * 136 * @param httpRequest The HTTP request. Must not be {@code null}. 137 * 138 * @return The token revocation request. 139 * 140 * @throws ParseException If the HTTP request couldn't be parsed to a 141 * token revocation request. 142 */ 143 public static TokenRevocationRequest parse(final HTTPRequest httpRequest) 144 throws ParseException { 145 146 // Only HTTP POST accepted 147 httpRequest.ensureMethod(HTTPRequest.Method.POST); 148 httpRequest.ensureContentType(CommonContentTypes.APPLICATION_URLENCODED); 149 150 Map<String,String> params = httpRequest.getQueryParameters(); 151 152 final String tokenValue = params.get("token"); 153 154 if (tokenValue == null || tokenValue.isEmpty()) { 155 throw new ParseException("Missing required token parameter"); 156 } 157 158 // Detect the token type 159 Token token = null; 160 161 final String tokenTypeHint = params.get("token_type_hint"); 162 163 if (tokenTypeHint == null) { 164 165 // Can be both access or refresh token 166 token = new Token() { 167 168 @Override 169 public String getValue() { 170 171 return tokenValue; 172 } 173 174 @Override 175 public Set<String> getParameterNames() { 176 177 return Collections.emptySet(); 178 } 179 180 @Override 181 public JSONObject toJSONObject() { 182 183 return new JSONObject(); 184 } 185 186 @Override 187 public boolean equals(final Object other) { 188 189 return other instanceof Token && other.toString().equals(tokenValue); 190 } 191 }; 192 193 } else if (tokenTypeHint.equals("access_token")) { 194 195 token = new TypelessAccessToken(tokenValue); 196 197 } else if (tokenTypeHint.equals("refresh_token")) { 198 199 token = new RefreshToken(tokenValue); 200 } 201 202 203 // Parse client auth 204 ClientAuthentication clientAuth = ClientAuthentication.parse(httpRequest); 205 206 URI uri; 207 208 try { 209 uri = httpRequest.getURL().toURI(); 210 211 } catch (URISyntaxException e) { 212 213 throw new ParseException(e.getMessage(), e); 214 } 215 216 return new TokenRevocationRequest(uri, clientAuth, token); 217 } 218}