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 * &code=SplxlOBeZQQYbYS6WxSbIA 035 * &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}