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 AbstractRequest {
046
047
048        /**
049         * The client authentication, {@code null} if none.
050         */
051        private final ClientAuthentication clientAuth;
052
053
054        /**
055         * The client identifier, {@code null} if not specified.
056         */
057        private final ClientID clientID;
058
059
060        /**
061         * The authorisation grant.
062         */
063        private final AuthorizationGrant authzGrant;
064
065
066        /**
067         * The requested scope, {@code null} if not specified.
068         */
069        private final Scope scope;
070
071
072        /**
073         * Creates a new token request with the specified client
074         * authentication.
075         *
076         * @param uri        The URI of the token endpoint. May be
077         *                   {@code null} if the {@link #toHTTPRequest} method
078         *                   will not be used.
079         * @param clientAuth The client authentication. Must not be
080         *                   {@code null}.
081         * @param authzGrant The authorisation grant. Must not be {@code null}.
082         * @param scope      The requested scope, {@code null} if not
083         *                   specified.
084         */
085        public TokenRequest(final URI uri,
086                            final ClientAuthentication clientAuth,
087                            final AuthorizationGrant authzGrant,
088                            final Scope scope) {
089
090                super(uri);
091
092                if (clientAuth == null)
093                        throw new IllegalArgumentException("The client authentication must not be null");
094
095                this.clientAuth = clientAuth;
096
097                clientID = null; // must not be set when client auth is present
098
099                this.authzGrant = authzGrant;
100
101                this.scope = scope;
102        }
103
104
105        /**
106         * Creates a new token request with the specified client
107         * authentication.
108         *
109         * @param uri        The URI of the token endpoint. May be
110         *                   {@code null} if the {@link #toHTTPRequest} method
111         *                   will not be used.
112         * @param clientAuth The client authentication. Must not be
113         *                   {@code null}.
114         * @param authzGrant The authorisation grant. Must not be {@code null}.
115         */
116        public TokenRequest(final URI uri,
117                            final ClientAuthentication clientAuth,
118                            final AuthorizationGrant authzGrant) {
119
120                this(uri, clientAuth, authzGrant, null);
121        }
122
123
124        /**
125         * Creates a new token request, with no explicit client authentication
126         * (may be present in the grant depending on its type).
127         *
128         * @param uri        The URI of the token endpoint. May be
129         *                   {@code null} if the {@link #toHTTPRequest} method
130         *                   will not be used.
131         * @param clientID   The client identifier, {@code null} if not
132         *                   specified.
133         * @param authzGrant The authorisation grant. Must not be {@code null}.
134         * @param scope      The requested scope, {@code null} if not
135         *                   specified.
136         */
137        public TokenRequest(final URI uri,
138                            final ClientID clientID,
139                            final AuthorizationGrant authzGrant,
140                            final Scope scope) {
141
142                super(uri);
143
144                if (authzGrant.getType().requiresClientAuthentication()) {
145                        throw new IllegalArgumentException("The \"" + authzGrant.getType() + "\" grant type requires client authentication");
146                }
147
148                if (authzGrant.getType().requiresClientID() && clientID == null) {
149                        throw new IllegalArgumentException("The \"" + authzGrant.getType() + "\" grant type requires a \"client_id\" parameter");
150                }
151
152                this.authzGrant = authzGrant;
153
154                this.clientID = clientID;
155                clientAuth = null;
156
157                this.scope = scope;
158        }
159
160
161        /**
162         * Creates a new token request, with no explicit client authentication
163         * (may be present in the grant depending on its type).
164         *
165         * @param uri        The URI of the token endpoint. May be
166         *                   {@code null} if the {@link #toHTTPRequest} method
167         *                   will not be used.
168         * @param clientID   The client identifier, {@code null} if not
169         *                   specified.
170         * @param authzGrant The authorisation grant. Must not be {@code null}.
171         */
172        public TokenRequest(final URI uri,
173                            final ClientID clientID,
174                            final AuthorizationGrant authzGrant) {
175
176                this(uri, clientID, authzGrant, null);
177        }
178
179
180        /**
181         * Creates a new token request, without client authentication and a
182         * specified client identifier.
183         *
184         * @param uri        The URI of the token endpoint. May be
185         *                   {@code null} if the {@link #toHTTPRequest} method
186         *                   will not be used.
187         * @param authzGrant The authorisation grant. Must not be {@code null}.
188         * @param scope      The requested scope, {@code null} if not
189         *                   specified.
190         */
191        public TokenRequest(final URI uri,
192                            final AuthorizationGrant authzGrant,
193                            final Scope scope) {
194
195                this(uri, (ClientID)null, authzGrant, scope);
196        }
197
198
199        /**
200         * Creates a new token request, without client authentication and a
201         * specified client identifier.
202         *
203         * @param uri        The URI of the token endpoint. May be
204         *                   {@code null} if the {@link #toHTTPRequest} method
205         *                   will not be used.
206         * @param authzGrant The authorisation grant. Must not be {@code null}.
207         */
208        public TokenRequest(final URI uri,
209                            final AuthorizationGrant authzGrant) {
210
211                this(uri, (ClientID)null, authzGrant, null);
212        }
213
214
215        /**
216         * Gets the client authentication.
217         *
218         * @see #getClientID()
219         *
220         * @return The client authentication, {@code null} if none.
221         */
222        public ClientAuthentication getClientAuthentication() {
223
224                return clientAuth;
225        }
226
227
228        /**
229         * Gets the client identifier (for a token request without explicit
230         * client authentication).
231         *
232         * @see #getClientAuthentication()
233         *
234         * @return The client identifier, {@code null} if not specified.
235         */
236        public ClientID getClientID() {
237
238                return clientID;
239        }
240
241
242
243        /**
244         * Gets the authorisation grant.
245         *
246         * @return The authorisation grant.
247         */
248        public AuthorizationGrant getAuthorizationGrant() {
249
250                return authzGrant;
251        }
252
253
254        /**
255         * Gets the requested scope.
256         *
257         * @return The requested scope, {@code null} if not specified.
258         */
259        public Scope getScope() {
260
261                return scope;
262        }
263
264
265        @Override
266        public HTTPRequest toHTTPRequest()
267                throws SerializeException {
268
269                if (getEndpointURI() == null)
270                        throw new SerializeException("The endpoint URI is not specified");
271
272                URL url;
273
274                try {
275                        url = getEndpointURI().toURL();
276
277                } catch (MalformedURLException e) {
278
279                        throw new SerializeException(e.getMessage(), e);
280                }
281
282                HTTPRequest httpRequest = new HTTPRequest(HTTPRequest.Method.POST, url);
283                httpRequest.setContentType(CommonContentTypes.APPLICATION_URLENCODED);
284
285                if (getClientAuthentication() != null) {
286                        getClientAuthentication().applyTo(httpRequest);
287                }
288
289                Map<String,String> params = httpRequest.getQueryParameters();
290
291                params.putAll(authzGrant.toParameters());
292
293                if (scope != null && ! scope.isEmpty()) {
294                        params.put("scope", scope.toString());
295                }
296
297                if (clientID != null) {
298                        params.put("client_id", clientID.getValue());
299                }
300
301                httpRequest.setQuery(URLUtils.serializeParameters(params));
302
303                return httpRequest;
304        }
305
306
307        /**
308         * Parses a token request from the specified HTTP request.
309         *
310         * @param httpRequest The HTTP request. Must not be {@code null}.
311         *
312         * @return The token request.
313         *
314         * @throws ParseException If the HTTP request couldn't be parsed to a
315         *                        token request.
316         */
317        public static TokenRequest parse(final HTTPRequest httpRequest)
318                throws ParseException {
319
320                // Only HTTP POST accepted
321                URI uri;
322
323                try {
324                        uri = httpRequest.getURL().toURI();
325
326                } catch (URISyntaxException e) {
327
328                        throw new ParseException(e.getMessage(), e);
329                }
330
331                httpRequest.ensureMethod(HTTPRequest.Method.POST);
332                httpRequest.ensureContentType(CommonContentTypes.APPLICATION_URLENCODED);
333
334                // Parse client authentication, if any
335                ClientAuthentication clientAuth = ClientAuthentication.parse(httpRequest);
336
337                // No fragment! May use query component!
338                Map<String,String> params = httpRequest.getQueryParameters();
339
340                // Parse grant
341                AuthorizationGrant grant = AuthorizationGrant.parse(params);
342
343                if (clientAuth == null && grant.getType().requiresClientAuthentication()) {
344                        String msg = "Missing client authentication";
345                        throw new ParseException(msg, OAuth2Error.INVALID_CLIENT.appendDescription(": " + msg));
346                }
347
348                // Parse client id
349                ClientID clientID = null;
350
351                if (clientAuth == null) {
352
353                        // Parse optional client ID
354                        String clientIDString = params.get("client_id");
355
356                        if (clientIDString != null && clientIDString.trim().length() > 0)
357                                clientID = new ClientID(clientIDString);
358
359                        if (clientID == null && grant.getType().requiresClientID()) {
360                                String msg = "Missing required \"client_id\" parameter";
361                                throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg));
362                        }
363                }
364
365                // Parse optional scope
366                String scopeValue = params.get("scope");
367
368                Scope scope = null;
369
370                if (scopeValue != null) {
371                        scope = Scope.parse(scopeValue);
372                }
373
374
375                if (clientAuth != null) {
376                        return new TokenRequest(uri, clientAuth, grant, scope);
377                } else {
378                        return new TokenRequest(uri, clientID, grant, scope);
379                }
380        }
381}