Commit 49056db1 by Tuomas Riihimäki

Add possibility to login with static basic auth credentials for rest apis.

username should be in format 'appauth:{appkey}:{userKey}' and password should be apiInstances apikey
1 parent bf394773
package fi.codecrew.moya.beans;
public interface JaasBeanLocal {
String REST_PREFIX = "rest:";
}
......@@ -18,8 +18,14 @@
*/
package fi.codecrew.moya.beans;
import java.util.*;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.Vector;
import javax.annotation.PostConstruct;
import javax.ejb.EJB;
import javax.ejb.Stateless;
......@@ -28,41 +34,29 @@ import org.slf4j.LoggerFactory;
import fi.codecrew.moya.AuthenticationResult;
import fi.codecrew.moya.MoyaRealmBeanRemote;
import fi.codecrew.moya.beans.auth.AuthenticationFormat;
import fi.codecrew.moya.beans.auth.BasicAuthPBean;
import fi.codecrew.moya.beans.auth.NormalAuthPBean;
import fi.codecrew.moya.beans.auth.RestMacAuthPBean;
import fi.codecrew.moya.enums.BortalApplication;
import fi.codecrew.moya.enums.apps.IAppPermission;
import fi.codecrew.moya.enums.apps.SpecialPermission;
import fi.codecrew.moya.enums.apps.UserPermission;
import fi.codecrew.moya.facade.ApiApplicationFacade;
import fi.codecrew.moya.facade.ApiApplicationInstanceFacade;
import fi.codecrew.moya.facade.EventUserFacade;
import fi.codecrew.moya.facade.UserFacade;
import fi.codecrew.moya.model.ApiApplication;
import fi.codecrew.moya.model.ApiApplicationInstance;
import fi.codecrew.moya.model.ApplicationPermission;
import fi.codecrew.moya.model.EventUser;
import fi.codecrew.moya.model.LanEvent;
import fi.codecrew.moya.model.LanEventProperty;
import fi.codecrew.moya.model.LanEventPropertyKey;
import fi.codecrew.moya.model.Role;
import fi.codecrew.moya.model.User;
import fi.codecrew.moya.utilities.PasswordFunctions;
import fi.codecrew.moya.utilities.moyamessage.MoyaEventType;
/**
* Session Bean implementation class SessionHandlerBean
*/
@Stateless(mappedName = MoyaRealmBeanRemote.REMOTE_BEAN_NAME)
public class JaasBean implements MoyaRealmBeanRemote {
public class JaasBean implements MoyaRealmBeanRemote, JaasBeanLocal {
private static final Logger logger = LoggerFactory.getLogger(JaasBean.class);
@EJB
private UserFacade userfacade;
@EJB
private EventUserFacade eventUserFacade;
@EJB
private UserBean userbean;
@EJB
private PermissionBeanLocal permbean;
......@@ -71,69 +65,11 @@ public class JaasBean implements MoyaRealmBeanRemote {
@EJB
private RestBean restbean;
@EJB
private ApiApplicationFacade appfacade;
@EJB
private ApiApplicationInstanceFacade appInstanceFacade;
@EJB
private EventBean eventorgbean;
@EJB
private LoggingBeanLocal secubean;
public EventUser tryLogin(String username, String password) {
// username = username.trim().toLowerCase();
EventUser eventUser = eventUserFacade.findByLogin(username);
logger.info("Found eventuser '{}' with username '{}'", eventUser, username);
User user = null;
// Might not have EventUser
if (eventUser == null) {
user = userfacade.findByLogin(username);
} else {
user = eventUser.getUser();
}
logger.info("User '{}' with '{}' ", user, username);
// If there is no eventuser found, try to create one.
if (user != null) {
logger.info("TryLogin user not null: {}, login {}", user, user.getLogin());
if (user.isAnonymous()) {
logger.info("logging in as anonymous!!!");
} else if (!user.checkPassword(password)) {
secubean.sendMessage(MoyaEventType.LOGIN_FAILED, eventUser, "Login failed: wrong password for username: ", username);
eventUser = null;
user = null;
}
LanEventProperty inviteonly = eventbean.getProperty(LanEventPropertyKey.INVITE_ONLY_EVENT);
boolean createEventuser = true;
if (inviteonly != null && inviteonly.isBooleanValue()) {
createEventuser = false;
}
if (createEventuser && user != null && eventUser == null)
{
LanEvent event = eventbean.getCurrentEvent();
eventUser = new EventUser(user, event, null);
// eventUser.setCreator(eventUser);
eventUserFacade.create(eventUser);
eventUserFacade.flush();
eventUser.setCreator(eventUser);
}
// jos logitetaan anomuumi, niin uuden tapahtuman luominen hajoaa jännästi.
if (user != null && !user.isAnonymous())
secubean.sendMessage(MoyaEventType.LOGIN_SUCCESSFULL, eventUser, "User logged in with username: '", username, "' eventuser: ", eventUser);
} else {
secubean.sendMessage(MoyaEventType.LOGIN_FAILED, eventUserFacade.findByLogin(User.ANONYMOUS_LOGINNAME), "Login failed: Username not found: ", username);
}
return eventUser;
}
private EventUserFacade eventUserFacade;
// public static void foo()
// {
......@@ -147,44 +83,44 @@ public class JaasBean implements MoyaRealmBeanRemote {
// }
// }
public static enum UserType
{
public static enum UserType {
USER, REST
}
@Override
public AuthenticationResult authUsername(String username, String password) {
@EJB
private BasicAuthPBean restauthBean;
@EJB
private RestMacAuthPBean restMacAuthBean;
@EJB
private NormalAuthPBean normalAuthBean;
AuthenticationResult ret = new AuthenticationResult();
ret.setUsertype(UserType.USER.name());
@PostConstruct
public void initAuthformats() {
authFormats = Arrays.asList(restauthBean, restMacAuthBean, normalAuthBean);
}
if ((username == null || username.isEmpty()) && password.startsWith("rest:")) {
ret.setUsertype(UserType.REST.name());
ret.setUsername(restAuth(password));
private List<AuthenticationFormat> authFormats;
} else {
EventUser retUser = tryLogin(username, password);
if (retUser != null) {
ret.setUsername(retUser.getLogin());
@Override
public AuthenticationResult authUsername(String username, String password) {
AuthenticationResult ret = null;
for (AuthenticationFormat auth : authFormats) {
ret = auth.authenticate(username, password);
if (ret != null) {
break;
}
}
return ret;
}
@Override
public boolean authenticate(String username, String password) {
return (tryLogin(username, password) != null);
return (normalAuthBean.tryLogin(username, password) != null);
}
private String restAuth(String restauth) {
String[] authsplit = restauth.split(":", 6);
logger.info("Trying to auth with rest {}", (Object) authsplit);
if (authsplit.length != 6 || !authsplit[0].equals("rest")) {
return null;
}
return authenticateApp(authsplit[5], authsplit[1], authsplit[2], authsplit[3], authsplit[4]);
}
@Override
public Enumeration<String> getGroupNames(String user, String usertype) {
......@@ -200,8 +136,7 @@ public class JaasBean implements MoyaRealmBeanRemote {
if (usertype != null) {
try {
switch (UserType.valueOf(usertype))
{
switch (UserType.valueOf(usertype)) {
case REST:
roleset.add(SpecialPermission.REST.name());
break;
......@@ -258,52 +193,5 @@ public class JaasBean implements MoyaRealmBeanRemote {
return getGroupNames(username, null);
}
public String authenticateApp(String pathInfo, String appId, String userId, String appStamp, String mac) {
logger.info("Authenticat app with pathinfo {}, appid {}, userid {}, appstamp {}, mac {}",
new Object[] { pathInfo, appId, userId, appStamp, mac }
);
if (mac == null) {
logger.warn("Rest auth failed: Mac is null");
return null;
}
ApiApplication app = appfacade.findByAppid(appId);
if (app == null) {
logger.warn("Rest auth failed: Application not found for appid {}", appId);
return null;
}
ApiApplicationInstance apiInstance = appInstanceFacade.findInstance(app, userId);
if (apiInstance == null) {
logger.warn("Rest auth failed; because appInstance not found for app{} and user {}", app, userId);
return null;
}
if (!app.isEnabled() || !apiInstance.isEnabled()) {
logger.warn("Rest auth failed: app or api-instance is disabled: app {}, apiInstance: {}", app, apiInstance);
return null;
}
String ret = null;
String macSource = PasswordFunctions.mkSeparatedString("+", pathInfo, appId, userId, appStamp, apiInstance.getSecretKey());
String macHash = PasswordFunctions.calculateSha1(macSource);
logger.info("Calculated hash {}, comparing to {}", macHash, mac);
if (mac.equalsIgnoreCase(macHash))
{
switch (app.getAuthtype()) {
case ORGAUTH:
ret = User.ANONYMOUS_LOGINNAME;
break;
case USERKEY:
if (apiInstance.getEventuser() != null) {
ret = apiInstance.getEventuser().getUser().getLogin();
}
break;
default:
throw new RuntimeException("Unknown application authtype!");
}
} else {
logger.warn("Rest auth failed: Calculated hash does not match received mac: Calculated {}, received {}", macHash, mac);
}
return ret;
}
}
package fi.codecrew.moya.beans.auth;
import javax.ejb.EJB;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import fi.codecrew.moya.facade.ApiApplicationFacade;
import fi.codecrew.moya.facade.ApiApplicationInstanceFacade;
import fi.codecrew.moya.model.ApiApplication;
import fi.codecrew.moya.model.ApiApplicationInstance;
import fi.codecrew.moya.model.User;
public class ApiAuth {
@EJB
private ApiApplicationFacade appfacade;
@EJB
private ApiApplicationInstanceFacade appInstanceFacade;
private static final Logger logger = LoggerFactory.getLogger(ApiAuth.class);
protected ApiApplicationInstance verifyAppInstance(String appId, String userId) {
ApiApplication app = appfacade.findByAppid(appId);
if (app == null) {
logger.warn("Rest auth failed: Application not found for appid {}", appId);
return null;
}
ApiApplicationInstance apiInstance = appInstanceFacade.findInstance(app, userId);
if (apiInstance == null) {
logger.warn("Rest auth failed; because appInstance not found for app{} and user {}", app, userId);
return null;
}
if (!app.isEnabled() || !apiInstance.isEnabled()) {
logger.warn("Rest auth failed: app or api-instance is disabled: app {}, apiInstance: {}", app, apiInstance);
return null;
}
return apiInstance;
}
protected String getUsername(ApiApplicationInstance apiInstance) {
String ret = null;
switch (apiInstance.getApplication().getAuthtype()) {
case ORGAUTH:
ret = User.ANONYMOUS_LOGINNAME;
break;
case USERKEY:
if (apiInstance.getEventuser() != null) {
ret = apiInstance.getEventuser().getUser().getLogin();
}
break;
default:
logger.warn("Unknown application authtype!");
throw new RuntimeException("Unknown application authtype!");
}
return ret;
}
}
package fi.codecrew.moya.beans.auth;
import fi.codecrew.moya.AuthenticationResult;
public interface AuthenticationFormat {
AuthenticationResult authenticate(String username, String password);
}
package fi.codecrew.moya.beans.auth;
import java.nio.charset.Charset;
import java.util.Base64;
import javax.ejb.EJB;
import javax.ejb.LocalBean;
import javax.ejb.Stateless;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import fi.codecrew.moya.AuthenticationResult;
import fi.codecrew.moya.beans.JaasBean.UserType;
import fi.codecrew.moya.facade.ApiApplicationFacade;
import fi.codecrew.moya.facade.ApiApplicationInstanceFacade;
import fi.codecrew.moya.model.ApiApplicationInstance;
@LocalBean
@Stateless
public class BasicAuthPBean extends ApiAuth implements AuthenticationFormat {
private static final String HEADER_PREFIX = "Basic ";
private static final String PASSWORD_DELIMITER = ":";
private static final String PASSWORD_PREFIX = "appauth";
private static final Logger logger = LoggerFactory.getLogger(BasicAuthPBean.class);
private static final Charset UTF8 = Charset.forName("UTF-8");
@EJB
private ApiApplicationFacade appfacade;
@EJB
private ApiApplicationInstanceFacade appInstanceFacade;
@Override
public AuthenticationResult authenticate(String username, String password) {
AuthenticationResult ret = null;
if ((username == null || username.isEmpty()) && password.startsWith(HEADER_PREFIX)) {
ret = new AuthenticationResult();
ret.setUsertype(UserType.REST.name());
try {
String[] pwdsplit = password.split(" ");
if (pwdsplit.length != 2) {
logger.warn("Rest auth with Basic failed because pwdsplit != 2: user '{}', password '{}'", username,
password);
return null;
}
String authStr = new String(Base64.getDecoder().decode(pwdsplit[1]), UTF8);
String[] splitStr = authStr.split(PASSWORD_DELIMITER);
if (splitStr.length != 4 && splitStr[0] != PASSWORD_PREFIX) {
logger.warn(
"Invalid Basic authentication string '{}'. Authstring must start with {}, followed by appid and app pass",
authStr, PASSWORD_PREFIX);
return null;
}
final String appId = splitStr[1];
final String userId = splitStr[2];
final String appKey = splitStr[3];
ApiApplicationInstance appInstance = verifyAppInstance(appId, userId);
if (appKey != null && !appKey.isEmpty() && appKey.equals(appInstance.getSecretKey())) {
ret.setUsername(getUsername(appInstance));
}
} catch (Exception e) {
logger.warn("Invalid base64 string on Rest Basic auth: " + password, e);
}
}
return ret;
}
}
package fi.codecrew.moya.beans.auth;
import javax.ejb.EJB;
import javax.ejb.LocalBean;
import javax.ejb.Stateless;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import fi.codecrew.moya.AuthenticationResult;
import fi.codecrew.moya.beans.EventBeanLocal;
import fi.codecrew.moya.beans.JaasBean.UserType;
import fi.codecrew.moya.beans.LoggingBeanLocal;
import fi.codecrew.moya.facade.EventUserFacade;
import fi.codecrew.moya.facade.UserFacade;
import fi.codecrew.moya.model.EventUser;
import fi.codecrew.moya.model.LanEvent;
import fi.codecrew.moya.model.LanEventProperty;
import fi.codecrew.moya.model.LanEventPropertyKey;
import fi.codecrew.moya.model.User;
import fi.codecrew.moya.utilities.moyamessage.MoyaEventType;
@LocalBean
@Stateless
public class NormalAuthPBean implements AuthenticationFormat {
private static final Logger logger = LoggerFactory.getLogger(NormalAuthPBean.class);
@EJB
private LoggingBeanLocal secubean;
@EJB
private EventUserFacade eventUserFacade;
@EJB
private UserFacade userfacade;
@EJB
private EventBeanLocal eventbean;
@Override
public AuthenticationResult authenticate(String username, String password) {
AuthenticationResult ret = null;
EventUser retUser = tryLogin(username, password);
if (retUser != null) {
ret = new AuthenticationResult();
ret.setUsertype(UserType.USER.name());
ret.setUsername(retUser.getLogin());
}
return ret;
}
public EventUser tryLogin(String username, String password) {
// username = username.trim().toLowerCase();
EventUser eventUser = eventUserFacade.findByLogin(username);
// logger.info("Found eventuser '{}' with username '{}'", eventUser,
// username);
User user = null;
// Might not have EventUser
if (eventUser == null) {
user = userfacade.findByLogin(username);
} else {
user = eventUser.getUser();
}
logger.info("User '{}' with '{}' ", user, username);
// If there is no eventuser found, try to create one.
if (user != null) {
logger.info("TryLogin user not null: {}, login {}", user, user.getLogin());
if (user.isAnonymous()) {
logger.info("logging in as anonymous!!!");
} else if (!user.checkPassword(password)) {
secubean.sendMessage(MoyaEventType.LOGIN_FAILED, eventUser,
"Login failed: wrong password for username: ", username);
eventUser = null;
user = null;
}
LanEventProperty inviteonly = eventbean.getProperty(LanEventPropertyKey.INVITE_ONLY_EVENT);
boolean createEventuser = true;
if (inviteonly != null && inviteonly.isBooleanValue()) {
createEventuser = false;
}
if (createEventuser && user != null && eventUser == null) {
LanEvent event = eventbean.getCurrentEvent();
eventUser = new EventUser(user, event, null);
// eventUser.setCreator(eventUser);
eventUserFacade.create(eventUser);
eventUserFacade.flush();
eventUser.setCreator(eventUser);
}
// jos logitetaan anomuumi, niin uuden tapahtuman luominen hajoaa
// jännästi.
if (user != null && !user.isAnonymous())
secubean.sendMessage(MoyaEventType.LOGIN_SUCCESSFULL, eventUser, "User logged in with username: '",
username, "' eventuser: ", eventUser);
} else {
secubean.sendMessage(MoyaEventType.LOGIN_FAILED, eventUserFacade.findByLogin(User.ANONYMOUS_LOGINNAME),
"Login failed: Username not found: ", username);
}
return eventUser;
}
}
package fi.codecrew.moya.beans.auth;
import javax.ejb.LocalBean;
import javax.ejb.Stateless;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import fi.codecrew.moya.AuthenticationResult;
import fi.codecrew.moya.beans.JaasBean.UserType;
import fi.codecrew.moya.beans.JaasBeanLocal;
import fi.codecrew.moya.model.ApiApplicationInstance;
import fi.codecrew.moya.utilities.PasswordFunctions;
@LocalBean
@Stateless
public class RestMacAuthPBean extends ApiAuth implements AuthenticationFormat {
private static final Logger logger = LoggerFactory.getLogger(RestMacAuthPBean.class);
@Override
public AuthenticationResult authenticate(String username, String password) {
AuthenticationResult ret = null;
if ((username == null || username.isEmpty()) && password.startsWith(JaasBeanLocal.REST_PREFIX)) {
ret = new AuthenticationResult();
ret.setUsertype(UserType.REST.name());
ret.setUsername(restAuth(password));
}
return ret;
}
private String restAuth(String restauth) {
String[] authsplit = restauth.split(":", 6);
logger.info("Trying to auth with rest {}", (Object) authsplit);
if (authsplit.length != 6 || !authsplit[0].equals("rest")) {
return null;
}
return authenticateApp(authsplit[5], authsplit[1], authsplit[2], authsplit[3], authsplit[4]);
}
public String authenticateApp(String pathInfo, String appId, String userId, String appStamp, String mac) {
logger.info("Authenticat app with pathinfo {}, appid {}, userid {}, appstamp {}, mac {}",
new Object[] { pathInfo, appId, userId, appStamp, mac });
if (mac == null) {
logger.warn("Rest auth failed: Mac is null");
return null;
}
ApiApplicationInstance apiInstance = verifyAppInstance(appId, userId);
if (apiInstance == null)
return null;
String ret = null;
String macSource = PasswordFunctions.mkSeparatedString("+", pathInfo, appId, userId, appStamp,
apiInstance.getSecretKey());
String macHash = PasswordFunctions.calculateSha1(macSource);
logger.info("Calculated hash {}, comparing to {}", macHash, mac);
if (mac.equalsIgnoreCase(macHash)) {
ret = getUsername(apiInstance);
} else {
logger.warn("Rest auth failed: Calculated hash does not match received mac: Calculated {}, received {}",
macHash, mac);
}
return ret;
}
}
......@@ -40,6 +40,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
import fi.codecrew.moya.beans.JaasBeanLocal;
import fi.codecrew.moya.beans.LoggingBeanLocal;
import fi.codecrew.moya.beans.RestBeanLocal;
import fi.codecrew.moya.beans.SessionMgmtBeanLocal;
......@@ -109,7 +110,7 @@ public class HostnameFilter implements Filter {
void insertLoggingContext(HttpServletRequest request, AuthType authType) {
if (request == null)
return;
Principal userPrincipal = request.getUserPrincipal();
if (userPrincipal != null) {
String userString = userPrincipal.getName();
......@@ -143,14 +144,13 @@ public class HostnameFilter implements Filter {
* @see Filter#doFilter(ServletRequest, ServletResponse, FilterChain)
*/
private static final String[] NOAUTH_RESTPATHS = new String[] {
"/reader/EventRole/", "/user/auth"
private static final String[] NOAUTH_RESTPATHS = new String[] { "/reader/EventRole/", "/user/auth"
};
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
// logger.info("HostnameFilter called!");
HttpServletRequest httpRequest = null;
AuthType authtype = AuthType.UNKNOWN;
......@@ -172,6 +172,7 @@ public class HostnameFilter implements Filter {
HttpServletResponse httpResp = (HttpServletResponse) response;
httpResp.setStatus(HttpServletResponse.SC_FORBIDDEN);
}
// Rest auth failed. Go away!
return;
}
} else {
......@@ -184,7 +185,8 @@ public class HostnameFilter implements Filter {
}
} else if (!httpRequest.getUserPrincipal().getName().equals(User.ANONYMOUS_LOGINNAME)) {
authtype = AuthType.USER;
sessionmgmt.updateSessionUser(httpRequest.getSession().getId(), httpRequest.getUserPrincipal().getName());
sessionmgmt.updateSessionUser(httpRequest.getSession().getId(),
httpRequest.getUserPrincipal().getName());
}
}
......@@ -223,17 +225,26 @@ public class HostnameFilter implements Filter {
}
}
StringBuilder hashBuilder = new StringBuilder();
hashBuilder.append("rest:");
hashBuilder.append(httpRequest.getParameter("appkey")).append(":");
hashBuilder.append(httpRequest.getParameter("appuser")).append(":");
hashBuilder.append(httpRequest.getParameter("appstamp")).append(":");
hashBuilder.append(httpRequest.getParameter("appmac")).append(":");
hashBuilder.append(httpRequest.getPathInfo());
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");
}
if (restAuthStr == null) {
StringBuilder hashBuilder = new StringBuilder();
hashBuilder.append(JaasBeanLocal.REST_PREFIX );
hashBuilder.append(httpRequest.getParameter("appkey")).append(":");
hashBuilder.append(httpRequest.getParameter("appuser")).append(":");
hashBuilder.append(httpRequest.getParameter("appstamp")).append(":");
hashBuilder.append(httpRequest.getParameter("appmac")).append(":");
hashBuilder.append(httpRequest.getPathInfo());
}
boolean ret = true;
try {
httpRequest.login(null, hashBuilder.toString());
httpRequest.login(null, restAuthStr);
} catch (ServletException loginEx) {
ret = false;
logger.info("Rest api authentication failed! ", loginEx);
......@@ -253,8 +264,7 @@ public class HostnameFilter implements Filter {
// int beginindex = 8; // Let's skip http://
int beginindex = url.indexOf("//", 0);
if (beginindex < 0)
{
if (beginindex < 0) {
beginindex = 0;
} else {
beginindex = beginindex + 2;
......@@ -292,9 +302,8 @@ public class HostnameFilter implements Filter {
login = principal.getName();
}
logbean.sendMessage(MoyaEventType.USER_PERMISSION_VIOLATION,
"Hostname mismatch privilege escalation! User '", login,
"' tried to change hostname from '", sessionHostname,
"' to '", hostname, ",");
"Hostname mismatch privilege escalation! User '", login, "' tried to change hostname from '",
sessionHostname, "' to '", hostname, ",");
throw new RuntimeException("Hostname mismatch!");
}
......
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!