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