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