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