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
268                if (getEndpointURI() == null)
269                        throw new SerializeException("The endpoint URI is not specified");
270
271                URL url;
272
273                try {
274                        url = getEndpointURI().toURL();
275
276                } catch (MalformedURLException e) {
277
278                        throw new SerializeException(e.getMessage(), e);
279                }
280
281                HTTPRequest httpRequest = new HTTPRequest(HTTPRequest.Method.POST, url);
282                httpRequest.setContentType(CommonContentTypes.APPLICATION_URLENCODED);
283
284                if (getClientAuthentication() != null) {
285                        getClientAuthentication().applyTo(httpRequest);
286                }
287
288                Map<String,String> params = httpRequest.getQueryParameters();
289
290                params.putAll(authzGrant.toParameters());
291
292                if (scope != null && ! scope.isEmpty()) {
293                        params.put("scope", scope.toString());
294                }
295
296                if (clientID != null) {
297                        params.put("client_id", clientID.getValue());
298                }
299
300                httpRequest.setQuery(URLUtils.serializeParameters(params));
301
302                return httpRequest;
303        }
304
305
306        /**
307         * Parses a token request from the specified HTTP request.
308         *
309         * @param httpRequest The HTTP request. Must not be {@code null}.
310         *
311         * @return The token request.
312         *
313         * @throws ParseException If the HTTP request couldn't be parsed to a
314         *                        token request.
315         */
316        public static TokenRequest parse(final HTTPRequest httpRequest)
317                throws ParseException {
318
319                // Only HTTP POST accepted
320                URI uri;
321
322                try {
323                        uri = httpRequest.getURL().toURI();
324
325                } catch (URISyntaxException e) {
326
327                        throw new ParseException(e.getMessage(), e);
328                }
329
330                httpRequest.ensureMethod(HTTPRequest.Method.POST);
331                httpRequest.ensureContentType(CommonContentTypes.APPLICATION_URLENCODED);
332
333                // Parse client authentication, if any
334                ClientAuthentication clientAuth = ClientAuthentication.parse(httpRequest);
335
336                // No fragment! May use query component!
337                Map<String,String> params = httpRequest.getQueryParameters();
338
339                // Parse grant
340                AuthorizationGrant grant = AuthorizationGrant.parse(params);
341
342                if (clientAuth == null && grant.getType().requiresClientAuthentication()) {
343                        String msg = "Missing client authentication";
344                        throw new ParseException(msg, OAuth2Error.INVALID_CLIENT.appendDescription(": " + msg));
345                }
346
347                // Parse client id
348                ClientID clientID = null;
349
350                if (clientAuth == null) {
351
352                        // Parse optional client ID
353                        String clientIDString = params.get("client_id");
354
355                        if (clientIDString != null && clientIDString.trim().length() > 0)
356                                clientID = new ClientID(clientIDString);
357
358                        if (clientID == null && grant.getType().requiresClientID()) {
359                                String msg = "Missing required \"client_id\" parameter";
360                                throw new ParseException(msg, OAuth2Error.INVALID_REQUEST.appendDescription(": " + msg));
361                        }
362                }
363
364                // Parse optional scope
365                String scopeValue = params.get("scope");
366
367                Scope scope = null;
368
369                if (scopeValue != null) {
370                        scope = Scope.parse(scopeValue);
371                }
372
373
374                if (clientAuth != null) {
375                        return new TokenRequest(uri, clientAuth, grant, scope);
376                } else {
377                        return new TokenRequest(uri, clientID, grant, scope);
378                }
379        }
380}