package org.multibit.mbm.auth.hmac;
import com.google.common.base.Optional;
import com.sun.jersey.api.core.HttpContext;
import com.sun.jersey.server.impl.inject.AbstractHttpContextInjectable;
import com.sun.jersey.spi.container.ContainerRequest;
import com.yammer.dropwizard.auth.AuthenticationException;
import com.yammer.dropwizard.auth.Authenticator;
import org.multibit.mbm.auth.Authority;
import com.yammer.dropwizard.logging.Log;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
/**
* <p>Injectable to provide the following to {@link HmacServerRestrictedToProvider}:</p>
* <ul>
* <li>Performs decode from HTTP request</li>
* <li>Carries HMAC authentication data</li>
* </ul>
*
* @since 0.0.1
*/
class HmacServerRestrictedToInjectable<T> extends AbstractHttpContextInjectable<T> {
private static final Log LOG = Log.forClass(HmacServerRestrictedToInjectable.class);
private final Authenticator<HmacServerCredentials, T> authenticator;
private final String realm;
private final Authority[] requiredAuthorities;
/**
* @param authenticator The Authenticator that will compare credentials
* @param realm The authentication realm
* @param requiredAuthorities The required authorities as provided by the RestrictedTo annotation
*/
HmacServerRestrictedToInjectable(
Authenticator<HmacServerCredentials, T> authenticator,
String realm,
Authority[] requiredAuthorities) {
this.authenticator = authenticator;
this.realm = realm;
this.requiredAuthorities = requiredAuthorities;
}
public Authenticator<HmacServerCredentials, T> getAuthenticator() {
return authenticator;
}
public String getRealm() {
return realm;
}
public Authority[] getRequiredAuthorities() {
return requiredAuthorities;
}
@Override
public T getValue(HttpContext httpContext) {
try {
// Get the Authorization header
final String header = httpContext.getRequest().getHeaderValue(HttpHeaders.AUTHORIZATION);
if (header != null) {
// Expect form of "Authorization: <Algorithm> <ApiKey> <Signature>"
final String[] authTokens = header.split(" ");
if (authTokens.length != 3) {
// Malformed
HmacServerRestrictedToProvider.LOG.debug("Error decoding credentials (length is {})", authTokens.length);
throw new WebApplicationException(Response.Status.BAD_REQUEST);
}
final String apiKey = authTokens[1];
final String signature = authTokens[2];
final ContainerRequest containerRequest = (ContainerRequest) httpContext.getRequest();
// Build the canonical representation for the server side
final String canonicalRepresentation = HmacUtils.createCanonicalRepresentation(containerRequest);
LOG.debug("Server side canonical representation: '{}'",canonicalRepresentation);
final HmacServerCredentials credentials = new HmacServerCredentials("HmacSHA1", apiKey, signature, canonicalRepresentation, requiredAuthorities);
final Optional<T> result = authenticator.authenticate(credentials);
if (result.isPresent()) {
return result.get();
}
}
} catch (IllegalArgumentException e) {
HmacServerRestrictedToProvider.LOG.debug(e, "Error decoding credentials");
} catch (AuthenticationException e) {
HmacServerRestrictedToProvider.LOG.warn(e, "Error authenticating credentials");
throw new WebApplicationException(Response.Status.INTERNAL_SERVER_ERROR);
}
// Must have failed to be here
throw new WebApplicationException(Response.status(Response.Status.UNAUTHORIZED)
.header(HttpHeaders.AUTHORIZATION,
String.format(HmacUtils.HEADER_VALUE, realm))
.entity("Credentials are required to access this resource.")
.type(MediaType.TEXT_PLAIN_TYPE)
.build());
}
}