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&amp;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&amp;token_type_hint=refresh_token&amp;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}