reogranized source code

This commit is contained in:
2018-11-15 11:48:27 +01:00
parent 389e3a6a73
commit 7b1d4f24ab
19 changed files with 782 additions and 776 deletions

View File

@ -0,0 +1,91 @@
/*
* Copyright 2017 Joern Muehlencord <joern at muehlencord.de>.
*
* 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.account.boundary;
import de.muehlencord.shared.account.business.account.control.AccountControl;
import de.muehlencord.shared.account.business.account.entity.Account;
import java.io.Serializable;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import javax.annotation.ManagedBean;
import javax.ejb.EJB;
import javax.enterprise.context.SessionScoped;
import javax.enterprise.inject.Produces;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.subject.Subject;
/**
*
* @author Joern Muehlencord <joern at muehlencord.de>
*/
@ManagedBean
@SessionScoped
public class AccountProducer implements Serializable {
private static final long serialVersionUID = -3806204732038165311L;
private final Map<String, Object> objectMap = new ConcurrentHashMap<>();
@EJB
AccountControl accountController;
private Account account = null;
@Produces
public Account getAccount() {
String accountName;
if (account == null) {
Subject subject = SecurityUtils.getSubject();
if (subject == null) {
return null;
}
if ((subject.isAuthenticated() == false) && (subject.isRemembered() == false)) {
accountName = "web";
} else {
accountName = subject.getPrincipal().toString();
}
account = accountController.getAccountEntity(accountName, true);
}
return account;
}
public <T> T getValue(String key, Class<T> clazz) {
if (objectMap.containsKey(key)) {
Object obj = objectMap.get(key);
if (obj == null) {
return null;
}
try {
return clazz.cast(obj);
} catch (ClassCastException ex) {
return null;
}
} else {
return null;
}
}
public void setValue(String key, Object obj) {
objectMap.put(key, obj);
}
@Produces
public Locale getLocale() {
return Locale.ENGLISH; // TODO depend lcoale on account or on incoming request
}
}

View File

@ -1,4 +1,4 @@
package de.muehlencord.shared.account.business.account.boundary;
package de.muehlencord.shared.account.business.account.control;
import de.muehlencord.shared.account.business.account.entity.AccountException;
import de.muehlencord.shared.account.business.account.entity.AccountStatus;

View File

@ -0,0 +1,71 @@
/*
* Copyright 2018 Joern Muehlencord <joern at muehlencord.de>.
*
* 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.application.boundary;
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 javax.annotation.PreDestroy;
import javax.enterprise.context.ApplicationScoped;
import javax.enterprise.context.Initialized;
import javax.enterprise.event.Observes;
import javax.inject.Inject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
*
* @author Joern Muehlencord <joern at muehlencord.de>
*/
@ApplicationScoped
public class StartupBean {
private static final Logger LOGGER = LoggerFactory.getLogger(StartupBean.class);
@Inject
ConfigService configService;
@Inject
ApplicationEntity application;
public void init(@Observes @Initialized(ApplicationScoped.class) Object init) {
try {
LOGGER.info("Starting application {}", application.getApplicationName());
String instanceName = configService.getConfigValue("base.instance", "Development System", true);
LOGGER.info("instanceName={}", instanceName);
// ensure maxFailedLogins is available
configService.getConfigValue("account.maxFailedLogins", "5", true);
LOGGER.info("Application startup complete");
} catch (ConfigException ex) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug(ex.toString(), ex);
} else {
LOGGER.error(ex.toString());
}
}
}
@PreDestroy
public void shutdown() {
LOGGER.info("Shutting down application {}", application.getApplicationName());
LOGGER.info("Application shutdown complete");
}
}

View File

@ -0,0 +1,116 @@
/*
* Copyright 2018 Joern Muehlencord <joern at muehlencord.de>.
*
* 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.application.control;
import de.muehlencord.shared.account.business.application.boundary.ApplicationService;
import de.muehlencord.shared.account.business.application.entity.ApplicationEntity;
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
import java.util.UUID;
import javax.annotation.PostConstruct;
import javax.ejb.EJB;
import javax.enterprise.context.ApplicationScoped;
import javax.enterprise.inject.Produces;
import javax.inject.Named;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
*
* @author Joern Muehlencord <joern at muehlencord.de>
*/
@Named("applicationController")
@ApplicationScoped
public class ApplicationController {
private static final Logger LOGGER = LoggerFactory.getLogger(ApplicationController.class);
@EJB
ApplicationService applicationService;
private String version;
private String buildDate;
private UUID uuid;
private ApplicationEntity application = null;
@PostConstruct
public void readBuildInfoProperties() {
if (LOGGER.isTraceEnabled()) {
LOGGER.trace("Trying to read buildInfo.properties");
}
InputStream in = Thread.currentThread().getContextClassLoader().getResourceAsStream("buildInfo.properties");
if (in == null) {
return;
}
Properties props = new Properties();
try {
props.load(in);
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("properties read from buildInfo.properties");
}
version = props.getProperty("build.version");
buildDate = props.getProperty("build.timestamp");
uuid = UUID.fromString(props.getProperty("application.uuid"));
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("buildInfo.properties parsed successfully");
}
} catch (IOException ex) {
LOGGER.error("Cannot find buildInfo.properties. ", ex);
version = "??";
buildDate = "??";
uuid = null;
LOGGER.error("Application id not readable, application will not be able to run");
}
if (uuid != null) {
this.application = applicationService.findById(uuid);
if (application == null) {
LOGGER.error("Could not find application with id ");
} else {
LOGGER.info("Found application {} with id {}", application.getApplicationName(), uuid.toString());
}
}
}
/**
* needs to return link to "Account UI" and not to current selected
* application TODO: ensure only Account UI can call functions where
* appliction can be handed in - all other applications need to call the
* function which use the injected application
*/
@Produces
public ApplicationEntity getApplication() {
return application;
}
public String getVersion() {
return version;
}
public String getBuildDate() {
return buildDate;
}
public UUID getApplicationId() {
return uuid;
}
}

View File

@ -1,4 +1,4 @@
package de.muehlencord.shared.account.business.account.boundary;
package de.muehlencord.shared.account.business.application.control;
import de.muehlencord.shared.account.business.account.entity.AccountException;
import de.muehlencord.shared.account.business.account.entity.ApplicationPermissionEntity;

View File

@ -3,7 +3,7 @@
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package de.muehlencord.shared.account.business.account.boundary;
package de.muehlencord.shared.account.business.application.control;
import de.muehlencord.shared.account.business.account.entity.AccountException;
import de.muehlencord.shared.account.business.account.entity.ApplicationPermissionEntity;

View File

@ -0,0 +1,53 @@
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package de.muehlencord.shared.account.business.config.boundary;
import de.muehlencord.shared.account.business.config.boundary.ConfigService;
import de.muehlencord.shared.account.business.accountconfig.entity.AccountConfigurationKey;
import de.muehlencord.shared.account.business.accountconfig.entity.AccountConfigurationValue;
import de.muehlencord.shared.account.business.config.entity.ConfigException;
import javax.ejb.EJB;
import javax.enterprise.context.Dependent;
import javax.enterprise.inject.Produces;
import javax.enterprise.inject.spi.Annotated;
import javax.enterprise.inject.spi.InjectionPoint;
/**
*
* @author Joern Muehlencord <joern at muehlencord.de>
*/
@Dependent
public class ConfigurationProducer {
@EJB
ConfigService configService;
@Produces
@AccountConfigurationValue(key = AccountConfigurationKey.Producer)
public String produceConfigurationValue(InjectionPoint injectionPoint) {
Annotated annotated = injectionPoint.getAnnotated();
AccountConfigurationValue annotation = annotated.getAnnotation(AccountConfigurationValue.class);
if (annotation != null) {
AccountConfigurationKey key = annotation.key();
if (key != null) {
try {
switch (key) {
case BaseUrl:
return configService.getConfigValue("base.url");
case PasswordResetUrl:
return configService.getConfigValue("base.url") + "/login.xhtml";
default:
throw new IllegalStateException("Invalid key " + key + " for injection point: " + injectionPoint);
}
} catch (ConfigException ex) {
throw new IllegalStateException("Invalid key " + key + " for injection point: " + injectionPoint + ". Exception: " + ex.getMessage());
}
}
}
throw new IllegalStateException("No key for injection point: " + injectionPoint);
}
}

View File

@ -1,185 +1,185 @@
package de.muehlencord.shared.account.presentation;
import de.muehlencord.shared.account.business.account.boundary.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.naming.NamingException;
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();
// 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;
}
}
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.naming.NamingException;
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();
// 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,48 +1,48 @@
package de.muehlencord.shared.account.presentation;
import de.muehlencord.shared.account.business.account.boundary.AccountControl;
import de.muehlencord.shared.jeeutil.FacesUtil;
import java.io.Serializable;
import javax.ejb.EJB;
import javax.faces.view.ViewScoped;
import javax.inject.Named;
/**
*
* @author joern@muehlencord.de
*/
@Named (value = "lostPasswordView")
@ViewScoped
public class LostPasswordView implements Serializable {
private static final long serialVersionUID = -1793445795465830069L;
@EJB
private AccountControl accountService;
private String userName;
private boolean passwordResetStarted = false;
public String initPasswordReset() {
if (accountService.initPasswordReset(userName)) {
passwordResetStarted = true;
FacesUtil.addGlobalInfoMessage("Password reset started.", "Please check your email account.");
} else {
FacesUtil.addGlobalErrorMessage("Error while resetting password.", "Please contact your administrator.");
}
return "/login.xhtml"; // TODO make configurable, get from LoginView?
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public boolean getPasswordResetStarted() {
return passwordResetStarted;
}
}
package de.muehlencord.shared.account.presentation;
import de.muehlencord.shared.account.business.account.control.AccountControl;
import de.muehlencord.shared.jeeutil.FacesUtil;
import java.io.Serializable;
import javax.ejb.EJB;
import javax.faces.view.ViewScoped;
import javax.inject.Named;
/**
*
* @author joern@muehlencord.de
*/
@Named (value = "lostPasswordView")
@ViewScoped
public class LostPasswordView implements Serializable {
private static final long serialVersionUID = -1793445795465830069L;
@EJB
private AccountControl accountService;
private String userName;
private boolean passwordResetStarted = false;
public String initPasswordReset() {
if (accountService.initPasswordReset(userName)) {
passwordResetStarted = true;
FacesUtil.addGlobalInfoMessage("Password reset started.", "Please check your email account.");
} else {
FacesUtil.addGlobalErrorMessage("Error while resetting password.", "Please contact your administrator.");
}
return "/login.xhtml"; // TODO make configurable, get from LoginView?
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public boolean getPasswordResetStarted() {
return passwordResetStarted;
}
}