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