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