updated libraries

updated to JUNIT5
This commit is contained in:
Joern Muehlencord
2019-07-12 15:07:13 +02:00
parent 3ae4dba8fe
commit 24dc927ab7
57 changed files with 1196 additions and 1083 deletions

View File

@ -54,15 +54,20 @@
<type>jar</type>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<scope>test</scope>
</dependency>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-junit-jupiter</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>

View File

@ -163,8 +163,9 @@ public class ApiKeyService implements Serializable {
@Transactional
@Lock(LockType.WRITE)
public ApiKeyObject createNewApiKey(String userName, short expirationInMinutes) throws ApiKeyException {
if ((password == null || issuer == null) || (userName == null)) {
LOGGER.error("password, issuer or username not set in, please validate configuration");
if (password == null || issuer == null || userName == null) {
String hint = "password, issuer or username not set in, please validate configuration";
throw new ApiKeyException(hint);
}
Date now = DateUtil.getCurrentTimeInUTC();
ZonedDateTime issuedOn = ZonedDateTime.ofInstant(now.toInstant(), ZoneId.of("UTC"));

View File

@ -15,7 +15,6 @@
*/
package de.muehlencord.shared.account.business.config.boundary;
import de.muehlencord.shared.db.ControllerException;
import de.muehlencord.shared.account.business.account.entity.Account;
import de.muehlencord.shared.account.business.account.entity.AccountEntity;
import de.muehlencord.shared.account.business.application.entity.ApplicationEntity;
@ -23,6 +22,7 @@ import de.muehlencord.shared.account.business.config.entity.ConfigEntity;
import de.muehlencord.shared.account.business.config.entity.ConfigEntityPK;
import de.muehlencord.shared.account.business.config.entity.ConfigException;
import de.muehlencord.shared.account.util.AccountPU;
import de.muehlencord.shared.db.ControllerException;
import java.io.Serializable;
import java.util.List;
import java.util.Optional;
@ -37,6 +37,7 @@ import javax.inject.Inject;
import javax.persistence.EntityManager;
import javax.persistence.Query;
import javax.transaction.Transactional;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -121,7 +122,7 @@ public class ConfigService implements Serializable {
// if config value is not found null has been returned
// in this case the value to return is the defaultValue
if (configValue == null) {
if (StringUtils.isEmpty(configValue)) {
configValue = defaultValue;
}

View File

@ -15,10 +15,10 @@
*/
package de.muehlencord.shared.account.business.instance.boundary;
import de.muehlencord.shared.db.ControllerException;
import de.muehlencord.shared.account.business.application.entity.ApplicationEntity;
import de.muehlencord.shared.account.business.config.boundary.ConfigService;
import de.muehlencord.shared.account.business.config.entity.ConfigException;
import de.muehlencord.shared.db.ControllerException;
import javax.annotation.PreDestroy;
import javax.enterprise.context.ApplicationScoped;
import javax.enterprise.context.Initialized;

View File

@ -1,202 +1,201 @@
/*
* Copyright 2018 joern.muehlencord.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package de.muehlencord.shared.account.presentation;
import de.muehlencord.shared.account.business.account.control.AccountControl;
import de.muehlencord.shared.account.business.account.entity.AccountEntity;
import de.muehlencord.shared.jeeutil.FacesUtil;
import java.io.IOException;
import java.io.Serializable;
import javax.ejb.EJB;
import javax.faces.context.ExternalContext;
import javax.faces.context.FacesContext;
import javax.faces.view.ViewScoped;
import javax.inject.Named;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.web.util.WebUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
*
* @author joern.muehlencord
*/
@Named(value = "loginView")
@ViewScoped
public class LoginView implements Serializable {
private static final long serialVersionUID = -1164860380769648432L;
@EJB
private AccountControl accountService;
private String username = null;
private String password = null;
private boolean rememberMe = false;
private String resetPasswordToken = null;
private static final Logger LOGGER = LoggerFactory.getLogger(LoginView.class.getName());
public void authenticate() {
// Example using most common scenario of username/password pair:
UsernamePasswordToken token = new UsernamePasswordToken(getUsername(), getPassword());
// "Remember Me" built-in:
token.setRememberMe(rememberMe);
Subject currentUser = SecurityUtils.getSubject();
LOGGER.info("Trying to login user {}", username);
try {
currentUser.login(token);
LOGGER.info("User {} logged in", username);
// user logged in, update account entity
AccountEntity account = accountService.getAccountEntity(username, true);
accountService.updateLogin(account);
// redirect to home
ExternalContext ec = FacesContext.getCurrentInstance().getExternalContext();
ServletResponse servletResponse = (ServletResponse) ec.getResponse();
String fallbackUrl = "/web/index.xhtml"; // TODO make configurable
// ec.redirect(url);
if (LOGGER.isTraceEnabled()) {
LOGGER.trace("redirecting to {}, fallbackUrl={}", servletResponse.toString(), fallbackUrl);
}
WebUtils.redirectToSavedRequest((ServletRequest) ec.getRequest(), servletResponse, fallbackUrl);
} catch (IOException | AuthenticationException ex) {
// Could catch a subclass of AuthenticationException if you like
String hint = "Error while authenticating user " + username;
if (LOGGER.isDebugEnabled()) {
LOGGER.debug(hint, ex);
}
if (ex.getMessage() != null) {
hint += "Reason: " + ex.getMessage();
} else {
hint += "Reason: " + ex.toString();
}
if ((ex.getCause() != null) && (ex.getCause().getMessage() != null)) {
hint += "Rootcause: " + ex.getMessage();
LOGGER.error(hint);
}
FacesUtil.addGlobalErrorMessage("Login failed", hint);
AccountEntity account = accountService.getAccountEntity(username, false);
if (account != null) {
accountService.addLoginError(account);
}
} finally {
token.clear();
}
}
public void logout() {
Subject currentUser = SecurityUtils.getSubject();
try {
currentUser.logout();
ExternalContext ec = FacesContext.getCurrentInstance().getExternalContext();
// ensure faces session is invalidated so beans are destroyed
ec.invalidateSession();
// check if redirect shall be executed
// default setting is yes to /login.xhtml
// can be overwritten using parameters
// de.muehlencord.shared.account.loginview.executeredirect boolean true/false
// de.muehlencord.shared.account.loginview.redirecttarget path to redirect to (without external context, will be added automatically)
String executeRedirectString = ec.getInitParameter("de.muehlencord.shared.account.loginview.executeredirect");
boolean executeRedirect = true;
if (executeRedirectString != null) {
executeRedirect = Boolean.parseBoolean(executeRedirectString);
}
String redirectTarget = ec.getInitParameter("de.muehlencord.shared.account.loginview.redirecttarget");
if ((redirectTarget == null) || (redirectTarget.equals(""))) {
redirectTarget = "/login.xhtml";
}
if (executeRedirect) {
String url = ec.getRequestContextPath() + redirectTarget;
ec.redirect(url);
}
} catch (Exception e) {
LOGGER.warn(e.toString());
}
}
public String executePasswordReset() {
boolean passwordResetted = accountService.resetPassword(username, password, resetPasswordToken);
if (passwordResetted) {
// TODO add email notification on updated user account
FacesUtil.addGlobalInfoMessage("Password resetted", null);
return login();
} else {
// TODO add email notificaton on failed password reset
FacesUtil.addGlobalErrorMessage("Password reset failed", null);
return login();
}
}
/* **** naviation rules **** */
public String login() {
return "/login.xhtml"; // TODO make configurable
}
/* *** getter / setter */
public String getUsername() {
return username;
}
public void setUsername(String un) {
this.username = un;
}
public String getPassword() {
return password;
}
public void setPassword(String pw) {
this.password = pw;
}
public boolean isRememberMe() {
return rememberMe;
}
public void setRememberMe(boolean rememberMe) {
this.rememberMe = rememberMe;
}
public String getResetPasswordToken() {
return resetPasswordToken;
}
public void setResetPasswordToken(String resetPasswordToken) {
this.resetPasswordToken = resetPasswordToken;
}
}
/*
* Copyright 2018 joern.muehlencord.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package de.muehlencord.shared.account.presentation;
import de.muehlencord.shared.account.business.account.control.AccountControl;
import de.muehlencord.shared.account.business.account.entity.AccountEntity;
import de.muehlencord.shared.jeeutil.FacesUtil;
import java.io.IOException;
import java.io.Serializable;
import javax.ejb.EJB;
import javax.faces.context.ExternalContext;
import javax.faces.context.FacesContext;
import javax.faces.view.ViewScoped;
import javax.inject.Named;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.util.WebUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
*
* @author joern.muehlencord
*/
@Named(value = "loginView")
@ViewScoped
public class LoginView implements Serializable {
private static final long serialVersionUID = -1164860380769648432L;
@EJB
private AccountControl accountService;
private String username = null;
private String password = null;
private boolean rememberMe = false;
private String resetPasswordToken = null;
private static final Logger LOGGER = LoggerFactory.getLogger(LoginView.class.getName());
public void authenticate() {
// Example using most common scenario of username/password pair:
UsernamePasswordToken token = new UsernamePasswordToken(getUsername(), getPassword());
// "Remember Me" built-in:
token.setRememberMe(rememberMe);
Subject currentUser = SecurityUtils.getSubject();
LOGGER.info("Trying to login user {}", username);
try {
currentUser.login(token);
LOGGER.info("User {} logged in", username);
// user logged in, update account entity
AccountEntity account = accountService.getAccountEntity(username, true);
accountService.updateLogin(account);
// redirect to home
ExternalContext ec = FacesContext.getCurrentInstance().getExternalContext();
ServletResponse servletResponse = (ServletResponse) ec.getResponse();
String fallbackUrl = "/web/index.xhtml"; // TODO make configurable
// ec.redirect(url);
if (LOGGER.isTraceEnabled()) {
LOGGER.trace("redirecting to {}, fallbackUrl={}", servletResponse.toString(), fallbackUrl);
}
WebUtils.redirectToSavedRequest((ServletRequest) ec.getRequest(), servletResponse, fallbackUrl);
} catch (IOException | AuthenticationException ex) {
// Could catch a subclass of AuthenticationException if you like
String hint = "Error while authenticating user " + username;
if (LOGGER.isDebugEnabled()) {
LOGGER.debug(hint, ex);
}
if (ex.getMessage() != null) {
hint += "Reason: " + ex.getMessage();
} else {
hint += "Reason: " + ex.toString();
}
if ((ex.getCause() != null) && (ex.getCause().getMessage() != null)) {
hint += "Rootcause: " + ex.getMessage();
LOGGER.error(hint);
}
FacesUtil.addGlobalErrorMessage("Login failed", hint);
AccountEntity account = accountService.getAccountEntity(username, false);
if (account != null) {
accountService.addLoginError(account);
}
} finally {
token.clear();
}
}
public void logout() {
Subject currentUser = SecurityUtils.getSubject();
try {
currentUser.logout();
ExternalContext ec = FacesContext.getCurrentInstance().getExternalContext();
// ensure faces session is invalidated so beans are destroyed
ec.invalidateSession();
// check if redirect shall be executed
// default setting is yes to /login.xhtml
// can be overwritten using parameters
// de.muehlencord.shared.account.loginview.executeredirect boolean true/false
// de.muehlencord.shared.account.loginview.redirecttarget path to redirect to (without external context, will be added automatically)
String executeRedirectString = ec.getInitParameter("de.muehlencord.shared.account.loginview.executeredirect");
boolean executeRedirect = true;
if (executeRedirectString != null) {
executeRedirect = Boolean.parseBoolean(executeRedirectString);
}
String redirectTarget = ec.getInitParameter("de.muehlencord.shared.account.loginview.redirecttarget");
if ((redirectTarget == null) || (redirectTarget.equals(""))) {
redirectTarget = "/login.xhtml";
}
if (executeRedirect) {
String url = ec.getRequestContextPath() + redirectTarget;
ec.redirect(url);
}
} catch (Exception e) {
LOGGER.warn(e.toString());
}
}
public String executePasswordReset() {
boolean passwordResetted = accountService.resetPassword(username, password, resetPasswordToken);
if (passwordResetted) {
// TODO add email notification on updated user account
FacesUtil.addGlobalInfoMessage("Password resetted", null);
return login();
} else {
// TODO add email notificaton on failed password reset
FacesUtil.addGlobalErrorMessage("Password reset failed", null);
return login();
}
}
/* **** naviation rules **** */
public String login() {
return "/login.xhtml"; // TODO make configurable
}
/* *** getter / setter */
public String getUsername() {
return username;
}
public void setUsername(String un) {
this.username = un;
}
public String getPassword() {
return password;
}
public void setPassword(String pw) {
this.password = pw;
}
public boolean isRememberMe() {
return rememberMe;
}
public void setRememberMe(boolean rememberMe) {
this.rememberMe = rememberMe;
}
public String getResetPasswordToken() {
return resetPasswordToken;
}
public void setResetPasswordToken(String resetPasswordToken) {
this.resetPasswordToken = resetPasswordToken;
}
}

View File

@ -1,172 +1,169 @@
/*
* Copyright 2018 joern.muehlencord.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package de.muehlencord.shared.account.shiro.filter;
import java.io.IOException;
import java.io.StringReader;
import javax.json.Json;
import javax.json.JsonObject;
import javax.json.JsonReader;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.io.IOUtils;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.web.filter.authc.AuthenticatingFilter;
import org.apache.shiro.web.util.WebUtils;
import org.slf4j.LoggerFactory;
import de.muehlencord.shared.account.business.account.boundary.ApiKeyService;
import de.muehlencord.shared.account.business.account.entity.JWTObject;
import de.muehlencord.shared.account.shiro.token.JWTAuthenticationToken;
import de.muehlencord.shared.account.util.AccountSecurityException;
import de.muehlencord.shared.jeeutil.restexfw.APIException;
/**
*
* @author Joern Muehlencord <joern at muehlencord.de>
*/
public final class JWTAuthenticationFilter extends AuthenticatingFilter {
private static final org.slf4j.Logger LOGGER = LoggerFactory.getLogger(JWTAuthenticationFilter.class);
protected static final String AUTHORIZATION_HEADER = "Authorization"; // NOI18N
protected static final String USERNAME = "username"; // NOI18N
protected static final String PASSWORD = "password"; // NOI18N
private final ApiKeyService apiKeyService = lookupApiKeyServiceBean();
public JWTAuthenticationFilter() {
// FIXME - logging in via JWTAuthenticationFilter does not yet work. Need to set login to anonymous to execute login in rest service
setLoginUrl("/rest/account/login");
}
@Override
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
boolean loggedIn = false;
if (isLoginRequest(request, response) || isLoggedAttempt(request, response)) {
loggedIn = executeLogin(request, response);
}
if (!loggedIn) {
HttpServletResponse httpResponse = WebUtils.toHttp(response);
httpResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
}
return loggedIn;
}
@Override
protected AuthenticationToken createToken(ServletRequest request, ServletResponse response) throws Exception {
if (isLoginRequest(request, response)) {
String json = IOUtils.toString(request.getInputStream(), "UTF-8"); // FIXME - check charset in request
if (json != null && !json.isEmpty()) {
try (JsonReader jr = Json.createReader(new StringReader(json))) {
JsonObject object = jr.readObject();
String username = object.getString(USERNAME);
String password = object.getString(PASSWORD);
return new UsernamePasswordToken(username, password);
}
}
}
if (isLoggedAttempt(request, response)) {
String jwtToken = getAuthzHeader(request);
if (LOGGER.isTraceEnabled()) {
LOGGER.trace("found jwtToke in header = {}", jwtToken);
}
if (jwtToken != null) {
JWTObject jwtObject = apiKeyService.getJWTObject(jwtToken);
return new JWTAuthenticationToken(jwtObject.getUserName(), jwtToken);
}
}
return new UsernamePasswordToken();
}
private boolean isLoggedAttempt(ServletRequest request, ServletResponse response) {
String authzHeader = getAuthzHeader(request);
return authzHeader != null;
}
private String getAuthzHeader(ServletRequest request) {
HttpServletRequest httpRequest = WebUtils.toHttp(request);
return httpRequest.getHeader(AUTHORIZATION_HEADER);
}
/**
* Overwrite cleanup to ensure no exception is thrown if an
* AccountSecurityException / APIException is raised during login. As long
* as the user is not logged in JERSEYs ExceptionMapper and intercepor
* classes are overruled by Shiro.
*
* @param request the incoming request
* @param response the response to return
* @param existing the raised exception
* @throws ServletException may be thrown by AuthenticatingFilter.cleanup if
* existing is not a AccountSecurityException
* @throws IOException may be thrown by AuthenticatingFilter.cleanup if
* existing is not a AccountSecurityException
*/
@Override
protected void cleanup(ServletRequest request, ServletResponse response, Exception existing) throws ServletException, IOException {
if ((existing != null) && (existing.getClass().isAssignableFrom(AccountSecurityException.class))) {
HttpServletResponse httpResponse = WebUtils.toHttp(response);
httpResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
} else if ((existing != null) && (existing.getClass().isAssignableFrom(APIException.class))) {
APIException apiException = (APIException) existing;
HttpServletResponse httpResponse = WebUtils.toHttp(response);
httpResponse.setStatus(apiException.getHttpResponse().getStatus());
httpResponse.addHeader(APIException.HTTP_HEADER_X_ERROR, apiException.getHttpResponse().getHeaderString(APIException.HTTP_HEADER_X_ERROR));
httpResponse.addHeader(APIException.HTTP_HEADER_X_ERROR_CODE, apiException.getHttpResponse().getHeaderString(APIException.HTTP_HEADER_X_ERROR_CODE));
if (apiException.getHttpResponse().getHeaderString(APIException.HTTP_HEADER_X_ROOT_CAUSE) != null) {
httpResponse.addHeader(APIException.HTTP_HEADER_X_ROOT_CAUSE, apiException.getHttpResponse().getHeaderString(APIException.HTTP_HEADER_X_ROOT_CAUSE));
}
} else {
super.cleanup(request, response, existing);
}
}
// TODO - can this be injected?
private ApiKeyService lookupApiKeyServiceBean() {
try {
Context c = new InitialContext();
return (ApiKeyService) c.lookup("java:module/ApiKeyService"); // NOI18N
} catch (NamingException ex) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug(ex.toString(), ex);
} else {
LOGGER.error(ex.toString());
}
throw new RuntimeException(ex);
}
}
}
/*
* Copyright 2018 joern.muehlencord.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package de.muehlencord.shared.account.shiro.filter;
import de.muehlencord.shared.account.business.account.boundary.ApiKeyService;
import de.muehlencord.shared.account.business.account.entity.JWTObject;
import de.muehlencord.shared.account.shiro.token.JWTAuthenticationToken;
import de.muehlencord.shared.account.util.AccountSecurityException;
import de.muehlencord.shared.jeeutil.restexfw.APIException;
import java.io.IOException;
import java.io.StringReader;
import javax.json.Json;
import javax.json.JsonObject;
import javax.json.JsonReader;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.io.IOUtils;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.web.filter.authc.AuthenticatingFilter;
import org.apache.shiro.web.util.WebUtils;
import org.slf4j.LoggerFactory;
/**
*
* @author Joern Muehlencord <joern at muehlencord.de>
*/
public final class JWTAuthenticationFilter extends AuthenticatingFilter {
private static final org.slf4j.Logger LOGGER = LoggerFactory.getLogger(JWTAuthenticationFilter.class);
protected static final String AUTHORIZATION_HEADER = "Authorization"; // NOI18N
protected static final String USERNAME = "username"; // NOI18N
protected static final String PASSWORD = "password"; // NOI18N
private final ApiKeyService apiKeyService = lookupApiKeyServiceBean();
public JWTAuthenticationFilter() {
// FIXME - logging in via JWTAuthenticationFilter does not yet work. Need to set login to anonymous to execute login in rest service
setLoginUrl("/rest/account/login");
}
@Override
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
boolean loggedIn = false;
if (isLoginRequest(request, response) || isLoggedAttempt(request, response)) {
loggedIn = executeLogin(request, response);
}
if (!loggedIn) {
HttpServletResponse httpResponse = WebUtils.toHttp(response);
httpResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
}
return loggedIn;
}
@Override
protected AuthenticationToken createToken(ServletRequest request, ServletResponse response) throws Exception {
if (isLoginRequest(request, response)) {
String json = IOUtils.toString(request.getInputStream(), "UTF-8"); // FIXME - check charset in request
if (json != null && !json.isEmpty()) {
try (JsonReader jr = Json.createReader(new StringReader(json))) {
JsonObject object = jr.readObject();
String username = object.getString(USERNAME);
String password = object.getString(PASSWORD);
return new UsernamePasswordToken(username, password);
}
}
}
if (isLoggedAttempt(request, response)) {
String jwtToken = getAuthzHeader(request);
if (LOGGER.isTraceEnabled()) {
LOGGER.trace("found jwtToke in header = {}", jwtToken);
}
if (jwtToken != null) {
JWTObject jwtObject = apiKeyService.getJWTObject(jwtToken);
return new JWTAuthenticationToken(jwtObject.getUserName(), jwtToken);
}
}
return new UsernamePasswordToken();
}
private boolean isLoggedAttempt(ServletRequest request, ServletResponse response) {
String authzHeader = getAuthzHeader(request);
return authzHeader != null;
}
private String getAuthzHeader(ServletRequest request) {
HttpServletRequest httpRequest = WebUtils.toHttp(request);
return httpRequest.getHeader(AUTHORIZATION_HEADER);
}
/**
* Overwrite cleanup to ensure no exception is thrown if an
* AccountSecurityException / APIException is raised during login. As long
* as the user is not logged in JERSEYs ExceptionMapper and intercepor
* classes are overruled by Shiro.
*
* @param request the incoming request
* @param response the response to return
* @param existing the raised exception
* @throws ServletException may be thrown by AuthenticatingFilter.cleanup if
* existing is not a AccountSecurityException
* @throws IOException may be thrown by AuthenticatingFilter.cleanup if
* existing is not a AccountSecurityException
*/
@Override
protected void cleanup(ServletRequest request, ServletResponse response, Exception existing) throws ServletException, IOException {
if ((existing != null) && (existing.getClass().isAssignableFrom(AccountSecurityException.class))) {
HttpServletResponse httpResponse = WebUtils.toHttp(response);
httpResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
} else if ((existing != null) && (existing.getClass().isAssignableFrom(APIException.class))) {
APIException apiException = (APIException) existing;
HttpServletResponse httpResponse = WebUtils.toHttp(response);
httpResponse.setStatus(apiException.getHttpResponse().getStatus());
httpResponse.addHeader(APIException.HTTP_HEADER_X_ERROR, apiException.getHttpResponse().getHeaderString(APIException.HTTP_HEADER_X_ERROR));
httpResponse.addHeader(APIException.HTTP_HEADER_X_ERROR_CODE, apiException.getHttpResponse().getHeaderString(APIException.HTTP_HEADER_X_ERROR_CODE));
if (apiException.getHttpResponse().getHeaderString(APIException.HTTP_HEADER_X_ROOT_CAUSE) != null) {
httpResponse.addHeader(APIException.HTTP_HEADER_X_ROOT_CAUSE, apiException.getHttpResponse().getHeaderString(APIException.HTTP_HEADER_X_ROOT_CAUSE));
}
} else {
super.cleanup(request, response, existing);
}
}
// TODO - can this be injected?
private ApiKeyService lookupApiKeyServiceBean() {
try {
Context c = new InitialContext();
return (ApiKeyService) c.lookup("java:module/ApiKeyService"); // NOI18N
} catch (NamingException ex) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug(ex.toString(), ex);
} else {
LOGGER.error(ex.toString());
}
throw new RuntimeException(ex);
}
}
}

View File

@ -1,21 +1,98 @@
package de.muehlencord.shared.account.business.account.boundary;
import org.junit.Test;
import de.muehlencord.shared.account.business.account.control.AccountControl;
import de.muehlencord.shared.account.business.account.entity.AccountEntity;
import de.muehlencord.shared.account.business.application.entity.ApplicationRoleEntity;
import de.muehlencord.shared.account.business.config.boundary.ConfigService;
import de.muehlencord.shared.account.business.config.entity.ConfigException;
import de.muehlencord.shared.account.dao.ApiKeyObject;
import de.muehlencord.shared.db.ControllerException;
import java.util.ArrayList;
import javax.persistence.EntityManager;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.fail;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.mockito.stubbing.Answer;
import org.slf4j.LoggerFactory;
/**
*
* @author Joern Muehlencord <joern at muehlencord.de>
*/
@ExtendWith(MockitoExtension.class)
public class ApiKeyServiceTest {
private static final org.slf4j.Logger LOGGER = LoggerFactory.getLogger(ApiKeyServiceTest.class);
ApiKeyService apiKeyService;
@BeforeEach
public void setup() {
apiKeyService = new ApiKeyService();
apiKeyService.configService = mock(ConfigService.class);
try {
when(apiKeyService.configService.getConfigValue(anyString(), anyString(), anyBoolean())).thenReturn("120");
when(apiKeyService.configService.getConfigValue(anyString())).thenAnswer(new Answer<String>() {
@Override
public String answer(InvocationOnMock invocation) throws Throwable {
Object[] args = invocation.getArguments();
String configKey = args[0].toString();
switch (configKey) {
case "rest.password":
return "secret";
case "rest.issuer":
return "ApiKeyServiceTest";
default:
return "unknown";
}
}
});
} catch (ConfigException | ControllerException ex) {
LOGGER.error(ex.getMessage());
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Detailed stacktrace", new Object[]{ex});
}
fail("An exception occured");
}
apiKeyService.accountControl = mock(AccountControl.class);
when(apiKeyService.accountControl.getAccountEntity(anyString(), anyBoolean())).thenAnswer(new Answer<AccountEntity>() {
@Override
public AccountEntity answer(InvocationOnMock iom) throws Throwable {
Object[] args = iom.getArguments();
String userName = args[0].toString();
boolean loadRoles = (boolean) args[1];
AccountEntity account = new AccountEntity();
account.setUsername(userName);
account.setFirstname("FirstName");
account.setLastname("LastName");
if (loadRoles) {
account.setApplicationRoleList(new ArrayList<>());
ApplicationRoleEntity exampleRoleEntity = new ApplicationRoleEntity();
exampleRoleEntity.setRoleName("Example Role");
account.getApplicationRoleList().add(exampleRoleEntity);
}
return account;
}
});
apiKeyService.em = mock (EntityManager.class);
apiKeyService.init();
}
@Test
public void testCreateApiKey() {
try {
ApiKeyService apiKeyService = new ApiKeyService();
apiKeyService.createNewApiKey("web", (short) 120);
ApiKeyObject apiKeyObject = apiKeyService.createNewApiKey("web", (short) 120);
assertNotNull(apiKeyObject);
LOGGER.info("authToken: "+apiKeyObject.getAuthToken());
} catch (ApiKeyException ex) {
LOGGER.error(ex.getMessage());
if (LOGGER.isDebugEnabled()) {

View File

@ -1,76 +1,76 @@
/*
* Copyright 2018 joern.muehlencord.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package de.muehlencord.shared.account.business.config.boundary;
import de.muehlencord.shared.account.business.account.entity.AccountEntity;
import de.muehlencord.shared.account.business.account.entity.AccountLoginEntity;
import de.muehlencord.shared.account.business.application.entity.ApplicationEntity;
import de.muehlencord.shared.account.business.config.entity.ConfigEntity;
import de.muehlencord.shared.account.business.config.entity.ConfigEntityPK;
import java.util.UUID;
import javax.persistence.EntityManager;
import org.junit.Test;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import static org.mockito.Mockito.when;
import org.mockito.runners.MockitoJUnitRunner;
/**
*
* @author joern.muehlencord
*/
@RunWith(MockitoJUnitRunner.class)
public class ConfigServiceTest {
@InjectMocks
private ConfigService configService;
@Mock
private EntityManager entityManagerMock;
@Before
public void init() {
ApplicationEntity application = new ApplicationEntity();
application.setId(UUID.randomUUID());
application.setApplicationName("Test App");
AccountEntity account = new AccountEntity();
account.setUsername("system");
AccountLoginEntity login = new AccountLoginEntity();
login.setAccount (account);
account.setAccountLogin(login);
ConfigEntityPK pk = new ConfigEntityPK(application, "account.maxFailedLogins", account);
ConfigEntity configEntity = new ConfigEntity (pk);
configEntity.setConfigValue("7");
when (entityManagerMock.find(ConfigEntity.class, "account.maxFailedLogins")).thenReturn (configEntity);
}
@Test
@Ignore
// TODO move to account test
public void testGetMaxFailedLogins() {
// configService.init();
// assertEquals ("maxFailedLogins", 7, configService.getMaxFailedLogins());
}
}
/*
* Copyright 2018 joern.muehlencord.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package de.muehlencord.shared.account.business.config.boundary;
import de.muehlencord.shared.account.business.account.entity.AccountEntity;
import de.muehlencord.shared.account.business.account.entity.AccountLoginEntity;
import de.muehlencord.shared.account.business.application.entity.ApplicationEntity;
import de.muehlencord.shared.account.business.config.entity.ConfigEntity;
import de.muehlencord.shared.account.business.config.entity.ConfigEntityPK;
import java.util.UUID;
import javax.persistence.EntityManager;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import static org.mockito.Mockito.when;
import org.mockito.junit.jupiter.MockitoExtension;
/**
*
* @author joern.muehlencord
*/
@ExtendWith(MockitoExtension.class)
public class ConfigServiceTest {
@InjectMocks
private ConfigService configService;
@Mock
private EntityManager entityManagerMock;
@BeforeEach
public void init() {
ApplicationEntity application = new ApplicationEntity();
application.setId(UUID.randomUUID());
application.setApplicationName("Test App");
AccountEntity account = new AccountEntity();
account.setUsername("system");
AccountLoginEntity login = new AccountLoginEntity();
login.setAccount (account);
account.setAccountLogin(login);
ConfigEntityPK pk = new ConfigEntityPK(application, "account.maxFailedLogins", account);
ConfigEntity configEntity = new ConfigEntity (pk);
configEntity.setConfigValue("7");
when (entityManagerMock.find(ConfigEntity.class, "account.maxFailedLogins")).thenReturn (configEntity);
}
@Test
@Disabled
// TODO move to account test
public void testGetMaxFailedLogins() {
// configService.init();
// assertEquals ("maxFailedLogins", 7, configService.getMaxFailedLogins());
}
}

View File

@ -1,68 +1,75 @@
/*
* Copyright 2018 joern.muehlencord.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package de.muehlencord.shared.account.shiro.realm;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.config.IniSecurityManagerFactory;
import org.apache.shiro.subject.Subject;
import org.junit.Test;
import org.apache.shiro.mgt.SecurityManager;
import static org.junit.Assume.assumeNotNull;
/**
*
* @author Joern Muehlencord <joern at muehlencord.de>
*/
public class UserNameActiveDirectoryRealmTest {
@Test
public void testUsernameLogin() {
String userName = "user.name";
String password = "secret";
testLogin(userName, password);
}
@Test
public void testEmailaddressLogin() {
String userName = "user.name@domain.com";
String password = "secret";
testLogin(userName, password);
}
@Test(expected=AuthenticationException.class)
public void testWrongUserNamePassword() {
String userName = "test123";
String password = "secret";
testLogin(userName, password);
}
private void testLogin(String userName, String password) throws AuthenticationException {
assumeNotNull(UserNameActiveDirectoryRealmTest.class.getResource("/shiro.ini"));
IniSecurityManagerFactory factory = new IniSecurityManagerFactory("classpath:shiro.ini");
SecurityManager securityManager = factory.getInstance();
SecurityUtils.setSecurityManager(securityManager);
UsernamePasswordToken token = new UsernamePasswordToken(userName, password);
Subject currentUser = SecurityUtils.getSubject();
currentUser.login(token);
System.out.println("Logged in");
}
}
/*
* Copyright 2018 joern.muehlencord.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package de.muehlencord.shared.account.shiro.realm;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.config.IniSecurityManagerFactory;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.subject.Subject;
import org.junit.jupiter.api.Assertions;
import static org.junit.jupiter.api.Assumptions.assumeFalse;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
/**
*
* @author Joern Muehlencord <joern at muehlencord.de>
*/
public class UserNameActiveDirectoryRealmTest {
@Test
public void testUsernameLogin() {
String userName = "root";
String password = "secret";
testLogin(userName, password);
}
@Test
@Disabled
public void testEmailaddressLogin() {
String userName = "user.name@domain.com";
String password = "secret";
Assertions.assertThrows(AuthenticationException.class, () -> {
testLogin(userName, password);
});
}
@Test
public void testWrongUserNamePassword() {
String userName = "test123";
String password = "secret";
Assertions.assertThrows(AuthenticationException.class, () -> {
testLogin(userName, password);
});
}
private void testLogin(String userName, String password) throws AuthenticationException {
assumeFalse(UserNameActiveDirectoryRealmTest.class.getResource("/shiro.ini") == null);
IniSecurityManagerFactory factory = new IniSecurityManagerFactory("classpath:shiro.ini");
SecurityManager securityManager = factory.getInstance();
SecurityUtils.setSecurityManager(securityManager);
UsernamePasswordToken token = new UsernamePasswordToken(userName, password);
Subject currentUser = SecurityUtils.getSubject();
currentUser.login(token);
System.out.println("Logged in");
}
}

View File

@ -1,32 +1,32 @@
/*
* Copyright 2018 joern.muehlencord.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package de.muehlencord.shared.account.util;
import org.junit.Test;
/**
*
* @author Joern Muehlencord <joern at muehlencord.de>
*/
public class SecurityUtilTest {
@Test
public void testCreatePassword() {
System.out.println (SecurityUtil.createPassword("secret"));
}
}
/*
* Copyright 2018 joern.muehlencord.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package de.muehlencord.shared.account.util;
import org.junit.jupiter.api.Test;
/**
*
* @author Joern Muehlencord <joern at muehlencord.de>
*/
public class SecurityUtilTest {
@Test
public void testCreatePassword() {
System.out.println (SecurityUtil.createPassword("secret"));
}
}

View File

@ -0,0 +1,25 @@
# =============================================================================
# Tutorial INI configuration
#
# Usernames/passwords are based on the classic Mel Brooks' film "Spaceballs" :)
# =============================================================================
# -----------------------------------------------------------------------------
# Users and their (optional) assigned roles
# username = password, role1, role2, ..., roleN
# -----------------------------------------------------------------------------
[users]
root = secret, admin
guest = guest, guest
presidentskroob = 12345, president
darkhelmet = ludicrousspeed, darklord, schwartz
lonestarr = vespa, goodguy, schwartz
# -----------------------------------------------------------------------------
# Roles with assigned permissions
# roleName = perm1, perm2, ..., permN
# -----------------------------------------------------------------------------
[roles]
admin = *
schwartz = lightsaber:*
goodguy = winnebago:drive:eagle5