package io.robe.auth.token.jersey;
import com.google.common.hash.Hashing;
import io.dropwizard.auth.AuthenticationException;
import io.dropwizard.auth.Authenticator;
import io.robe.auth.Credentials;
import io.robe.auth.token.BasicToken;
import org.glassfish.jersey.server.internal.inject.AbstractContainerRequestValueFactory;
import org.glassfish.jersey.uri.UriTemplate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Cookie;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Optional;
public class TokenFactory<T extends BasicToken> extends AbstractContainerRequestValueFactory<Credentials> {
private static final Logger LOGGER = LoggerFactory.getLogger(TokenFactory.class);
public static Authenticator<String, BasicToken> authenticator;
public static String tokenKey;
private boolean required = true;
public TokenFactory(boolean required) {
this.required = required;
}
public TokenFactory() {
}
public static Credentials createEmptyCredentials() {
return new Credentials() {
@Override
public String getUserId() {
return null;
}
@Override
public String getUsername() {
return null;
}
@Override
public String getName() {
return null;
}
};
}
public boolean isRequired() {
return required;
}
public void setRequired(boolean required) {
this.required = required;
}
private WebApplicationException unauthorized() {
WebApplicationException ex = new WebApplicationException(
Response.status(Response.Status.UNAUTHORIZED).type(MediaType.APPLICATION_JSON).build());
try {
ex.getResponse().getHeaders().putSingle("Set-Cookie", TokenBasedAuthResponseFilter.getTokenSentence(null));
return ex;
} catch (Exception e) {
e.printStackTrace();
return new WebApplicationException(e);
}
}
@Override
public Credentials provide() {
Cookie tokenCookie = getContainerRequest().getCookies().get(tokenKey);
if (isRequired()) {
if (tokenCookie == null) {
throw unauthorized();
} else if (nullOrEmpty(tokenCookie.getValue())) {
throw unauthorized();
} else {
try {
if (!isRealOwnerOfToken(tokenCookie)) {
throw unauthorized();
}
Optional<BasicToken> result = authenticator.authenticate(tokenCookie.getValue());
if (!result.isPresent()) {
throw unauthorized();
} else if (!isAuthorized(result.get(), getContainerRequest().getUriInfo().getMatchedTemplates(), getContainerRequest().getMethod())) {
throw new WebApplicationException(Response.Status.FORBIDDEN);
} else {
return result.get();
}
} catch (io.dropwizard.auth.AuthenticationException e) {
LOGGER.error("Authentication Exception by Dropwizard", e);
throw unauthorized();
} catch (Exception e) {
LOGGER.error("Authentication Exception (Is Real ownwer of token) ", e);
throw unauthorized();
}
}
} else {
if (tokenCookie == null) {
return createEmptyCredentials();
} else {
try {
Optional<BasicToken> result = authenticator.authenticate(tokenCookie.getValue());
if (result.isPresent()) {
return result.get();
} else {
return createEmptyCredentials();
}
} catch (AuthenticationException e) {
// TODO ignore this error
return createEmptyCredentials();
}
}
}
}
private boolean isRealOwnerOfToken(Cookie tokenCookie) throws Exception {
LOGGER.debug("HttpContext : " + this.getContainerRequest().getPath(true) + " Cookie : " + tokenCookie);
BasicToken token = new BasicToken(tokenCookie.getValue());
String hash = generateAttributesHash();
return hash.equals(token.getAttributesHash());
}
private boolean nullOrEmpty(String token) {
return token == null || token.length() == 0;
}
/**
* Merges all path patterns and and creates a single string value which will be equal with service methods path
* annotation value and HTTP method type. Generated string will be used for permission checks.
*
* @param token for checking permission list
* @param matchedTemplates matched templates of context. They will be merged with reverse order
* @param method HTTP Method of the request. Will be merged with
* @return true if user is Authorized.
*/
private boolean isAuthorized(BasicToken token, List<UriTemplate> matchedTemplates, String method) {
StringBuilder path = new StringBuilder();
// Merge all path templates and generate a path.
for (UriTemplate template : matchedTemplates) {
path.insert(0, template.getTemplate());
}
path.append(":").append(method);
//Look at user permissions to see if the service is permitted.
return token.getPermissions().contains(path.toString());
}
public String generateAttributesHash() {
StringBuilder attr = new StringBuilder();
attr.append(this.getContainerRequest().getHeaderString("User-Agent"));
return Hashing.sha256().hashString(attr.toString(), StandardCharsets.UTF_8).toString();
}
}