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