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.Map;
009
010import net.jcip.annotations.Immutable;
011
012import com.nimbusds.oauth2.sdk.auth.ClientAuthentication;
013import com.nimbusds.oauth2.sdk.id.ClientID;
014import com.nimbusds.oauth2.sdk.http.CommonContentTypes;
015import com.nimbusds.oauth2.sdk.http.HTTPRequest;
016import com.nimbusds.oauth2.sdk.util.URLUtils;
017
018
019/**
020 * Token request. Used to obtain an
021 * {@link com.nimbusds.oauth2.sdk.token.AccessToken access token} and an
022 * optional {@link com.nimbusds.oauth2.sdk.token.RefreshToken refresh token}
023 * at the Token endpoint of the authorisation server.
024 *
025 * <p>Example token request with an authorisation code grant:
026 *
027 * <pre>
028 * POST /token HTTP/1.1
029 * Host: server.example.com
030 * Content-Type: application/x-www-form-URIencoded
031 * Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW
032 *
033 * grant_type=authorization_code
034 * &amp;code=SplxlOBeZQQYbYS6WxSbIA
035 * &amp;redirect_uri=https%3A%2F%2Fclient.example.org%2Fcb
036 * </pre>
037 *
038 * <p>Related specifications:
039 *
040 * <ul>
041 *     <li>OAuth 2.0 (RFC 6749), sections 4.1.3, 4.3.2, 4.4.2 and 6.
042 * </ul>
043 */
044@Immutable
045public class TokenRequest extends AbstractOptionallyAuthenticatedRequest {
046
047
048        /**
049         * The client identifier, {@code null} if not specified.
050         */
051        private final ClientID clientID;
052
053
054        /**
055         * The authorisation grant.
056         */
057        private final AuthorizationGrant authzGrant;
058
059
060        /**
061         * The requested scope, {@code null} if not specified.
062         */
063        private final Scope scope;
064
065
066        /**
067         * Creates a new token request with the specified client
068         * authentication.
069         *
070         * @param uri        The URI of the token endpoint. May be
071         *                   {@code null} if the {@link #toHTTPRequest} method
072         *                   will not be used.
073         * @param clientAuth The client authentication. Must not be
074         *                   {@code null}.
075         * @param authzGrant The authorisation grant. Must not be {@code null}.
076         * @param scope      The requested scope, {@code null} if not
077         *                   specified.
078         */
079        public TokenRequest(final URI uri,
080                            final ClientAuthentication clientAuth,
081                            final AuthorizationGrant authzGrant,
082                            final Scope scope) {
083
084                super(uri, clientAuth);
085
086                if (clientAuth == null)
087                        throw new IllegalArgumentException("The client authentication must not be null");
088
089                clientID = null; // must not be set when client auth is present
090
091                this.authzGrant = authzGrant;
092
093                this.scope = scope;
094        }
095
096
097        /**
098         * Creates a new token request with the specified client
099         * authentication.
100         *
101         * @param uri        The URI of the token endpoint. May be
102         *                   {@code null} if the {@link #toHTTPRequest} method
103         *                   will not be used.
104         * @param clientAuth The client authentication. Must not be
105         *                   {@code null}.
106         * @param authzGrant The authorisation grant. Must not be {@code null}.
107         */
108        public TokenRequest(final URI uri,
109                            final ClientAuthentication clientAuth,
110                            final AuthorizationGrant authzGrant) {
111
112                this(uri, clientAuth, authzGrant, null);
113        }
114
115
116        /**
117         * Creates a new token request, with no explicit client authentication
118         * (may be present in the grant depending on its type).
119         *
120         * @param uri        The URI of the token endpoint. May be
121         *                   {@code null} if the {@link #toHTTPRequest} method
122         *                   will not be used.
123         * @param clientID   The client identifier, {@code null} if not
124         *                   specified.
125         * @param authzGrant The authorisation grant. Must not be {@code null}.
126         * @param scope      The requested scope, {@code null} if not
127         *                   specified.
128         */
129        public TokenRequest(final URI uri,
130                            final ClientID clientID,
131                            final AuthorizationGrant authzGrant,
132                            final Scope scope) {
133
134                super(uri, null);
135
136                if (authzGrant.getType().requiresClientAuthentication()) {
137                        throw new IllegalArgumentException("The \"" + authzGrant.getType() + "\" grant type requires client authentication");
138                }
139
140                if (authzGrant.getType().requiresClientID() && clientID == null) {
141                        throw new IllegalArgumentException("The \"" + authzGrant.getType() + "\" grant type requires a \"client_id\" parameter");
142                }
143
144                this.authzGrant = authzGrant;
145
146                this.clientID = clientID;
147
148                this.scope = scope;
149        }
150
151
152        /**
153         * Creates a new token request, with no explicit client authentication
154         * (may be present in the grant depending on its type).
155         *
156         * @param uri        The URI of the token endpoint. May be
157         *                   {@code null} if the {@link #toHTTPRequest} method
158         *                   will not be used.
159         * @param clientID   The client identifier, {@code null} if not
160         *                   specified.
161         * @param authzGrant The authorisation grant. Must not be {@code null}.
162         */
163        public TokenRequest(final URI uri,
164                            final ClientID clientID,
165                            final AuthorizationGrant authzGrant) {
166
167                this(uri, clientID, authzGrant, null);
168        }
169
170
171        /**
172         * Creates a new token request, without client authentication and a
173         * specified client identifier.
174         *
175         * @param uri        The URI of the token endpoint. May be
176         *                   {@code null} if the {@link #toHTTPRequest} method
177         *                   will not be used.
178         * @param authzGrant The authorisation grant. Must not be {@code null}.
179         * @param scope      The requested scope, {@code null} if not
180         *                   specified.
181         */
182        public TokenRequest(final URI uri,
183                            final AuthorizationGrant authzGrant,
184                            final Scope scope) {
185
186                this(uri, (ClientID)null, authzGrant, scope);
187        }
188
189
190        /**
191         * Creates a new token request, without client authentication and a
192         * specified client identifier.
193         *
194         * @param uri        The URI of the token endpoint. May be
195         *                   {@code null} if the {@link #toHTTPRequest} method
196         *                   will not be used.
197         * @param authzGrant The authorisation grant. Must not be {@code null}.
198         */
199        public TokenRequest(final URI uri,
200                            final AuthorizationGrant authzGrant) {
201
202                this(uri, (ClientID)null, authzGrant, null);
203        }
204
205
206        /**
207         * Gets the client identifier (for a token request without explicit
208         * client authentication).
209         *
210         * @see #getClientAuthentication()
211         *
212         * @return The client identifier, {@code null} if not specified.
213         */
214        public ClientID getClientID() {
215
216                return clientID;
217        }
218
219
220        /**
221         * Gets the authorisation grant.
222         *
223         * @return The authorisation grant.
224         */
225        public AuthorizationGrant getAuthorizationGrant() {
226
227                return authzGrant;
228        }
229
230
231        /**
232         * Gets the requested scope.
233         *
234         * @return The requested scope, {@code null} if not specified.
235         */
236        public Scope getScope() {
237
238                return scope;
239        }
240
241
242        @Override
243        public HTTPRequest toHTTPRequest() {
244
245                if (getEndpointURI() == null)
246                        throw new SerializeException("The endpoint URI is not specified");
247
248                URL url;
249
250                try {
251                        url = getEndpointURI().toURL();
252
253                } catch (MalformedURLException e) {
254
255                        throw new SerializeException(e.getMessage(), e);
256                }
257
258                HTTPRequest httpRequest = new HTTPRequest(HTTPRequest.Method.POST, url);
259                httpRequest.setContentType(CommonContentTypes.APPLICATION_URLENCODED);
260
261                if (getClientAuthentication() != null) {
262                        getClientAuthentication().applyTo(httpRequest);
263                }
264
265                Map<String,String> params = httpRequest.getQueryParameters();
266
267                params.putAll(authzGrant.toParameters());
268
269                if (scope != null && ! scope.isEmpty()) {
270                        params.put("scope", scope.toString());
271                }
272
273                if (clientID != null) {
274                        params.put("client_id", clientID.getValue());
275                }
276
277                httpRequest.setQuery(URLUtils.serializeParameters(params));
278
279                return httpRequest;
280        }
281
282
283        /**
284         * Parses a token request from the specified HTTP request.
285         *
286         * @param httpRequest The HTTP request. Must not be {@code null}.
287         *
288         * @return The token request.
289         *
290         * @throws ParseException If the HTTP request couldn't be parsed to a
291         *                        token request.
292         */
293        public static TokenRequest parse(final HTTPRequest httpRequest)
294                throws ParseException {
295
296                // Only HTTP POST accepted
297                URI uri;
298
299                try {
300                        uri = httpRequest.getURL().toURI();
301
302                } catch (URISyntaxException e) {
303
304                        throw new ParseException(e.getMessage(), e);
305                }
306
307                httpRequest.ensureMethod(HTTPRequest.Method.POST);
308                httpRequest.ensureContentType(CommonContentTypes.APPLICATION_URLENCODED);
309
310                // Parse client authentication, if any
311                ClientAuthentication clientAuth = ClientAuthentication.parse(httpRequest);
312
313                // No fragment! May use query component!
314                Map<String,String> params = httpRequest.getQueryParameters();
315
316                // Parse grant
317                AuthorizationGrant grant = AuthorizationGrant.parse(params);
318
319                if (clientAuth == null && grant.getType().requiresClientAuthentication()) {
320                        String msg = "Missing client authentication";
321                        throw new ParseException(msg, OAuth2Error.INVALID_CLIENT.appendDescription(": " + msg));
322                }
323
324                // Parse client id
325                ClientID clientID = null;
326
327                if (clientAuth == null) {
328
329                        // Parse optional client ID
330                        String clientIDString = params.get("client_id");
331
332                        if (clientIDString != null && ! clientIDString.trim().isEmpty())
333                                clientID = new ClientID(clientIDString);
334
335                        if (clientID == null && grant.getType().requiresClientID()) {
336                                String msg = "Missing required \"client_id\" parameter";
337                                throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg));
338                        }
339                }
340
341                // Parse optional scope
342                String scopeValue = params.get("scope");
343
344                Scope scope = null;
345
346                if (scopeValue != null) {
347                        scope = Scope.parse(scopeValue);
348                }
349
350
351                if (clientAuth != null) {
352                        return new TokenRequest(uri, clientAuth, grant, scope);
353                } else {
354                        return new TokenRequest(uri, clientID, grant, scope);
355                }
356        }
357}