/*
**
** Copyright 2014, Jules White
**
**
*/
package org.magnum.videoup.client.oauth;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executor;
import org.apache.commons.io.IOUtils;
import retrofit.Endpoint;
import retrofit.ErrorHandler;
import retrofit.Profiler;
import retrofit.RequestInterceptor;
import retrofit.RestAdapter;
import retrofit.RestAdapter.Log;
import retrofit.RestAdapter.LogLevel;
import retrofit.client.Client;
import retrofit.client.Client.Provider;
import retrofit.client.Header;
import retrofit.client.OkClient;
import retrofit.client.Request;
import retrofit.client.Response;
import retrofit.converter.Converter;
import retrofit.mime.FormUrlEncodedTypedOutput;
import com.google.common.io.BaseEncoding;
import com.google.gson.Gson;
import com.google.gson.JsonObject;
/**
* A Builder class for a Retrofit REST Adapter. Extends the default implementation by providing logic to
* handle an OAuth 2.0 password grant login flow. The RestAdapter that it produces uses an interceptor
* to automatically obtain a bearer token from the authorization server and insert it into all client
* requests.
*
* You can use it like this:
*
private VideoSvcApi videoService = new SecuredRestBuilder()
.setLoginEndpoint(TEST_URL + VideoSvcApi.TOKEN_PATH)
.setUsername(USERNAME)
.setPassword(PASSWORD)
.setClientId(CLIENT_ID)
.setClient(new ApacheClient(UnsafeHttpsClient.createUnsafeClient()))
.setEndpoint(TEST_URL).setLogLevel(LogLevel.FULL).build()
.create(VideoSvcApi.class);
*
* @author Jules, Mitchell
*
*/
public class SecuredRestBuilder extends RestAdapter.Builder {
private class OAuthHandler implements RequestInterceptor {
private boolean loggedIn;
private Client client;
private String tokenIssuingEndpoint;
private String username;
private String password;
private String clientId;
private String clientSecret;
private String accessToken;
public OAuthHandler(Client client, String tokenIssuingEndpoint, String username,
String password, String clientId, String clientSecret) {
super();
this.client = client;
this.tokenIssuingEndpoint = tokenIssuingEndpoint;
this.username = username;
this.password = password;
this.clientId = clientId;
this.clientSecret = clientSecret;
}
/**
* Every time a method on the client interface is invoked, this method is
* going to get called. The method checks if the client has previously obtained
* an OAuth 2.0 bearer token. If not, the method obtains the bearer token by
* sending a password grant request to the server.
*
* Once this method has obtained a bearer token, all future invocations will
* automatically insert the bearer token as the "Authorization" header in
* outgoing HTTP requests.
*
*/
@Override
public void intercept(RequestFacade request) {
// If we're not logged in, login and store the authentication token.
if (!loggedIn) {
try {
// This code below programmatically builds an OAuth 2.0 password
// grant request and sends it to the server.
// Encode the username and password into the body of the request.
FormUrlEncodedTypedOutput to = new FormUrlEncodedTypedOutput();
to.addField("username", username);
to.addField("password", password);
// Add the client ID and client secret to the body of the request.
to.addField("client_id", clientId);
to.addField("client_secret", clientSecret);
// Indicate that we're using the OAuth Password Grant Flow
// by adding grant_type=password to the body
to.addField("grant_type", "password");
// The password grant requires BASIC authentication of the client.
// In order to do BASIC authentication, we need to concatenate the
// client_id and client_secret values together with a colon and then
// Base64 encode them. The final value is added to the request as
// the "Authorization" header and the value is set to "Basic "
// concatenated with the Base64 client_id:client_secret value described
// above.
String base64Auth = BaseEncoding.base64().encode(new String(clientId + ":" + clientSecret).getBytes());
// Add the basic authorization header
List<Header> headers = new ArrayList<Header>();
headers.add(new Header("Authorization", "Basic " + base64Auth));
// Create the actual password grant request using the data above
Request req = new Request("POST", tokenIssuingEndpoint, headers, to);
// Request the password grant.
Response resp = client.execute(req);
// Make sure the server responded with 200 OK
if (resp.getStatus() < 200 || resp.getStatus() > 299) {
// If not, we probably have bad credentials
throw new SecuredRestException("Login failure: "
+ resp.getStatus() + " - " + resp.getReason());
} else {
// Extract the string body from the response
String body = IOUtils.toString(resp.getBody().in());
// Extract the access_token (bearer token) from the response so that we
// can add it to future requests.
accessToken = new Gson().fromJson(body, JsonObject.class).get("access_token").getAsString();
// Add the access_token to this request as the "Authorization"
// header.
request.addHeader("Authorization", "Bearer " + accessToken);
// Let future calls know we've already fetched the access token
loggedIn = true;
}
} catch (Exception e) {
throw new SecuredRestException(e);
}
}
else {
// Add the access_token that we previously obtained to this request as
// the "Authorization" header.
request.addHeader("Authorization", "Bearer " + accessToken );
}
}
}
private String username;
private String password;
private String loginUrl;
private String clientId;
private String clientSecret = "";
private Client client;
public SecuredRestBuilder setLoginEndpoint(String endpoint){
loginUrl = endpoint;
return this;
}
@Override
public SecuredRestBuilder setEndpoint(String endpoint) {
return (SecuredRestBuilder) super.setEndpoint(endpoint);
}
@Override
public SecuredRestBuilder setEndpoint(Endpoint endpoint) {
return (SecuredRestBuilder) super.setEndpoint(endpoint);
}
@Override
public SecuredRestBuilder setClient(Client client) {
this.client = client;
return (SecuredRestBuilder) super.setClient(client);
}
@Override
public SecuredRestBuilder setClient(Provider clientProvider) {
client = clientProvider.get();
return (SecuredRestBuilder) super.setClient(clientProvider);
}
@Override
public SecuredRestBuilder setErrorHandler(ErrorHandler errorHandler) {
return (SecuredRestBuilder) super.setErrorHandler(errorHandler);
}
@Override
public SecuredRestBuilder setExecutors(Executor httpExecutor,
Executor callbackExecutor) {
return (SecuredRestBuilder) super.setExecutors(httpExecutor,
callbackExecutor);
}
@Override
public SecuredRestBuilder setRequestInterceptor(
RequestInterceptor requestInterceptor) {
return (SecuredRestBuilder) super
.setRequestInterceptor(requestInterceptor);
}
@Override
public SecuredRestBuilder setConverter(Converter converter) {
return (SecuredRestBuilder) super.setConverter(converter);
}
@Override
public SecuredRestBuilder setProfiler(@SuppressWarnings("rawtypes") Profiler profiler) {
return (SecuredRestBuilder) super.setProfiler(profiler);
}
@Override
public SecuredRestBuilder setLog(Log log) {
return (SecuredRestBuilder) super.setLog(log);
}
@Override
public SecuredRestBuilder setLogLevel(LogLevel logLevel) {
return (SecuredRestBuilder) super.setLogLevel(logLevel);
}
public SecuredRestBuilder setUsername(String username) {
this.username = username;
return this;
}
public SecuredRestBuilder setPassword(String password) {
this.password = password;
return this;
}
public SecuredRestBuilder setClientId(String clientId) {
this.clientId = clientId;
return this;
}
public SecuredRestBuilder setClientSecret(String clientSecret) {
this.clientSecret = clientSecret;
return this;
}
@Override
public RestAdapter build() {
if (username == null || password == null) {
throw new SecuredRestException(
"You must specify both a username and password for a "
+ "SecuredRestBuilder before calling the build() method.");
}
if (client == null) {
client = new OkClient();
}
OAuthHandler hdlr = new OAuthHandler(client, loginUrl, username, password, clientId, clientSecret);
setRequestInterceptor(hdlr);
return super.build();
}
}