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