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 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}