Commit f2abe3a0 by Tuukka Kivilahti

Merge branch 'basic-resauth' into 'master'

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

Static login can be used only if ssl is used (or debug is enabled). 

Login credentials are provided as normal basic auth schema. Username should be 'appauth' and password should be {ApiApplication.applicationKey}:{ApiApplicationInstance.authname}:{ApiApplicationInstance.apikey}', eg 'acdcabbacd:usersapikey:supersecretuserkey'

See merge request !335
2 parents bf394773 42ac6905
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 || !PASSWORD_PREFIX.equals(splitStr[0])) {
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 (appInstance != null && 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;
......@@ -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 {
}
}
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("rest:");
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!