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