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