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 AbstractOptionallyAuthenticatedRequest {
049
050
051        /**
052         * The token to revoke.
053         */
054        private final Token token;
055
056
057        /**
058         * Creates a new token revocation request.
059         *
060         * @param uri        The URI of the token revocation endpoint. May be
061         *                   {@code null} if the {@link #toHTTPRequest} method
062         *                   will not be used.
063         * @param clientAuth The client authentication, {@code null} if none.
064         * @param token      The access or refresh token to revoke. Must not be
065         *                   {@code null}.
066         */
067        public TokenRevocationRequest(final URI uri,
068                                      final ClientAuthentication clientAuth,
069                                      final Token token) {
070
071                super(uri, clientAuth);
072
073                if (token == null)
074                        throw new IllegalArgumentException("The token must not be null");
075
076                this.token = token;
077        }
078
079
080        /**
081         * Returns the token to revoke. The {@code instanceof} operator can be
082         * used to infer the token type. If it's neither
083         * {@link com.nimbusds.oauth2.sdk.token.AccessToken} nor
084         * {@link com.nimbusds.oauth2.sdk.token.RefreshToken} the
085         * {@code token_type_hint} has not been provided as part of the token
086         * revocation request.
087         *
088         * @return The token.
089         */
090        public Token getToken() {
091
092                return token;
093        }
094
095
096        @Override
097        public HTTPRequest toHTTPRequest() {
098
099                if (getEndpointURI() == null)
100                        throw new SerializeException("The endpoint URI is not specified");
101
102                URL url;
103
104                try {
105                        url = getEndpointURI().toURL();
106
107                } catch (MalformedURLException e) {
108
109                        throw new SerializeException(e.getMessage(), e);
110                }
111
112                HTTPRequest httpRequest = new HTTPRequest(HTTPRequest.Method.POST, url);
113                httpRequest.setContentType(CommonContentTypes.APPLICATION_URLENCODED);
114
115                Map<String,String> params = new HashMap<>();
116                params.put("token", token.getValue());
117
118                if (token instanceof AccessToken) {
119                        params.put("token_type_hint", "access_token");
120                } else if (token instanceof RefreshToken) {
121                        params.put("token_type_hint", "refresh_token");
122                }
123
124                httpRequest.setQuery(URLUtils.serializeParameters(params));
125
126                if (getClientAuthentication() != null)
127                        getClientAuthentication().applyTo(httpRequest);
128
129                return httpRequest;
130        }
131
132
133        /**
134         * Parses a token revocation request from the specified HTTP request.
135         *
136         * @param httpRequest The HTTP request. Must not be {@code null}.
137         *
138         * @return The token revocation request.
139         *
140         * @throws ParseException If the HTTP request couldn't be parsed to a
141         *                        token revocation request.
142         */
143        public static TokenRevocationRequest parse(final HTTPRequest httpRequest)
144                throws ParseException {
145
146                // Only HTTP POST accepted
147                httpRequest.ensureMethod(HTTPRequest.Method.POST);
148                httpRequest.ensureContentType(CommonContentTypes.APPLICATION_URLENCODED);
149
150                Map<String,String> params = httpRequest.getQueryParameters();
151
152                final String tokenValue = params.get("token");
153
154                if (tokenValue == null || tokenValue.isEmpty()) {
155                        throw new ParseException("Missing required token parameter");
156                }
157
158                // Detect the token type
159                Token token = null;
160
161                final String tokenTypeHint = params.get("token_type_hint");
162
163                if (tokenTypeHint == null) {
164
165                        // Can be both access or refresh token
166                        token = new Token() {
167
168                                @Override
169                                public String getValue() {
170
171                                        return tokenValue;
172                                }
173
174                                @Override
175                                public Set<String> getParameterNames() {
176
177                                        return Collections.emptySet();
178                                }
179
180                                @Override
181                                public JSONObject toJSONObject() {
182
183                                        return new JSONObject();
184                                }
185
186                                @Override
187                                public boolean equals(final Object other) {
188
189                                        return other instanceof Token && other.toString().equals(tokenValue);
190                                }
191                        };
192
193                } else if (tokenTypeHint.equals("access_token")) {
194
195                        token = new TypelessAccessToken(tokenValue);
196
197                } else if (tokenTypeHint.equals("refresh_token")) {
198
199                        token = new RefreshToken(tokenValue);
200                }
201
202
203                // Parse client auth
204                ClientAuthentication clientAuth = ClientAuthentication.parse(httpRequest);
205
206                URI uri;
207
208                try {
209                        uri = httpRequest.getURL().toURI();
210
211                } catch (URISyntaxException e) {
212
213                        throw new ParseException(e.getMessage(), e);
214                }
215
216                return new TokenRevocationRequest(uri, clientAuth, token);
217        }
218}