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