/*
* Copyright 2014 Stormpath, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.stormpath.sdk.oauth;
import com.stormpath.sdk.api.ApiRequestAuthenticator;
import com.stormpath.sdk.http.HttpRequest;
/**
* An OAuth-specific {@code ApiRequestAuthenticator} that implements the
* <a href="http://en.wikipedia.org/wiki/Builder_pattern">Builder design pattern</a> to allow customization of how
* the authentication attempt is processed. For example:
*
* <pre>
* AuthenticationResult result = {@link com.stormpath.sdk.application.Application#authenticateAccount(AuthenticationRequest)}
* application.authenticateOauthRequest(httpRequest)}
* <b>{@link #using(ScopeFactory) .using(scopeFactory)}
* {@link #withTtl(long) .withTtl(3600)}
* {@link #execute() .execute()};</b>
* </pre>
*
* @see com.stormpath.sdk.application.Application#authenticateAccount(AuthenticationRequest)}
* @see #execute()
* @since 1.0.RC
*/
public interface OAuthApiRequestAuthenticator extends ApiRequestAuthenticator {
/**
* Specifies the {@link ScopeFactory} to be used when generating a new Access Token as a result of authenticating
* the OAuth request.
*
* <p><b>This method should only be called when the OAuth client is specifically requesting a new Access Token</b>,
* for example, a request to your application's oauth token endpoint, e.g. {@code /oauth/token}</p>
*
* @param scopeFactory the {@link ScopeFactory} to be used for this authentication request.
* @return a new {@link AccessTokenRequestAuthenticator} instance created with the current state of the
* this builder.
*/
AccessTokenRequestAuthenticator using(ScopeFactory scopeFactory);
/**
* Specifies the <a href="http://en.wikipedia.org/wiki/Time_to_live">time to live</a> of this authentication request
* in seconds. If not specified, the default value is {@code 3600} (seconds) - i.e. 1 hour.
*
* <p><b>This method should only be called when the OAuth client is specifically requesting a new Access Token</b>,
* for example, a request to your application's oauth token endpoint, e.g. {@code /oauth/token}</p>
*
* @param ttl the time to live (in seconds) of this authentication request.
* @return a new {@link AccessTokenRequestAuthenticator} instance created with the current state of the
* this builder.
*/
AccessTokenRequestAuthenticator withTtl(long ttl);
/**
* Specifies the request location(s) that will be checked when looking up the request's Access Token. Unspecified
* locations will not be checked.
* <p>
* If this method is not called, both the request header and body will be checked by default.
* </p>
*
* <p>This method will return a new {@link ResourceRequestAuthenticator} with the current state of the this
* builder.</p>
*
* @param locations the location(s) for the <code>Bearer</code>.
* @return a new {@link ResourceRequestAuthenticator} instance created with the current state of the
* this builder.
*/
ResourceRequestAuthenticator inLocation(RequestLocation... locations);
/**
* Authenticates an OAuth-based HTTP request submitted to your application's API, returning a result that
* reflects the successfully authenticated {@link com.stormpath.sdk.account.Account} that made the request and the {@link com.stormpath.sdk.api.ApiKey} used to
* authenticate the request. Throws a {@link com.stormpath.sdk.resource.ResourceException} if the request cannot be authenticated.
*
* <p>This method is only useful if you know for sure the HTTP request is an Oauth-based request, and:
*
* <ul>
* <li>
* The request is authenticating with an Access Token and you want to explicitly control the
* locations in the request where you allow the access token to exist. If you're comfortable with the default
* behavior of inspecting the headers and request body (and not request params, as they can be seen as a less
* secure way of authentication), you do not need to call this method, and should call the
* ServletApi method instead.
* </li>
* <li>
* <p>The HTTP request is an OAuth Client Credentials Grant Type request whereby the client is explicitly
* asking for a new Access Token <em>and</em> you want to control the returned token's OAuth scope and/or
* time-to-live (TTL).</p>
* <p>This almost always is the case when the client is interacting with your
* OAuth token endpoint, for example, a URI like {@code /oauth2/tokens}. If either the request is a normal OAuth
* request or the above condition does not apply to you, then you do not need to call this method, and
* should call the {@link ApiRequestAuthenticator#authenticate(HttpRequest)} method instead.</p>
* </li>
* </ul>
* <p>Again, if either of these two scenarios above does not apply to your use case, do not call this method; call
* the {@link ApiRequestAuthenticator#authenticate(HttpRequest)} method instead.</p>
*
* <p>Next, we'll cover these 2 scenarios.</p>
*
* <h3>Scenario 1: OAuth (Bearer) Access Token Allowed Locations</h3>
*
* <p>By default, this method and {@link ApiRequestAuthenticator#authenticate(HttpRequest)} will authenticate an OAuth request
* that presents its (bearer) Access Token in two of three locations in the request:
* <ol>
* <li>
* The request's {@code Authorization} header, per the
* <a href="http://tools.ietf.org/html/rfc6750#section-2.1">OAuth 2 Bearer Token specification, Section
* 2.1</a>.
* </li>
* <li>
* The request {@code application/x-www-form-urlencoded} body as a {@code access_token} parameter, per the
* <a href="http://tools.ietf.org/html/rfc6750#section-2.2">OAuth 2 Bearer Token specification, Section
* 2.2</a>
* </li>
* </ol>
* </p>
* <p>
* Although checking a request {@code access_token} query parameter, per the
* <a href="http://tools.ietf.org/html/rfc6750#section-2.3">OAuth 2 Bearer Token specification, Section 2.3</a> is
* also supported, query parameters are <em>NOT</em> inspected by default. Using request parameters for
* authentication is often seen as a potential security risk and generally discouraged. That being said, if you
* need to support this location, perhaps because you need to support a legacy client, you can enable this
* location explicitly if desired, for example:
* <pre>
* import static com.stormpath.sdk.oauth.RequestLocation.*;
*
* Application application = client.getResource(myApplicationRestUrl, Application.class);
*
* OAuthAuthenticationResult result = Applications.oauthRequestAuthenticator(application)
* <b>{@link OAuthApiRequestAuthenticator#inLocation(com.stormpath.sdk.oauth.RequestLocation...) .inLocation(}{@link com.stormpath.sdk.oauth.RequestLocation#HEADER HEADER}, {@link com.stormpath.sdk.oauth.RequestLocation#BODY BODY}, {@link com.stormpath.sdk.oauth.RequestLocation#QUERY_PARAM QUERY_PARAM})</b>
* .authenticate(httpRequest);
* </pre>
* </p>
*
* <p>The above code example implies that you will tell developers which options they
* may use (and may not use) when configuring their OAuth client to communicate with your API.</p>
*
* <h3>Scenario 2: Creating OAuth Access Tokens</h3>
*
* <p>If the HTTP request is sent to your OAuth token creation endpoint, for example, {@code /oauth2/token} you
* will need to call this method, and the Stormpath SDK will automatically create an Access Token for you. After
* it is created, you must send the token to the client in the HTTP response. For example:
*
* <pre>
* //assume a POST request to, say, https://api.mycompany.com/oauth/token:
*
* public void processOauthTokenRequest(HttpServletRequest request, HttpServletResponse response) {
*
* Application application = client.getResource(myApplicationRestUrl, Application.class);
*
* AccessTokenResult result = (AccessTokenResult) Applications.oauthRequestAuthenticator(application).authenticate(request);
*
* <b>TokenResponse token = result.getTokenResponse();
*
* response.setStatus(HttpServletResponse.SC_OK);
* response.setContentType("application/json");
* response.getWriter().print(token.toJson());</b>
*
* response.getWriter().flush();
* }
* </pre>
* </p>
*
* <p>As you can see, {@link com.stormpath.sdk.oauth.TokenResponse#toJson() tokenResponse.toJson()} method
* will return a JSON string to populate the response body - it is not strictly
* necessary to read individual properties on the {@code TokenResponse} instance.</p>
*
* <h4>Non Servlet Environments</h4>
*
* <p>If your application does not run in a Servlet environment - for example, maybe you use a custom HTTP
* framework, or Netty, or Play!, you can use the {@link com.stormpath.sdk.http.HttpRequestBuilder
* HttpRequestBuilder} to represent your framework-specific HTTP request object as a type the Stormpath SDK
* understands. For example:</p>
* <pre>
* ...
* <b>// Convert the framework-specific HTTP Request into a request type the Stormpath SDK understands:
* {@link com.stormpath.sdk.http.HttpRequest HttpRequest} request = {@link com.stormpath.sdk.http.HttpRequests HttpRequests}.method(frameworkSpecificRequest.getMethod())
* .headers(frameworkSpecificRequest.getHeaders())
* .queryParameters(frameworkSpecificRequest.getQueryParameters())
* .build();</b>
*
* ApiAuthenticationResult result = Applications.oauthRequestAuthenticator(application).authenticate(request);
* ...
* </pre>
*
* <h4>Customizing the OAuth Access Token Time-To-Live (TTL)</h4>
*
* <p>By default, this SDK creates Access Tokens that are valid for 3600 seconds (1 hour). If you want to change
* this value, you will need to invoke the {@code withTtl} method on the returned executor and specify your desired
* TTL. For example:
*
* <pre>
* //assume a POST request to, say, https://api.mycompany.com/oauth/token:
*
* public void processOauthTokenRequest(HttpServletRequest request, HttpServletResponse response) {
*
* Application application = client.getResource(myApplicationRestUrl, Application.class);
*
* <b>int desiredTimeoutSeconds = 3600; //change to your preferred value</b>
*
* AccessTokenResult result = (AccessTokenResult) Applications.oauthRequestAuthenticator(application)
* .withTtl(desiredTimeoutSeconds)
* .authenticate(request);
*
* TokenResponse token = result.getTokenResponse();
*
* response.setStatus(HttpServletResponse.SC_OK);
* response.setContentType("application/json");
* response.getWriter().print(token.toJson());
*
* response.getWriter().flush();
* }
* </pre>
* </p>
*
* <h4>Customizing the OAuth Access Token Scope</h4>
*
* <p>As an Authorization protocol, OAuth allows you to attach <em>scope</em>, aka application-specific
* <em>permissions</em> to an Access Token when it is created. You can check this scope on
* {@link ApiRequestAuthenticator#authenticate(HttpRequest)} later requests} and make authorization decisions to allow or deny the API
* request based on the granted scope.</p>
*
* <p>When an access token is created, you can specify your application's own custom scope by calling the
* {@code withScope} method on the returned executor. For example:
*
* <pre>
* //assume a POST request to, say, https://api.mycompany.com/oauth/token:
*
* public void processOauthTokenRequest(HttpServletRequest request, HttpServletResponse response) {
*
* Application application = client.getResource(myApplicationRestUrl, Application.class);
*
* int desiredTimeoutSeconds = 3600; //change to your preferred value
*
* <b>ScopeFactory scopeFactory = getScopeFactory(); //get your ScopeFactory implementation from your app config</b>
*
* AccessTokenResult result = (AccessTokenResult)application
* .authenticateOauthRequest(request)
* .withTtl(desiredTimeoutSeconds)
* <b>.withScopeFactory(scopeFactory)</b>
* .authenticate(request);
*
* TokenResponse token = result.getTokenResponse();
*
* response.setStatus(HttpServletResponse.SC_OK);
* response.setContentType("application/json");
* response.getWriter().print(token.toJson());
*
* response.getWriter().flush();
* }
* </pre>
* </p>
*
* <p>Your {@link com.stormpath.sdk.oauth.ScopeFactory ScopeFactory} implementation can inspect the
* 1) successfully authenticated API client Account and 2) the client's <em>requested</em> scope. Your
* implementation returns the <em>actual</em> scope that you want granted to the Access Token (which may or may not
* be different than the requested scope based on your requirements).</p>
*
* @param httpRequest a {@link com.stormpath.sdk.http.HttpRequest} instance.
* @return a new {@link OAuthAuthenticationResult} if the API request was authenticated successfully.
* @throws IllegalArgumentException if the method argument is null or is not either a {@link com.stormpath.sdk.http.HttpRequest} instance.
* @see ApiRequestAuthenticator#authenticate(com.stormpath.sdk.http.HttpRequest)
*
* @since 1.0.RC4.6
*/
OAuthAuthenticationResult authenticate(HttpRequest httpRequest);
}