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 no explicit client authentication
107         * (may be present in the grant depending on its type).
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 clientID   The client identifier, {@code null} if not
113         *                   specified.
114         * @param authzGrant The authorisation grant. Must not be {@code null}.
115         * @param scope      The requested scope, {@code null} if not
116         *                   specified.
117         */
118        public TokenRequest(final URI uri,
119                            final ClientID clientID,
120                            final AuthorizationGrant authzGrant,
121                            final Scope scope) {
122
123                super(uri);
124
125                if (authzGrant.getType().requiresClientAuthentication()) {
126                        throw new IllegalArgumentException("The \"" + authzGrant.getType() + "\" grant type requires client authentication");
127                }
128
129                if (authzGrant.getType().requiresClientID() && clientID == null) {
130                        throw new IllegalArgumentException("The \"" + authzGrant.getType() + "\" grant type requires a \"client_id\" parameter");
131                }
132
133                this.authzGrant = authzGrant;
134
135                this.clientID = clientID;
136                clientAuth = null;
137
138                this.scope = scope;
139        }
140
141
142        /**
143         * Creates a new token request, without client authentication and a
144         * specified client identifier.
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 authzGrant The authorisation grant. Must not be {@code null}.
150         * @param scope      The requested scope, {@code null} if not
151         *                   specified.
152         */
153        public TokenRequest(final URI uri,
154                            final AuthorizationGrant authzGrant,
155                            final Scope scope) {
156
157                this(uri, (ClientID)null, authzGrant, scope);
158        }
159
160
161        /**
162         * Gets the client authentication.
163         *
164         * @see #getClientID()
165         *
166         * @return The client authentication, {@code null} if none.
167         */
168        public ClientAuthentication getClientAuthentication() {
169
170                return clientAuth;
171        }
172
173
174        /**
175         * Gets the client identifier (for a token request without explicit
176         * client authentication).
177         *
178         * @see #getClientAuthentication()
179         *
180         * @return The client identifier, {@code null} if not specified.
181         */
182        public ClientID getClientID() {
183
184                return clientID;
185        }
186
187
188
189        /**
190         * Gets the authorisation grant.
191         *
192         * @return The authorisation grant.
193         */
194        public AuthorizationGrant getAuthorizationGrant() {
195
196                return authzGrant;
197        }
198
199
200        /**
201         * Gets the requested scope.
202         *
203         * @return The requested scope, {@code null} if not specified.
204         */
205        public Scope getScope() {
206
207                return scope;
208        }
209
210
211        @Override
212        public HTTPRequest toHTTPRequest()
213                throws SerializeException {
214
215                if (getEndpointURI() == null)
216                        throw new SerializeException("The endpoint URI is not specified");
217
218                URL url;
219
220                try {
221                        url = getEndpointURI().toURL();
222
223                } catch (MalformedURLException e) {
224
225                        throw new SerializeException(e.getMessage(), e);
226                }
227
228                HTTPRequest httpRequest = new HTTPRequest(HTTPRequest.Method.POST, url);
229                httpRequest.setContentType(CommonContentTypes.APPLICATION_URLENCODED);
230
231                if (getClientAuthentication() != null)
232                        getClientAuthentication().applyTo(httpRequest);
233
234                Map<String,String> params = authzGrant.toParameters();
235
236                if (scope != null && ! scope.isEmpty()) {
237                        params.put("scope", scope.toString());
238                }
239
240                if (clientID != null) {
241                        params.put("client_id", clientID.getValue());
242                }
243
244                httpRequest.setQuery(URLUtils.serializeParameters(params));
245
246                return httpRequest;
247        }
248
249
250        /**
251         * Parses a token request from the specified HTTP request.
252         *
253         * @param httpRequest The HTTP request. Must not be {@code null}.
254         *
255         * @return The token request.
256         *
257         * @throws ParseException If the HTTP request couldn't be parsed to a
258         *                        token request.
259         */
260        public static TokenRequest parse(final HTTPRequest httpRequest)
261                throws ParseException {
262
263                // Only HTTP POST accepted
264                URI uri;
265
266                try {
267                        uri = httpRequest.getURL().toURI();
268
269                } catch (URISyntaxException e) {
270
271                        throw new ParseException(e.getMessage(), e);
272                }
273
274                httpRequest.ensureMethod(HTTPRequest.Method.POST);
275                httpRequest.ensureContentType(CommonContentTypes.APPLICATION_URLENCODED);
276
277                // Parse client authentication, if any
278                ClientAuthentication clientAuth = ClientAuthentication.parse(httpRequest);
279
280                // No fragment! May use query component!
281                Map<String,String> params = httpRequest.getQueryParameters();
282
283                // Parse grant
284                AuthorizationGrant grant = AuthorizationGrant.parse(params);
285
286                if (clientAuth == null && grant.getType().requiresClientAuthentication()) {
287                        throw new ParseException("Missing client authentication", OAuth2Error.INVALID_CLIENT);
288                }
289
290                // Parse client id
291                ClientID clientID = null;
292
293                if (clientAuth == null) {
294
295                        // Parse optional client ID
296                        String clientIDString = params.get("client_id");
297
298                        if (clientIDString != null && clientIDString.trim().length() > 0)
299                                clientID = new ClientID(clientIDString);
300
301                        if (clientID == null && grant.getType().requiresClientID()) {
302                                throw new ParseException("Missing required \"client_id\" parameter", OAuth2Error.INVALID_REQUEST);
303                        }
304                }
305
306                // Parse optional scope
307                String scopeValue = params.get("scope");
308
309                Scope scope = null;
310
311                if (scopeValue != null) {
312                        scope = Scope.parse(scopeValue);
313                }
314
315
316                if (clientAuth != null) {
317                        return new TokenRequest(uri, clientAuth, grant, scope);
318                } else {
319                        return new TokenRequest(uri, clientID, grant, scope);
320                }
321        }
322}