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                throws SerializeException {
117
118                if (getEndpointURI() == null)
119                        throw new SerializeException("The endpoint URI is not specified");
120
121                URL url;
122
123                try {
124                        url = getEndpointURI().toURL();
125
126                } catch (MalformedURLException e) {
127
128                        throw new SerializeException(e.getMessage(), e);
129                }
130
131                HTTPRequest httpRequest = new HTTPRequest(HTTPRequest.Method.POST, url);
132                httpRequest.setContentType(CommonContentTypes.APPLICATION_URLENCODED);
133
134                Map<String,String> params = new HashMap<>();
135                params.put("token", token.getValue());
136
137                if (token instanceof AccessToken) {
138                        params.put("token_type_hint", "access_token");
139                } else if (token instanceof RefreshToken) {
140                        params.put("token_type_hint", "refresh_token");
141                }
142
143                httpRequest.setQuery(URLUtils.serializeParameters(params));
144
145                if (getClientAuthentication() != null)
146                        getClientAuthentication().applyTo(httpRequest);
147
148                return httpRequest;
149        }
150
151
152        /**
153         * Parses a token revocation request from the specified HTTP request.
154         *
155         * @param httpRequest The HTTP request. Must not be {@code null}.
156         *
157         * @return The token revocation request.
158         *
159         * @throws ParseException If the HTTP request couldn't be parsed to a
160         *                        token revocation request.
161         */
162        public static TokenRevocationRequest parse(final HTTPRequest httpRequest)
163                throws ParseException {
164
165                // Only HTTP POST accepted
166                httpRequest.ensureMethod(HTTPRequest.Method.POST);
167                httpRequest.ensureContentType(CommonContentTypes.APPLICATION_URLENCODED);
168
169                Map<String,String> params = httpRequest.getQueryParameters();
170
171                final String tokenValue = params.get("token");
172
173                if (tokenValue == null || tokenValue.isEmpty()) {
174                        throw new ParseException("Missing required token parameter");
175                }
176
177                // Detect the token type
178                Token token = null;
179
180                final String tokenTypeHint = params.get("token_type_hint");
181
182                if (tokenTypeHint == null) {
183
184                        // Can be both access or refresh token
185                        token = new Token() {
186
187                                @Override
188                                public String getValue() {
189
190                                        return tokenValue;
191                                }
192
193                                @Override
194                                public Set<String> getParamNames() {
195
196                                        return Collections.emptySet();
197                                }
198
199                                @Override
200                                public JSONObject toJSONObject() {
201
202                                        return new JSONObject();
203                                }
204
205                                @Override
206                                public boolean equals(final Object other) {
207
208                                        return other instanceof Token && other.toString().equals(tokenValue);
209                                }
210                        };
211
212                } else if (tokenTypeHint.equals("access_token")) {
213
214                        token = new TypelessAccessToken(tokenValue);
215
216                } else if (tokenTypeHint.equals("refresh_token")) {
217
218                        token = new RefreshToken(tokenValue);
219                }
220
221
222                // Parse client auth
223                ClientAuthentication clientAuth = ClientAuthentication.parse(httpRequest);
224
225                URI uri;
226
227                try {
228                        uri = httpRequest.getURL().toURI();
229
230                } catch (URISyntaxException e) {
231
232                        throw new ParseException(e.getMessage(), e);
233                }
234
235                return new TokenRevocationRequest(uri, clientAuth, token);
236        }
237}