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