Commit 08fe2fd8 by Tuomas Riihimäki

Refactor rest auth to bean level, so it can be used in multiple places

1 parent 4d7f6b40
package fi.codecrew.moya.beans.auth;
import javax.ejb.Local;
import javax.servlet.ServletException;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
@Local
public interface AuthHelperBeanLocal {
String parseHostname(HttpServletRequest httpRequest);
RestAuthResponse parseRestAuthCredentials(HttpServletRequest httpRequest) throws ServletException;
class RestAuthResponse {
public final String username;
public final String password;
public RestAuthResponse(String username, String password) {
this.username = username;
this.password = password;
}
}
}
package fi.codecrew.moya.beans.auth;
import fi.codecrew.moya.beans.ApiApplicationBeanLocal;
import fi.codecrew.moya.beans.JaasBeanLocal;
import fi.codecrew.moya.beans.LoggingBeanLocal;
import fi.codecrew.moya.utilities.UserLoginUtils;
import fi.codecrew.moya.utilities.moyamessage.MoyaEventType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.ejb.EJB;
import javax.ejb.LocalBean;
import javax.ejb.Stateless;
import javax.servlet.ServletException;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import java.nio.charset.StandardCharsets;
import java.security.Principal;
import java.util.Base64;
@LocalBean
@Stateless
public class AuthHelperBean implements AuthHelperBeanLocal {
private static final String X_FORWARDED_PROTO = "X-Forwarded-Proto";
private static final Logger logger = LoggerFactory.getLogger(AuthHelperBean.class);
@EJB
private LoggingBeanLocal logbean;
@EJB
private ApiApplicationBeanLocal apibean;
@Override
public String parseHostname(HttpServletRequest httpRequest) {
StringBuffer url = httpRequest.getRequestURL();
String requestHostHeader = httpRequest.getHeader("host");
String hostname = "localhost";
if (requestHostHeader != null) {
hostname = requestHostHeader.split(":")[0];
}
// if proxy provides scheme in header, use it..
String scheme = httpRequest.getHeader(X_FORWARDED_PROTO);
if (scheme == null || scheme.isEmpty()) {
scheme = url.substring(0, 5).toLowerCase();
}
Principal principal = httpRequest.getUserPrincipal();
if (principal != null) {
String userDomain = UserLoginUtils.getDomainFromJaas(principal);
// If there is no logged-in user, we can and should not check userDomain against hostname
if (principal != null && !hostname.equals(userDomain)) {
logbean.sendMessage(MoyaEventType.USER_PERMISSION_VIOLATION,
"Hostname mismatch privilege escalation! User '", httpRequest.getUserPrincipal(), "' tried to change hostname from '",
userDomain, "' to '", hostname, ",");
throw new RuntimeException("Hostname mismatch! Expected: " + hostname + " but logged in as " + userDomain);
}
}
return hostname;
}
/**
* Check if we should
*
* @param httpRequest
* @return Return wether rest failed. If it was not tried, function will return true, which
* will allow other authentication methods to be tried.
*/
public AuthHelperBeanLocal.RestAuthResponse parseRestAuthCredentials(HttpServletRequest httpRequest) throws ServletException {
// Allow api auth with password only if ssl is enabled ( or in
// development mode... )
// if (BortalLocalContextHolder.isSsl() ||
// BortalLocalContextHolder.isInDevelopmentMode()) {
String restAuthStr = httpRequest.getHeader("Authorization");
// }
String userkey = null;
String appkey = null;
// Payara got updated, and does not allow changing of username anymore, so we must send
// username and domain to initial jaas-login query...
if (restAuthStr != null) {
String decodedStr = new String(Base64.getDecoder().decode(restAuthStr.split(" ")[1]), StandardCharsets.UTF_8);
String[] splitStr = decodedStr.split(":");
appkey = splitStr[1];
userkey = splitStr[2];
// final String appKey = splitStr[3];
} else if (httpRequest.getParameter("appkey") != null) {
appkey = httpRequest.getParameter("appkey");
userkey = httpRequest.getParameter("appuser");
StringBuilder hashBuilder = new StringBuilder();
hashBuilder.append(JaasBeanLocal.REST_PREFIX);
hashBuilder.append(appkey).append(":");
hashBuilder.append(userkey).append(":");
hashBuilder.append(httpRequest.getParameter("appstamp")).append(":");
hashBuilder.append(httpRequest.getParameter("appmac")).append(":");
hashBuilder.append(httpRequest.getPathInfo());
restAuthStr = hashBuilder.toString();
} else {
// If no rest authentication is even tried, allow, anon login
return null;
}
String domain = parseHostname(httpRequest);
if (restAuthStr == null) {
throw new ServletException("No auth data");
}
//final String username = "@" + parseHostname(httpRequest);
String userLogin = apibean.findUsernameForApikey(appkey, userkey, domain);
if (userLogin == null) {
throw new ServletException("Unable to find username for credentials");
}
return new RestAuthResponse(userLogin + '@' + domain, restAuthStr);
}
}
......@@ -18,37 +18,28 @@
*/
package fi.codecrew.moya;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.security.Principal;
import java.util.Base64;
import fi.codecrew.moya.beans.*;
import fi.codecrew.moya.beans.auth.AuthHelperBeanLocal;
import fi.codecrew.moya.clientutils.BortalLocalContextHolder;
import fi.codecrew.moya.model.User;
import fi.codecrew.moya.rest.RestApplicationEntrypoint;
import fi.codecrew.moya.utilities.RestAuthHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
import javax.ejb.EJB;
import javax.faces.application.ProjectStage;
import javax.faces.context.FacesContext;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import fi.codecrew.moya.beans.*;
import fi.codecrew.moya.utilities.UserLoginUtils;
import org.apache.commons.codec.Charsets;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
import fi.codecrew.moya.clientutils.BortalLocalContextHolder;
import fi.codecrew.moya.model.User;
import fi.codecrew.moya.rest.RestApplicationEntrypoint;
import fi.codecrew.moya.utilities.moyamessage.MoyaEventType;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.security.Principal;
import java.util.Base64;
/**
* Servlet Filter implementation class HostnameFilter
......@@ -56,13 +47,15 @@ import fi.codecrew.moya.utilities.moyamessage.MoyaEventType;
@WebFilter(filterName = "hostnameFilter", displayName = "hostname and authentication filter", urlPatterns = {"/*"})
public class HostnameFilter implements Filter {
private static final String X_FORWARDED_PROTO = "X-Forwarded-Proto";
private static final String SESSION_HOSTNAMESTORE = "moya-session-hostname";
private static final Logger logger = LoggerFactory.getLogger(HostnameFilter.class);
private static final String HTTP_HOSTNAME_ID = "moya_hostname_session_id";
private boolean developmentMode = false;
@EJB
private AuthHelperBeanLocal authHelper;
@EJB
private RestBeanLocal restauth;
@EJB
private LoggingBeanLocal logbean;
......@@ -169,13 +162,14 @@ public class HostnameFilter implements Filter {
chain.doFilter(request, response);
return;
}
BortalLocalContextHolder.setInDevelopmentMode(developmentMode);
try {
httpRequest = (HttpServletRequest) request;
insertServerLoggingContext(httpRequest, authtype);
logger.info("Logging in with username {} and password {}, remote {}, authtype: {}", httpRequest.getUserPrincipal(), httpRequest.getRemoteUser(), httpRequest.getAuthType());
String hostname = parseHostname(httpRequest);
String hostname = authHelper.parseHostname(httpRequest);
if (httpRequest.getUserPrincipal() == null) {
// Check if we are can login in with rest alternative methods ( appkey, basic auth, etc.. )
......@@ -184,9 +178,9 @@ public class HostnameFilter implements Filter {
authtype = AuthType.REST;
if (!restAuth(httpRequest, response) && RestApplicationEntrypoint.REST_PATH.equals(httpRequest.getServletPath())) {
if (!tryRestAuth(httpRequest, response) && RestApplicationEntrypoint.REST_PATH.equals(httpRequest.getServletPath())) {
response.reset();
response.getOutputStream().write("Rest auth failed! ".getBytes( StandardCharsets.UTF_8));
response.getOutputStream().write("Rest auth failed! ".getBytes(StandardCharsets.UTF_8));
if (response instanceof HttpServletResponse) {
HttpServletResponse httpResp = (HttpServletResponse) response;
......@@ -231,19 +225,12 @@ public class HostnameFilter implements Filter {
}
}
// public static String getCurrentHostname(HttpSession sess) {
// String ret = null;
// if (sess != null) {
// Object retObj = sess.getAttribute(EventBeanLocal.HTTP_URL_HOSTNAME);
// if (retObj != null) {
// ret = retObj.toString();
// }
// }
// return ret;
// }
private boolean restAuth(HttpServletRequest httpRequest, ServletResponse response) {
/**
* @param httpRequest
* @param response
* @return Return false, if rest auth failed, and no other authenthication methods should be tried
*/
private boolean tryRestAuth(HttpServletRequest httpRequest, ServletResponse response) {
String sp = httpRequest.getPathInfo();
for (String s : NOAUTH_RESTPATHS) {
if (sp.startsWith(s)) {
......@@ -251,99 +238,29 @@ public class HostnameFilter implements Filter {
}
}
String restAuthStr = null;
// Allow api auth with password only if ssl is enabled ( or in
// development mode... )
// if (BortalLocalContextHolder.isSsl() ||
// BortalLocalContextHolder.isInDevelopmentMode()) {
restAuthStr = httpRequest.getHeader("Authorization");
// }
String userkey = null;
String appkey = null;
// Payara got updated, and does not allow changing of username anymore, so we must send
// username and domain to initial jaas-login query...
if (restAuthStr != null) {
String decodedStr = new String(Base64.getDecoder().decode(restAuthStr.split(" ")[1]), UTF8);
String[] splitStr = decodedStr.split(":");
appkey = splitStr[1];
userkey = splitStr[2];
// final String appKey = splitStr[3];
} else if (httpRequest.getParameter("appkey") != null) {
appkey = httpRequest.getParameter("appkey");
userkey = httpRequest.getParameter("appuser");
StringBuilder hashBuilder = new StringBuilder();
hashBuilder.append(JaasBeanLocal.REST_PREFIX);
hashBuilder.append(appkey).append(":");
hashBuilder.append(userkey).append(":");
hashBuilder.append(httpRequest.getParameter("appstamp")).append(":");
hashBuilder.append(httpRequest.getParameter("appmac")).append(":");
hashBuilder.append(httpRequest.getPathInfo());
restAuthStr = hashBuilder.toString();
} else {
// If no rest authentication is even tried, allow, anon login
try {
AuthHelperBeanLocal.RestAuthResponse creds = authHelper.parseRestAuthCredentials(httpRequest);
if (creds == null) {
return true;
}
boolean ret = true;
String domain = parseHostname(httpRequest);
try {
if (restAuthStr == null) {
throw new ServletException("No auth data");
}
//final String username = "@" + parseHostname(httpRequest);
String userLogin = apibean.findUsernameForApikey(appkey, userkey, domain);
if (userLogin != null) {
httpRequest.login(userLogin + '@' + domain, restAuthStr);
}
httpRequest.login(creds.username, creds.password);
Principal p = httpRequest.getUserPrincipal();
logger.warn("Logged in with rest:{}, ", (p == null) ? null : p.getName());
logger.warn("Logged in with rest: {}, ", (p == null) ? null : p.getName());
} catch (ServletException loginEx) {
ret = false;
logger.info("Rest api authentication failed for path " + httpRequest.getPathInfo() + " "
+ httpRequest.getParameterMap().toString(), loginEx);
if (response instanceof HttpServletResponse) {
HttpServletResponse httpResp = ((HttpServletResponse) response);
httpResp.setStatus(HttpServletResponse.SC_FORBIDDEN);
}
return false;
}
return ret;
}
protected String parseHostname(HttpServletRequest httpRequest) {
StringBuffer url = httpRequest.getRequestURL();
String requestHostHeader = httpRequest.getHeader("host");
String hostname = "localhost";
if (requestHostHeader != null) {
hostname = requestHostHeader.split(":")[0];
}
// if proxy provides scheme in header, use it..
String scheme = httpRequest.getHeader(X_FORWARDED_PROTO);
if (scheme == null || scheme.isEmpty()) {
scheme = url.substring(0, 5).toLowerCase();
return true;
}
Principal principal = httpRequest.getUserPrincipal();
if (principal != null) {
String userDomain = UserLoginUtils.getDomainFromJaas(principal);
// If there is no logged-in user, we can and should not check userDomain against hostname
if (principal != null && !hostname.equals(userDomain)) {
logbean.sendMessage(MoyaEventType.USER_PERMISSION_VIOLATION,
"Hostname mismatch privilege escalation! User '", httpRequest.getUserPrincipal(), "' tried to change hostname from '",
userDomain, "' to '", hostname, ",");
throw new RuntimeException("Hostname mismatch! Expected: " + hostname + " but logged in as " + userDomain);
}
}
BortalLocalContextHolder.setInDevelopmentMode(developmentMode);
return hostname;
}
}
......@@ -26,6 +26,7 @@ import javax.ejb.EJB;
import javax.enterprise.context.RequestScoped;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import javax.servlet.http.Part;
import javax.ws.rs.Consumes;
import javax.ws.rs.DefaultValue;
......@@ -43,6 +44,7 @@ import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.ResponseBuilder;
import javax.ws.rs.core.Response.Status;
import fi.codecrew.moya.beans.auth.AuthHelperBean;
import fi.codecrew.moya.model.*;
import io.swagger.v3.oas.annotations.OpenAPIDefinition;
import io.swagger.v3.oas.annotations.Operation;
......@@ -105,6 +107,8 @@ public class UserRestView {
@EJB
private PlaceBeanLocal placebean;
@EJB
private AuthHelperBean authHelperBean;
@POST
@Path("/giveplace/{placeId}")
......@@ -222,12 +226,11 @@ public class UserRestView {
}
}
if (principal == null || principal.getName() == null || !principal.getName().equals(username)) {
String requestHostHeader = servletRequest.getHeader("host");
String hostname = null;
if (requestHostHeader != null) {
hostname = requestHostHeader.split(":")[0];
}
servletRequest.getSession(true);
String hostname = authHelperBean.parseHostname(servletRequest);
HttpSession session = servletRequest.getSession(true);
logger.warn("Session Id {}", session.getId());
servletRequest.login(username + '@' + hostname, password);
}
} catch (ServletException e) {
......
......@@ -73,7 +73,7 @@ public class ApiAppRestViewV1 {
return Response.ok(ret).build();
} catch (ServletException e) {
logger.warn("Error logging in while creating ApiApplication instance");
logger.warn("Error logging in while creating ApiApplication instance",e);
return Response.serverError().entity(e.getCause()).build();
}
}
......
......@@ -2,6 +2,7 @@ package fi.codecrew.moya.rest.appconfig.v1;
import fi.codecrew.moya.beans.EventBeanLocal;
import fi.codecrew.moya.beans.PermissionBeanLocal;
import fi.codecrew.moya.beans.auth.AuthHelperBeanLocal;
import fi.codecrew.moya.rest.PojoUtils;
import fi.codecrew.moya.rest.pojo.appconfig.v1.EventRoot;
import io.swagger.v3.oas.annotations.OpenAPIDefinition;
......@@ -52,6 +53,9 @@ public class EventInfoV1 {
@EJB
private EventBeanLocal eventBean;
@EJB
private AuthHelperBeanLocal authHelperBean;
@GET
@Path("/current")
public Response getCurrentEventInfo() {
......@@ -63,6 +67,17 @@ public class EventInfoV1 {
public Response getEventsForUser() {
try {
if (!permissionBean.isLoggedIn()) {
AuthHelperBeanLocal.RestAuthResponse authCredentials = authHelperBean.parseRestAuthCredentials(servletRequest);
String password;
String username;
if (authCredentials != null) {
username = authCredentials.username;
password = authCredentials.password;
} else {
String authHeader = servletRequest.getHeader(AUTH_HEADER);
if (authHeader == null || !authHeader.startsWith(AUTH_PREFIX)) {
......@@ -76,21 +91,28 @@ public class EventInfoV1 {
if (principal != null && principal.getName() != null) {
servletRequest.logout();
}
String domain = servletRequest.getHeader("host");
String domain = authHelperBean.parseHostname(servletRequest);
username = splitAuth[0] + "@" + domain;
password = splitAuth[1];
}
servletRequest.getSession(true);
servletRequest.login(splitAuth[0] + "@" + domain, splitAuth[1]);
servletRequest.login(username, password);
}
return Response.ok(PojoUtils.parseEvents(eventBean.findAllEventsForCurrentUser())).build();
} catch (ServletException e) {
logger.warn("Error logging in while creating ApiApplication instance");
logger.warn("Error logging in while fetching events", e);
return Response.serverError().entity(e.getCause()).build();
}
}
@GET
@Path("/listevents/")
@Operation(description = "Get all events for current user", responses = { @ApiResponse(responseCode = "200", content = @Content(schema = @Schema(implementation = EventRoot.class))) })
@Operation(description = "Get all events for current user", responses = {@ApiResponse(responseCode = "200", content = @Content(schema = @Schema(implementation = EventRoot.class)))})
public Response getEventsForCurrentUser() {
if (permissionBean.getCurrentUser().isAnonymous()) {
......
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!