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;
011
012import com.nimbusds.oauth2.sdk.auth.ClientAuthentication;
013import com.nimbusds.oauth2.sdk.http.CommonContentTypes;
014import com.nimbusds.oauth2.sdk.http.HTTPRequest;
015import com.nimbusds.oauth2.sdk.id.ClientID;
016import com.nimbusds.oauth2.sdk.util.URLUtils;
017import net.jcip.annotations.Immutable;
018import org.apache.commons.collections4.MapUtils;
019
020
021/**
022 * Token request. Used to obtain an
023 * {@link com.nimbusds.oauth2.sdk.token.AccessToken access token} and an
024 * optional {@link com.nimbusds.oauth2.sdk.token.RefreshToken refresh token}
025 * at the Token endpoint of the authorisation server. Supports custom request
026 * parameters.
027 *
028 * <p>Example token request with an authorisation code grant:
029 *
030 * <pre>
031 * POST /token HTTP/1.1
032 * Host: server.example.com
033 * Content-Type: application/x-www-form-URIencoded
034 * Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW
035 *
036 * grant_type=authorization_code
037 * &amp;code=SplxlOBeZQQYbYS6WxSbIA
038 * &amp;redirect_uri=https%3A%2F%2Fclient.example.org%2Fcb
039 * </pre>
040 *
041 * <p>Related specifications:
042 *
043 * <ul>
044 *     <li>OAuth 2.0 (RFC 6749), sections 4.1.3, 4.3.2, 4.4.2 and 6.
045 * </ul>
046 */
047@Immutable
048public class TokenRequest extends AbstractOptionallyIdentifiedRequest {
049
050
051        /**
052         * The authorisation grant.
053         */
054        private final AuthorizationGrant authzGrant;
055
056
057        /**
058         * The requested scope, {@code null} if not specified.
059         */
060        private final Scope scope;
061
062
063        /**
064         * Additional custom request parameters.
065         */
066        private final Map<String,String> customParams;
067
068
069        /**
070         * Creates a new token request with the specified client
071         * authentication.
072         *
073         * @param uri        The URI of the token endpoint. May be
074         *                   {@code null} if the {@link #toHTTPRequest} method
075         *                   will not be used.
076         * @param clientAuth The client authentication. Must not be
077         *                   {@code null}.
078         * @param authzGrant The authorisation grant. Must not be {@code null}.
079         * @param scope      The requested scope, {@code null} if not
080         *                   specified.
081         */
082        public TokenRequest(final URI uri,
083                            final ClientAuthentication clientAuth,
084                            final AuthorizationGrant authzGrant,
085                            final Scope scope) {
086
087                this(uri, clientAuth, authzGrant, scope, null);
088        }
089
090
091        /**
092         * Creates a new token request with the specified client
093         * authentication and additional custom parameters.
094         *
095         * @param uri          The URI of the token endpoint. May be
096         *                     {@code null} if the {@link #toHTTPRequest}
097         *                     method will not be used.
098         * @param clientAuth   The client authentication. Must not be
099         *                     {@code null}.
100         * @param authzGrant   The authorisation grant. Must not be
101         *                     {@code null}.
102         * @param scope        The requested scope, {@code null} if not
103         *                     specified.
104         * @param customParams Additional custom parameters to be included in
105         *                     the request body, empty map or {@code null} if
106         *                     none.
107         */
108        public TokenRequest(final URI uri,
109                            final ClientAuthentication clientAuth,
110                            final AuthorizationGrant authzGrant,
111                            final Scope scope,
112                            final Map<String,String> customParams) {
113
114                super(uri, clientAuth);
115
116                if (clientAuth == null)
117                        throw new IllegalArgumentException("The client authentication must not be null");
118
119                this.authzGrant = authzGrant;
120
121                this.scope = scope;
122
123                if (MapUtils.isNotEmpty(customParams)) {
124                        this.customParams = customParams;
125                } else {
126                        this.customParams = Collections.emptyMap();
127                }
128        }
129
130
131        /**
132         * Creates a new token request with the specified client
133         * authentication.
134         *
135         * @param uri        The URI of the token endpoint. May be
136         *                   {@code null} if the {@link #toHTTPRequest} method
137         *                   will not be used.
138         * @param clientAuth The client authentication. Must not be
139         *                   {@code null}.
140         * @param authzGrant The authorisation grant. Must not be {@code null}.
141         */
142        public TokenRequest(final URI uri,
143                            final ClientAuthentication clientAuth,
144                            final AuthorizationGrant authzGrant) {
145
146                this(uri, clientAuth, authzGrant, null);
147        }
148
149
150        /**
151         * Creates a new token request, with no explicit client authentication
152         * (may be present in the grant depending on its type).
153         *
154         * @param uri        The URI of the token endpoint. May be
155         *                   {@code null} if the {@link #toHTTPRequest} method
156         *                   will not be used.
157         * @param clientID   The client identifier, {@code null} if not
158         *                   specified.
159         * @param authzGrant The authorisation grant. Must not be {@code null}.
160         * @param scope      The requested scope, {@code null} if not
161         *                   specified.
162         */
163        public TokenRequest(final URI uri,
164                            final ClientID clientID,
165                            final AuthorizationGrant authzGrant,
166                            final Scope scope) {
167
168                this(uri, clientID, authzGrant, scope, null);
169        }
170
171
172        /**
173         * Creates a new token request, with no explicit client authentication
174         * (may be present in the grant depending on its type) and additional
175         * custom parameters.
176         *
177         * @param uri          The URI of the token endpoint. May be
178         *                     {@code null} if the {@link #toHTTPRequest}
179         *                     method will not be used.
180         * @param clientID     The client identifier, {@code null} if not
181         *                     specified.
182         * @param authzGrant   The authorisation grant. Must not be
183         *                     {@code null}.
184         * @param scope        The requested scope, {@code null} if not
185         *                     specified.
186         * @param customParams Additional custom parameters to be included in
187         *                     the request body, empty map or {@code null} if
188         *                     none.
189         */
190        public TokenRequest(final URI uri,
191                            final ClientID clientID,
192                            final AuthorizationGrant authzGrant,
193                            final Scope scope,
194                            final Map<String,String> customParams) {
195
196                super(uri, clientID);
197
198                if (authzGrant.getType().requiresClientAuthentication()) {
199                        throw new IllegalArgumentException("The \"" + authzGrant.getType() + "\" grant type requires client authentication");
200                }
201
202                if (authzGrant.getType().requiresClientID() && clientID == null) {
203                        throw new IllegalArgumentException("The \"" + authzGrant.getType() + "\" grant type requires a \"client_id\" parameter");
204                }
205
206                this.authzGrant = authzGrant;
207
208                this.scope = scope;
209
210                if (MapUtils.isNotEmpty(customParams)) {
211                        this.customParams = customParams;
212                } else {
213                        this.customParams = Collections.emptyMap();
214                }
215        }
216
217
218        /**
219         * Creates a new token request, with no explicit client authentication
220         * (may be present in the grant depending on its type).
221         *
222         * @param uri        The URI of the token endpoint. May be
223         *                   {@code null} if the {@link #toHTTPRequest} method
224         *                   will not be used.
225         * @param clientID   The client identifier, {@code null} if not
226         *                   specified.
227         * @param authzGrant The authorisation grant. Must not be {@code null}.
228         */
229        public TokenRequest(final URI uri,
230                            final ClientID clientID,
231                            final AuthorizationGrant authzGrant) {
232
233                this(uri, clientID, authzGrant, null);
234        }
235
236
237        /**
238         * Creates a new token request, without client authentication and a
239         * specified client identifier.
240         *
241         * @param uri        The URI of the token endpoint. May be
242         *                   {@code null} if the {@link #toHTTPRequest} method
243         *                   will not be used.
244         * @param authzGrant The authorisation grant. Must not be {@code null}.
245         * @param scope      The requested scope, {@code null} if not
246         *                   specified.
247         */
248        public TokenRequest(final URI uri,
249                            final AuthorizationGrant authzGrant,
250                            final Scope scope) {
251
252                this(uri, (ClientID)null, authzGrant, scope);
253        }
254
255
256        /**
257         * Creates a new token request, without client authentication and a
258         * specified client identifier.
259         *
260         * @param uri        The URI of the token endpoint. May be
261         *                   {@code null} if the {@link #toHTTPRequest} method
262         *                   will not be used.
263         * @param authzGrant The authorisation grant. Must not be {@code null}.
264         */
265        public TokenRequest(final URI uri,
266                            final AuthorizationGrant authzGrant) {
267
268                this(uri, (ClientID)null, authzGrant, null);
269        }
270
271
272        /**
273         * Gets the authorisation grant.
274         *
275         * @return The authorisation grant.
276         */
277        public AuthorizationGrant getAuthorizationGrant() {
278
279                return authzGrant;
280        }
281
282
283        /**
284         * Gets the requested scope.
285         *
286         * @return The requested scope, {@code null} if not specified.
287         */
288        public Scope getScope() {
289
290                return scope;
291        }
292
293
294        /**
295         * Returns the additional custom parameters included in the request
296         * body.
297         *
298         * <p>Example:
299         *
300         * <pre>
301         * resource=http://xxxxxx/PartyOData
302         * </pre>
303         *
304         * @return The additional custom parameters as a unmodifiable map,
305         *         empty map if none.
306         */
307        public Map<String,String> getCustomParameters () {
308
309                return customParams;
310        }
311
312
313        /**
314         * Returns the specified custom parameter included in the request body.
315         *
316         * @param name The parameter name. Must not be {@code null}.
317         *
318         * @return The parameter value, {@code null} if not specified.
319         */
320        public String getCustomParameter(final String name) {
321
322                return customParams.get(name);
323        }
324
325
326        @Override
327        public HTTPRequest toHTTPRequest() {
328
329                if (getEndpointURI() == null)
330                        throw new SerializeException("The endpoint URI is not specified");
331
332                URL url;
333
334                try {
335                        url = getEndpointURI().toURL();
336
337                } catch (MalformedURLException e) {
338
339                        throw new SerializeException(e.getMessage(), e);
340                }
341
342                HTTPRequest httpRequest = new HTTPRequest(HTTPRequest.Method.POST, url);
343                httpRequest.setContentType(CommonContentTypes.APPLICATION_URLENCODED);
344
345                if (getClientAuthentication() != null) {
346                        getClientAuthentication().applyTo(httpRequest);
347                }
348
349                Map<String,String> params = httpRequest.getQueryParameters();
350
351                params.putAll(authzGrant.toParameters());
352
353                if (scope != null && ! scope.isEmpty()) {
354                        params.put("scope", scope.toString());
355                }
356
357                if (getClientID() != null) {
358                        params.put("client_id", getClientID().getValue());
359                }
360
361                if (! getCustomParameters().isEmpty()) {
362                        params.putAll(getCustomParameters());
363                }
364
365                httpRequest.setQuery(URLUtils.serializeParameters(params));
366
367                return httpRequest;
368        }
369
370
371        /**
372         * Parses a token request from the specified HTTP request.
373         *
374         * @param httpRequest The HTTP request. Must not be {@code null}.
375         *
376         * @return The token request.
377         *
378         * @throws ParseException If the HTTP request couldn't be parsed to a
379         *                        token request.
380         */
381        public static TokenRequest parse(final HTTPRequest httpRequest)
382                throws ParseException {
383
384                // Only HTTP POST accepted
385                URI uri;
386
387                try {
388                        uri = httpRequest.getURL().toURI();
389
390                } catch (URISyntaxException e) {
391
392                        throw new ParseException(e.getMessage(), e);
393                }
394
395                httpRequest.ensureMethod(HTTPRequest.Method.POST);
396                httpRequest.ensureContentType(CommonContentTypes.APPLICATION_URLENCODED);
397
398                // Parse client authentication, if any
399                ClientAuthentication clientAuth = ClientAuthentication.parse(httpRequest);
400
401                // No fragment! May use query component!
402                Map<String,String> params = httpRequest.getQueryParameters();
403
404                // Parse grant
405                AuthorizationGrant grant = AuthorizationGrant.parse(params);
406
407                if (clientAuth == null && grant.getType().requiresClientAuthentication()) {
408                        String msg = "Missing client authentication";
409                        throw new ParseException(msg, OAuth2Error.INVALID_CLIENT.appendDescription(": " + msg));
410                }
411
412                // Parse client id
413                ClientID clientID = null;
414
415                if (clientAuth == null) {
416
417                        // Parse optional client ID
418                        String clientIDString = params.get("client_id");
419
420                        if (clientIDString != null && ! clientIDString.trim().isEmpty())
421                                clientID = new ClientID(clientIDString);
422
423                        if (clientID == null && grant.getType().requiresClientID()) {
424                                String msg = "Missing required \"client_id\" parameter";
425                                throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg));
426                        }
427                }
428
429                // Parse optional scope
430                String scopeValue = params.get("scope");
431
432                Scope scope = null;
433
434                if (scopeValue != null) {
435                        scope = Scope.parse(scopeValue);
436                }
437
438                // Parse custom parameters
439                Map<String,String> customParams = new HashMap<>();
440
441                for (Map.Entry<String,String> p: params.entrySet()) {
442
443                        if (p.getKey().equalsIgnoreCase("grant_type")) {
444                                continue; // skip
445                        }
446
447                        if (p.getKey().equalsIgnoreCase("client_id")) {
448                                continue; // skip
449                        }
450
451                        if (p.getKey().equalsIgnoreCase("client_secret")) {
452                                continue; // skip
453                        }
454
455                        if (p.getKey().equalsIgnoreCase("client_assertion_type")) {
456                                continue; // skip
457                        }
458
459                        if (p.getKey().equalsIgnoreCase("client_assertion")) {
460                                continue; // skip
461                        }
462
463                        if (p.getKey().equalsIgnoreCase("scope")) {
464                                continue; // skip
465                        }
466
467                        if (! grant.getType().getRequestParameterNames().contains(p.getKey())) {
468                                // We have a custom (non-registered) parameter
469                                customParams.put(p.getKey(), p.getValue());
470                        }
471                }
472
473                if (clientAuth != null) {
474                        return new TokenRequest(uri, clientAuth, grant, scope, customParams);
475                } else {
476                        return new TokenRequest(uri, clientID, grant, scope, customParams);
477                }
478        }
479}