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