package org.jboss.resteasy.skeleton.key.as7;
import org.apache.catalina.Lifecycle;
import org.apache.catalina.LifecycleEvent;
import org.apache.catalina.LifecycleException;
import org.apache.catalina.LifecycleListener;
import org.apache.catalina.authenticator.Constants;
import org.apache.catalina.authenticator.FormAuthenticator;
import org.apache.catalina.connector.Request;
import org.apache.catalina.connector.Response;
import org.apache.catalina.core.StandardContext;
import org.apache.catalina.deploy.LoginConfig;
import org.apache.catalina.realm.GenericPrincipal;
import org.bouncycastle.openssl.PEMWriter;
import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.map.ObjectWriter;
import org.codehaus.jackson.map.SerializationConfig;
import org.codehaus.jackson.map.annotate.JsonSerialize;
import org.jboss.logging.Logger;
import org.jboss.resteasy.client.jaxrs.ResteasyClient;
import org.jboss.resteasy.client.jaxrs.ResteasyClientBuilder;
import org.jboss.resteasy.jose.jws.JWSBuilder;
import org.jboss.resteasy.jose.jws.JWSInput;
import org.jboss.resteasy.jose.jws.crypto.RSAProvider;
import org.jboss.resteasy.jwt.JsonSerialization;
import org.jboss.resteasy.plugins.providers.RegisterBuiltin;
import org.jboss.resteasy.plugins.server.servlet.ServletUtil;
import org.jboss.resteasy.skeleton.key.EnvUtil;
import org.jboss.resteasy.skeleton.key.PemUtils;
import org.jboss.resteasy.skeleton.key.ResourceMetadata;
import org.jboss.resteasy.skeleton.key.SkeletonKeySession;
import org.jboss.resteasy.skeleton.key.config.AuthServerConfig;
import org.jboss.resteasy.skeleton.key.config.ManagedResourceConfig;
import org.jboss.resteasy.skeleton.key.representations.AccessTokenResponse;
import org.jboss.resteasy.skeleton.key.representations.SkeletonKeyToken;
import org.jboss.resteasy.skeleton.key.servlet.ServletActionURLs;
import org.jboss.resteasy.spi.ResteasyProviderFactory;
import org.jboss.resteasy.spi.ResteasyUriInfo;
import org.jboss.resteasy.util.BasicAuthHelper;
import javax.security.auth.login.LoginException;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.ws.rs.client.WebTarget;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.UriBuilder;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import java.security.KeyStore;
import java.security.Principal;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.cert.Certificate;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;
/**
* Turns a web deployment into an authentication server that follwos the OAuth 2 protocol and Skeleton Key bearer tokens.
* Authentication store is backed by a JBoss security domain.
* <p/>
* Servlet FORM authentication that uses the local security domain to authenticate and for role mappings.
* <p/>
* Supports bearer token creation and authentication. The client asking for access must be set up as a valid user
* within the security domain.
* <p/>
* If no an OAuth access request, this works like normal FORM authentication and authorization.
*
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class OAuthAuthenticationServerValve extends FormAuthenticator implements LifecycleListener
{
public static class AccessCode
{
protected String id = UUID.randomUUID().toString() + System.currentTimeMillis();
protected long expiration;
protected SkeletonKeyToken token;
protected String client;
protected boolean sso;
protected String redirect;
public boolean isExpired()
{
return expiration != 0 && (System.currentTimeMillis() / 1000) > expiration;
}
public String getId()
{
return id;
}
public long getExpiration()
{
return expiration;
}
public void setExpiration(long expiration)
{
this.expiration = expiration;
}
public SkeletonKeyToken getToken()
{
return token;
}
public void setToken(SkeletonKeyToken token)
{
this.token = token;
}
public String getClient()
{
return client;
}
public void setClient(String client)
{
this.client = client;
}
public boolean isSso()
{
return sso;
}
public void setSso(boolean sso)
{
this.sso = sso;
}
public String getRedirect()
{
return redirect;
}
public void setRedirect(String redirect)
{
this.redirect = redirect;
}
}
protected ConcurrentHashMap<String, AccessCode> accessCodeMap = new ConcurrentHashMap<String, AccessCode>();
private static final Logger log = Logger.getLogger(OAuthAuthenticationServerValve.class);
private static AtomicLong counter = new AtomicLong(1);
private static String generateId()
{
return counter.getAndIncrement() + "." + UUID.randomUUID().toString();
}
protected AuthServerConfig skeletonKeyConfig;
protected PrivateKey realmPrivateKey;
protected PublicKey realmPublicKey;
protected String realmPublicKeyPem;
protected ResteasyProviderFactory providers;
protected ResourceMetadata resourceMetadata;
protected UserSessionManagement userSessionManagement = new UserSessionManagement();
protected ObjectMapper mapper;
protected ObjectWriter accessTokenResponseWriter;
protected ObjectWriter mapWriter;
private static KeyStore loadKeyStore(String filename, String password) throws Exception
{
KeyStore trustStore = KeyStore.getInstance(KeyStore
.getDefaultType());
File truststoreFile = new File(filename);
FileInputStream trustStream = new FileInputStream(truststoreFile);
trustStore.load(trustStream, password.toCharArray());
trustStream.close();
return trustStore;
}
@Override
public void start() throws LifecycleException
{
super.start();
StandardContext standardContext = (StandardContext) context;
standardContext.addLifecycleListener(this);
}
@Override
public void lifecycleEvent(LifecycleEvent event)
{
if (event.getType() == Lifecycle.AFTER_START_EVENT) init();
}
protected void init()
{
mapper = new ObjectMapper();
mapper.setSerializationInclusion(JsonSerialize.Inclusion.NON_DEFAULT);
accessTokenResponseWriter = mapper.writerWithType(AccessTokenResponse.class);
mapWriter = mapper.writerWithType(mapper.getTypeFactory().constructMapType(Map.class, String.class, String.class));
InputStream is = null;
String path = context.getServletContext().getInitParameter("skeleton.key.config.file");
if (path == null)
{
is = context.getServletContext().getResourceAsStream("/WEB-INF/resteasy-oauth.json");
}
else
{
try
{
is = new FileInputStream(path);
}
catch (FileNotFoundException e)
{
throw new RuntimeException(e);
}
}
try
{
skeletonKeyConfig = mapper.readValue(is, AuthServerConfig.class);
}
catch (IOException e)
{
throw new RuntimeException(e);
}
if (skeletonKeyConfig.getLoginRole() == null)
{
throw new RuntimeException("You must define the login-role in your config file");
}
if (skeletonKeyConfig.getClientRole() == null)
{
throw new RuntimeException("You must define the oauth-client-role in your config file");
}
if (skeletonKeyConfig.getRealmPrivateKey() != null)
{
try
{
realmPrivateKey = PemUtils.decodePrivateKey(skeletonKeyConfig.getRealmPrivateKey());
}
catch (Exception e)
{
throw new RuntimeException(e);
}
}
if (skeletonKeyConfig.getRealmPublicKey() != null)
{
try
{
realmPublicKey = PemUtils.decodePublicKey(skeletonKeyConfig.getRealmPublicKey());
realmPublicKeyPem = skeletonKeyConfig.getRealmPublicKey();
}
catch (Exception e)
{
throw new RuntimeException(e);
}
}
if (skeletonKeyConfig.getRealmKeyStore() != null)
{
if (skeletonKeyConfig.getRealmKeyAlias() == null) throw new RuntimeException("Must define realm-key-alias");
String keystorePath = EnvUtil.replace(skeletonKeyConfig.getRealmKeyStore());
try
{
KeyStore ks = loadKeyStore(keystorePath, skeletonKeyConfig.getRealmKeystorePassword());
if (realmPrivateKey == null)
{
realmPrivateKey = (PrivateKey) ks.getKey(skeletonKeyConfig.getRealmKeyAlias(), skeletonKeyConfig.getRealmPrivateKeyPassword().toCharArray());
}
if (realmPublicKey == null)
{
Certificate cert = ks.getCertificate(skeletonKeyConfig.getRealmKeyAlias());
realmPublicKey = cert.getPublicKey();
}
}
catch (Exception e)
{
throw new RuntimeException(e);
}
}
if (realmPublicKey == null) throw new RuntimeException("You have not declared a keystore or public key");
if (realmPrivateKey == null) throw new RuntimeException("You have not declared a keystore or private key");
if (realmPublicKeyPem == null)
{
StringWriter sw = new StringWriter();
PEMWriter writer = new PEMWriter(sw);
try
{
writer.writeObject(realmPublicKey);
writer.flush();
}
catch (IOException e)
{
throw new RuntimeException(e);
}
realmPublicKeyPem = sw.toString();
realmPublicKeyPem = PemUtils.removeBeginEnd(realmPublicKeyPem);
}
providers = new ResteasyProviderFactory();
ClassLoader old = Thread.currentThread().getContextClassLoader();
Thread.currentThread().setContextClassLoader(OAuthAuthenticationServerValve.class.getClassLoader());
try
{
ResteasyProviderFactory.getInstance(); // initialize builtins
RegisterBuiltin.register(providers);
}
finally
{
Thread.currentThread().setContextClassLoader(old);
}
resourceMetadata = new ResourceMetadata();
resourceMetadata.setRealm(skeletonKeyConfig.getRealm());
resourceMetadata.setRealmKey(realmPublicKey);
String truststore = skeletonKeyConfig.getTruststore();
if (truststore != null)
{
truststore = EnvUtil.replace(truststore);
String truststorePassword = skeletonKeyConfig.getTruststorePassword();
KeyStore trust = null;
try
{
trust = loadKeyStore(truststore, truststorePassword);
}
catch (Exception e)
{
throw new RuntimeException("Failed to load truststore", e);
}
resourceMetadata.setTruststore(trust);
}
String clientKeystore = skeletonKeyConfig.getClientKeystore();
String clientKeyPassword = null;
if (clientKeystore != null)
{
clientKeystore = EnvUtil.replace(clientKeystore);
String clientKeystorePassword = skeletonKeyConfig.getClientKeystorePassword();
KeyStore serverKS = null;
try
{
serverKS = loadKeyStore(clientKeystore, clientKeystorePassword);
}
catch (Exception e)
{
throw new RuntimeException("Failed to load keystore", e);
}
resourceMetadata.setClientKeystore(serverKS);
clientKeyPassword = skeletonKeyConfig.getClientKeyPassword();
resourceMetadata.setClientKeyPassword(clientKeyPassword);
}
}
@Override
public void invoke(Request request, Response response) throws IOException, ServletException
{
try
{
String contextPath = request.getContextPath();
String requestURI = request.getDecodedRequestURI();
log.debug("--- invoke: " + requestURI);
if (request.getMethod().equalsIgnoreCase("GET")
&& context.getLoginConfig().getLoginPage().equals(request.getRequestPathMB().toString()))
{
if (handleLoginPage(request, response)) return;
}
else if (request.getMethod().equalsIgnoreCase("GET")
&& requestURI.endsWith(ServletActionURLs.J_OAUTH_LOGOUT))
{
logoutCurrentUser(request, response);
return;
}
else if (request.getMethod().equalsIgnoreCase("POST")
&& requestURI.endsWith(ServletActionURLs.J_OAUTH_ADMIN_FORCED_LOGOUT))
{
adminLogout(request, response);
return;
}
else if (request.getMethod().equalsIgnoreCase("POST")
&& requestURI.startsWith(contextPath) &&
requestURI.endsWith(Constants.FORM_ACTION)
&& request.getParameter("client_id") != null)
{
handleOAuth(request, response);
return;
}
else if (request.getMethod().equalsIgnoreCase("POST")
&& requestURI.endsWith(ServletActionURLs.J_OAUTH_TOKEN_GRANT))
{
tokenGrant(request, response);
return;
}
else if (request.getMethod().equalsIgnoreCase("POST")
&& requestURI.startsWith(contextPath) &&
requestURI.endsWith(ServletActionURLs.J_OAUTH_RESOLVE_ACCESS_CODE))
{
resolveAccessCode(request, response);
return;
}
else if (request.getMethod().equalsIgnoreCase("GET")
&& requestURI.startsWith(contextPath) &&
requestURI.endsWith("j_oauth_realm_info.html"))
{
publishRealmInfoHtml(request, response);
return;
}
// propagate the skeleton key token string?
if (!skeletonKeyConfig.isCancelPropagation())
{
if (request.getAttribute(SkeletonKeySession.class.getName()) == null && request.getSessionInternal() != null)
{
SkeletonKeySession skSession = (SkeletonKeySession) request.getSessionInternal().getNote(SkeletonKeySession.class.getName());
if (skSession != null)
{
request.setAttribute(SkeletonKeySession.class.getName(), skSession);
ResteasyProviderFactory.pushContext(SkeletonKeySession.class, skSession);
}
}
}
request.setAttribute("OAUTH_FORM_ACTION", "j_security_check");
super.invoke(request, response);
}
finally
{
ResteasyProviderFactory.clearContextData(); // to clear push of SkeletonKeySession
}
}
protected boolean handleLoginPage(Request request, Response response) throws IOException, ServletException
{
String client_id = request.getParameter("client_id");
// if this is not an OAUTH redirect, just return and let the default flow happen
if (client_id == null) return false;
String redirect_uri = request.getParameter("redirect_uri");
String state = request.getParameter("state");
if (redirect_uri == null)
{
response.sendError(400, "No oauth redirect query parameter set");
return true;
}
// only bypass authentication if our session is authenticated,
// the login query parameter is on request URL,
// and we have configured the login-role
else if (!skeletonKeyConfig.isSsoDisabled()
&& request.getSessionInternal() != null
&& request.getSessionInternal().getPrincipal() != null
&& request.getParameter("login") != null)
{
log.debug("We're ALREADY LOGGED IN!!!");
GenericPrincipal gp = (GenericPrincipal) request.getSessionInternal().getPrincipal();
redirectAccessCode(true, response, redirect_uri, client_id, state, gp);
}
else
{
UriBuilder builder = UriBuilder.fromUri("j_security_check")
.queryParam("redirect_uri", redirect_uri)
.queryParam("client_id", client_id);
if (state != null) builder.queryParam("state", state);
String loginAction = builder.build().toString();
request.setAttribute("OAUTH_FORM_ACTION", loginAction);
getNext().invoke(request, response);
}
return true;
}
protected GenericPrincipal checkLoggedIn(Request request, HttpServletResponse response)
{
if (request.getPrincipal() != null)
{
return (GenericPrincipal) request.getPrincipal();
}
else if (request.getSessionInternal() != null && request.getSessionInternal().getPrincipal() != null)
{
return (GenericPrincipal) request.getSessionInternal().getPrincipal();
}
return null;
}
protected void adminLogout(Request request, HttpServletResponse response) throws IOException
{
log.debug("<< adminLogout");
GenericPrincipal gp = checkLoggedIn(request, response);
if (gp == null)
{
if (bearer(request, response, false))
{
gp = (GenericPrincipal) request.getPrincipal();
}
else
{
response.sendError(403);
return;
}
}
if (!gp.hasRole(skeletonKeyConfig.getAdminRole()))
{
response.sendError(403);
return;
}
String logoutUser = request.getParameter("user");
if (logoutUser != null)
{
userSessionManagement.logout(logoutUser);
logoutResources(logoutUser, gp.getName());
}
else
{
userSessionManagement.logoutAllBut(gp.getName());
logoutResources(null, gp.getName());
}
String forwardTo = request.getParameter("forward");
if (forwardTo == null)
{
response.setStatus(204);
return;
}
RequestDispatcher disp =
context.getServletContext().getRequestDispatcher(forwardTo);
try
{
disp.forward(request.getRequest(), response);
}
catch (Throwable t)
{
request.setAttribute(RequestDispatcher.ERROR_EXCEPTION, t);
response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
"failed to forward");
}
}
protected void logoutCurrentUser(Request request, HttpServletResponse response) throws IOException
{
if (request.getSessionInternal() == null || request.getSessionInternal().getPrincipal() == null)
{
redirectToWelcomePage(request, response);
return;
}
GenericPrincipal principal = (GenericPrincipal) request.getSessionInternal().getPrincipal();
String username = principal.getName();
String admin = username;
userSessionManagement.logout(username);
request.setUserPrincipal(null);
request.setAuthType(null);
// logout user on all declared authenticated resources
logoutResources(username, admin);
redirectToWelcomePage(request, response);
}
protected void logoutResources(String username, String admin)
{
if (skeletonKeyConfig.getResources().size() != 0)
{
SkeletonKeyToken token = new SkeletonKeyToken();
token.id(generateId());
token.principal(admin);
token.audience(skeletonKeyConfig.getRealm());
SkeletonKeyToken.Access realmAccess = new SkeletonKeyToken.Access();
realmAccess.addRole(skeletonKeyConfig.getAdminRole());
token.setRealmAccess(realmAccess);
String tokenString = buildTokenString(realmPrivateKey, token);
ResteasyClient client = new ResteasyClientBuilder()
.providerFactory(providers)
.hostnameVerification(ResteasyClientBuilder.HostnameVerificationPolicy.ANY)
.trustStore(resourceMetadata.getTruststore())
.keyStore(resourceMetadata.getClientKeystore(), resourceMetadata.getClientKeyPassword())
.build();
try
{
for (String resource : skeletonKeyConfig.getResources())
{
try
{
log.debug("logging out: " + resource);
WebTarget target = client.target(resource).path(ServletActionURLs.J_OAUTH_REMOTE_LOGOUT);
if (username != null) target = target.queryParam("user", username);
javax.ws.rs.core.Response response = target.request()
.header("Authorization", "Bearer " + tokenString)
.put(null);
if (response.getStatus() != 204) log.error("Failed to log out");
response.close();
}
catch (Exception ignored)
{
log.error("Failed to log out", ignored);
}
}
}
finally
{
client.close();
}
}
}
protected void redirectToWelcomePage(Request request, HttpServletResponse response) throws IOException
{
ResteasyUriInfo uriInfo = ServletUtil.extractUriInfo(request, null);
String[] welcomes = context.findWelcomeFiles();
if (welcomes.length > 0)
{
UriBuilder welcome = uriInfo.getBaseUriBuilder().path(welcomes[0]);
response.sendRedirect(welcome.toTemplate());
}
else
{
response.setStatus(204);
}
}
protected void publishRealmInfoHtml(Request request, HttpServletResponse response) throws IOException
{
ManagedResourceConfig rep = getRealmRepresentation(request);
StringWriter writer;
String json;
ObjectMapper mapper = new ObjectMapper();
mapper.setSerializationInclusion(JsonSerialize.Inclusion.NON_DEFAULT);
mapper.enable(SerializationConfig.Feature.INDENT_OUTPUT);
StringBuffer html = new StringBuffer();
html.append("<html><body bgcolor=\"#CED8F6\">");
html.append("<h1>Realm: ").append(rep.getRealm()).append("</h1>");
ManagedResourceConfig bearer = new ManagedResourceConfig();
bearer.setRealm(rep.getRealm());
bearer.setRealmKey(rep.getRealmKey());
writer = new StringWriter();
mapper.writeValue(writer, bearer);
json = writer.toString();
html.append("<h3>BearerTokenAuthValve Json Config</h3>");
html.append("<form><textarea rows=\"7\" cols=\"80\">").append(json).append("</textarea></form>");
html.append("<br>");
writer = new StringWriter();
rep.getClientCredentials().put("password", "REQUIRED");
rep.setClientId("REQUIRED");
rep.setTruststore("REQUIRED");
rep.setTruststorePassword("REQUIRED");
mapper.writeValue(writer, rep);
json = writer.toString();
html.append("<h3>OAuthManagedResourceValve Json Config</h3>");
html.append("<form><textarea rows=\"20\" cols=\"80\">").append(json).append("</textarea></form>");
html.append("</body></html>");
response.setStatus(200);
response.setContentType("text/html");
response.getOutputStream().println(html.toString());
response.getOutputStream().flush();
}
protected ManagedResourceConfig getRealmRepresentation(Request request)
{
ManagedResourceConfig rep = new ManagedResourceConfig();
ResteasyUriInfo uriInfo = ServletUtil.extractUriInfo(request, null);
UriBuilder authUrl = uriInfo.getBaseUriBuilder().path(context.getLoginConfig().getLoginPage());
UriBuilder codeUrl = uriInfo.getBaseUriBuilder().path(ServletActionURLs.J_OAUTH_RESOLVE_ACCESS_CODE);
rep.setRealm(skeletonKeyConfig.getRealm());
rep.setRealmKey(realmPublicKeyPem);
rep.setAuthUrl(authUrl.toTemplate());
rep.setCodeUrl(codeUrl.toTemplate());
rep.setAdminRole(skeletonKeyConfig.getAdminRole());
return rep;
}
public boolean bearer(Request request, HttpServletResponse response, boolean propagate) throws IOException
{
if (request.getHeader("Authorization") != null)
{
CatalinaBearerTokenAuthenticator bearer = new CatalinaBearerTokenAuthenticator(resourceMetadata, true, false);
try
{
if (bearer.login(request, response))
{
return true;
}
}
catch (LoginException e)
{
}
}
return false;
}
@Override
protected void register(Request request, HttpServletResponse response, Principal principal, String authType, String username, String password)
{
super.register(request, response, principal, authType, username, password);
log.debug("authenticate userSessionManage.login(): " + principal.getName());
userSessionManagement.login(request.getSessionInternal(), principal.getName());
if (!skeletonKeyConfig.isCancelPropagation())
{
GenericPrincipal gp = (GenericPrincipal) request.getPrincipal();
if (gp != null)
{
SkeletonKeyToken token = buildToken(gp);
String stringToken = buildTokenString(realmPrivateKey, token);
SkeletonKeySession skSession = new SkeletonKeySession(stringToken, resourceMetadata);
request.setAttribute(SkeletonKeySession.class.getName(), skSession);
ResteasyProviderFactory.pushContext(SkeletonKeySession.class, skSession);
request.getSessionInternal(true).setNote(SkeletonKeySession.class.getName(), skSession);
}
}
}
@Override
public boolean authenticate(Request request, HttpServletResponse response, LoginConfig config) throws IOException
{
if (bearer(request, response, true))
{
return true;
}
return super.authenticate(request, response, config);
}
protected void resolveAccessCode(Request request, Response response) throws IOException
{
if (!request.isSecure())
{
response.sendError(400);
return;
}
// always verify code and remove access code from map before authenticating user
// if user authentication fails, we want the code to be removed irreguardless just in case we're under attack
String code = request.getParameter("code");
JWSInput input = new JWSInput(code, providers);
boolean verifiedCode = false;
try
{
verifiedCode = RSAProvider.verify(input, realmPublicKey);
}
catch (Exception ignored)
{
log.error("Failed to verify signature", ignored);
}
if (!verifiedCode)
{
Map<String, String> res = new HashMap<String, String>();
res.put("error", "invalid_grant");
res.put("error_description", "Unable to verify code signature");
response.sendError(400);
response.setContentType("application/json");
mapWriter.writeValue(response.getOutputStream(), res);
response.getOutputStream().flush();
return;
}
String key = input.readContent(String.class);
AccessCode accessCode = accessCodeMap.remove(key);
String redirect = request.getParameter("redirect_uri");
GenericPrincipal gp = basicAuth(request, response);
if (gp == null)
{
log.error("Failed to authenticate client_id");
return;
}
if (accessCode == null)
{
log.error("No access code: " + code);
response.sendError(400);
return;
}
if (accessCode.isExpired())
{
log.debug("Access code expired");
Map<String, String> res = new HashMap<String, String>();
res.put("error", "invalid_grant");
res.put("error_description", "Code is expired");
response.setStatus(400);
response.setContentType("application/json");
mapWriter.writeValue(response.getOutputStream(), res);
response.getOutputStream().flush();
return;
}
if (!accessCode.getToken().isActive())
{
log.debug("token not active");
Map<String, String> res = new HashMap<String, String>();
res.put("error", "invalid_grant");
res.put("error_description", "Token expired");
response.setStatus(400);
response.setContentType("application/json");
mapWriter.writeValue(response.getOutputStream(), res);
response.getOutputStream().flush();
return;
}
if (!gp.getName().equals(accessCode.getClient()))
{
log.debug("not equal client");
Map<String, String> res = new HashMap<String, String>();
res.put("error", "invalid_grant");
res.put("error_description", "Auth error");
response.setStatus(400);
response.setContentType("application/json");
mapWriter.writeValue(response.getOutputStream(), res);
response.getOutputStream().flush();
return;
}
if (!accessCode.getRedirect().equals(redirect))
{
log.debug("not equal redirect");
Map<String, String> res = new HashMap<String, String>();
res.put("error", "invalid_grant");
res.put("error_description", "Auth error");
response.setStatus(400);
response.setContentType("application/json");
mapWriter.writeValue(response.getOutputStream(), res);
response.getOutputStream().flush();
return;
}
if (accessCode.isSso() && !gp.hasRole(skeletonKeyConfig.getLoginRole()))
{
// we did not authenticate user on an access code request because a session was already established
// but, the client_id does not have permission to bypass this on a simple grant. We want
// to always ask for credentials from a simple oath request
log.debug("does not have login permission");
Map<String, String> res = new HashMap<String, String>();
res.put("error", "invalid_grant");
res.put("error_description", "Auth error");
response.setStatus(400);
response.setContentType("application/json");
mapWriter.writeValue(response.getOutputStream(), res);
response.getOutputStream().flush();
return;
}
else if (!gp.hasRole(skeletonKeyConfig.getClientRole()) && !gp.hasRole(skeletonKeyConfig.getLoginRole()))
{
log.debug("does not have login or client role permission for access token request");
Map<String, String> res = new HashMap<String, String>();
res.put("error", "invalid_grant");
res.put("error_description", "Auth error");
response.setStatus(400);
response.setContentType("application/json");
mapWriter.writeValue(response.getOutputStream(), res);
response.getOutputStream().flush();
return;
}
String wildcard = skeletonKeyConfig.getWildcardRole() == null ? "*" : skeletonKeyConfig.getWildcardRole();
Set<String> codeRoles = accessCode.getToken().getRealmAccess().getRoles();
if (codeRoles != null &&
(codeRoles.contains(skeletonKeyConfig.getClientRole()) || codeRoles.contains(skeletonKeyConfig.getLoginRole())))
{
// we store roles a oauth client is granted in the user role mapping, remove those roles as we don't want those clients with those
// permissions if they are logging in.
Set<String> newRoles = new HashSet<String>();
if (codeRoles.contains(skeletonKeyConfig.getClientRole())) newRoles.add(skeletonKeyConfig.getClientRole());
if (codeRoles.contains(skeletonKeyConfig.getLoginRole())) newRoles.add(skeletonKeyConfig.getLoginRole());
if (codeRoles.contains(wildcard)) newRoles.add(wildcard);
codeRoles.clear();
codeRoles.addAll(newRoles);
}
// is we have a login role, then we don't need to filter out roles, just grant all the roles the user has
// Also, if the client has the "wildcard" role, then we don't need to filter out roles
if (codeRoles != null
&& !gp.hasRole(wildcard)
&& !gp.hasRole(skeletonKeyConfig.getLoginRole()))
{
Set<String> clientAllowed = new HashSet<String>();
for (String role : gp.getRoles())
{
clientAllowed.add(role);
}
Set<String> newRoles = new HashSet<String>();
newRoles.addAll(codeRoles);
for (String role : newRoles)
{
if (!clientAllowed.contains(role))
{
codeRoles.remove(role);
}
}
}
AccessTokenResponse res = accessTokenResponse(realmPrivateKey, accessCode.getToken());
response.setStatus(200);
response.setContentType("application/json");
accessTokenResponseWriter.writeValue(response.getOutputStream(), res);
response.getOutputStream().flush();
}
protected AccessTokenResponse accessTokenResponse(PrivateKey privateKey, SkeletonKeyToken token)
{
String encodedToken = buildTokenString(privateKey, token);
AccessTokenResponse res = new AccessTokenResponse();
res.setToken(encodedToken);
res.setTokenType("bearer");
if (token.getExpiration() != 0)
{
long time = token.getExpiration() - (System.currentTimeMillis() / 1000);
res.setExpiresIn(time);
}
return res;
}
protected String buildTokenString(PrivateKey privateKey, SkeletonKeyToken token)
{
byte[] tokenBytes = null;
try
{
tokenBytes = JsonSerialization.toByteArray(token, false);
}
catch (Exception e)
{
throw new RuntimeException(e);
}
return new JWSBuilder()
.content(tokenBytes)
.rsa256(privateKey);
}
protected void handleOAuth(Request request, Response response) throws IOException
{
log.debug("<--- Begin oauthAuthenticate");
String redirect_uri = request.getParameter("redirect_uri");
String client_id = request.getParameter("client_id");
String state = request.getParameter("state");
String username = request.getParameter(Constants.FORM_USERNAME);
String password = request.getParameter(Constants.FORM_PASSWORD);
Principal principal = context.getRealm().authenticate(username, password);
if (principal == null)
{
UriBuilder builder = UriBuilder.fromUri(redirect_uri).queryParam("error", "unauthorized_client");
if (state != null) builder.queryParam("state", state);
response.sendRedirect(builder.toTemplate());
return;
}
GenericPrincipal gp = (GenericPrincipal) principal;
register(request, response, principal, HttpServletRequest.FORM_AUTH, username, password);
userSessionManagement.login(request.getSessionInternal(), username);
redirectAccessCode(false, response, redirect_uri, client_id, state, gp);
return;
}
protected void tokenGrant(Request request, Response response) throws IOException
{
if (!request.isSecure())
{
response.sendError(400);
return;
}
GenericPrincipal gp = basicAuth(request, response);
if (gp == null) return;
SkeletonKeyToken token = buildToken(gp);
AccessTokenResponse res = accessTokenResponse(realmPrivateKey, token);
response.setStatus(200);
response.setContentType("application/json");
accessTokenResponseWriter.writeValue(response.getOutputStream(), res);
response.getOutputStream().flush();
}
protected GenericPrincipal basicAuth(Request request, Response response) throws IOException
{
String authHeader = request.getHeader(HttpHeaders.AUTHORIZATION);
if (authHeader == null)
{
basicAuthError(response);
return null;
}
String[] creds = BasicAuthHelper.parseHeader(authHeader);
if (creds == null)
{
basicAuthError(response);
return null;
}
String username = creds[0];
String password = creds[1];
GenericPrincipal gp = (GenericPrincipal) context.getRealm().authenticate(username, password);
if (gp == null)
{
basicAuthError(response);
return null;
}
return gp;
}
protected void basicAuthError(Response response) throws IOException
{
response.setHeader(HttpHeaders.WWW_AUTHENTICATE, "Basic realm=\"" + context.getLoginConfig().getRealmName() + "\"");
response.sendError(401);
}
protected void redirectAccessCode(boolean sso, Response response, String redirect_uri, String client_id, String state, GenericPrincipal gp) throws IOException
{
SkeletonKeyToken token = buildToken(gp);
AccessCode code = new AccessCode();
code.setToken(token);
code.setClient(client_id);
code.setSso(sso);
code.setRedirect(redirect_uri);
int expiration = skeletonKeyConfig.getAccessCodeLifetime() == 0 ? 300 : skeletonKeyConfig.getAccessCodeLifetime();
code.setExpiration((System.currentTimeMillis() / 1000) + expiration);
accessCodeMap.put(code.getId(), code);
log.debug("--- sign access code");
String accessCode = null;
try
{
accessCode = new JWSBuilder().content(code.getId().getBytes("UTF-8")).rsa256(realmPrivateKey);
}
catch (UnsupportedEncodingException e)
{
throw new RuntimeException(e);
}
log.debug("--- build redirect");
UriBuilder redirectUri = UriBuilder.fromUri(redirect_uri).queryParam("code", accessCode);
if (state != null) redirectUri.queryParam("state", state);
response.sendRedirect(redirectUri.toTemplate());
log.debug("<--- end oauthAuthenticate");
}
protected SkeletonKeyToken buildToken(GenericPrincipal gp)
{
SkeletonKeyToken token = new SkeletonKeyToken();
token.id(generateId());
token.principal(gp.getName());
token.audience(skeletonKeyConfig.getRealm());
int expiration = skeletonKeyConfig.getAccessCodeLifetime() == 0 ? 3600 : skeletonKeyConfig.getAccessCodeLifetime();
if (skeletonKeyConfig.getTokenLifetime() > 0)
{
token.expiration((System.currentTimeMillis() / 1000) + expiration);
}
SkeletonKeyToken.Access realmAccess = new SkeletonKeyToken.Access();
for (String role : gp.getRoles())
{
realmAccess.addRole(role);
}
token.setRealmAccess(realmAccess);
return token;
}
}