introduced account based configuration

This commit is contained in:
2018-08-21 18:07:51 +02:00
parent 077ab22438
commit 4559f20170
10 changed files with 444 additions and 193 deletions

View File

@ -7,6 +7,7 @@ import de.muehlencord.shared.account.business.mail.entity.MailException;
import de.muehlencord.shared.account.business.mail.boundary.MailService;
import de.muehlencord.shared.account.business.account.entity.AccountEntity;
import de.muehlencord.shared.account.business.account.entity.ApplicationRoleEntity;
import de.muehlencord.shared.account.business.config.entity.ConfigException;
import de.muehlencord.shared.account.util.SecurityUtil;
import java.io.Serializable;
import java.util.ArrayList;
@ -254,25 +255,33 @@ public class AccountControl implements Serializable {
}
public void addLoginError(AccountEntity account) {
Date now = new Date(); // TODO now in UTC
account.setLastFailedLogin(now);
account.setFailureCount(account.getFailureCount() + 1);
int maxFailedLogins = configService.getMaxFailedLogins();
if ((account.getFailureCount() >= maxFailedLogins) && (!account.getStatus().equals("LOCKED"))) { // TOD add status enum
// max failed logins reached, disabling user
LOGGER.info("Locking account " + account.getUsername() + " due to " + account.getFailureCount() + " failed logins");
account.setStatus(AccountStatus.BLOCKED.name());
try {
Date now = new Date(); // TODO now in UTC
account.setLastFailedLogin(now);
account.setFailureCount(account.getFailureCount() + 1);
int maxFailedLogins = Integer.parseInt(configService.getConfigValue("account.maxFailedLogins"));
if ((account.getFailureCount() >= maxFailedLogins) && (!account.getStatus().equals("LOCKED"))) { // TOD add status enum
// max failed logins reached, disabling user
LOGGER.info("Locking account " + account.getUsername() + " due to " + account.getFailureCount() + " failed logins");
account.setStatus(AccountStatus.BLOCKED.name());
}
// on a failed login request, disable password reset
account.setPasswordResetOngoing(false);
account.setPasswordResetHash(null);
account.setPasswordResetValidTo(null);
account.setLastUpdatedBy("system");
account.setLastUpdatedOn(now);
em.merge(account);
} catch (ConfigException ex) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug(ex.toString(), ex);
} else {
LOGGER.error(ex.toString());
}
}
// on a failed login request, disable password reset
account.setPasswordResetOngoing(false);
account.setPasswordResetHash(null);
account.setPasswordResetValidTo(null);
account.setLastUpdatedBy("system");
account.setLastUpdatedOn(now);
em.merge(account);
}
}

View File

@ -1,5 +1,6 @@
package de.muehlencord.shared.account.business.account.entity;
import de.muehlencord.shared.account.business.config.entity.ConfigEntity;
import java.io.Serializable;
import java.util.Date;
import java.util.List;
@ -55,7 +56,7 @@ import org.hibernate.annotations.Type;
public class AccountEntity implements Serializable, Account {
private static final long serialVersionUID = 6216991757526150935L;
@Id
@Basic(optional = false)
@NotNull
@ -148,6 +149,10 @@ public class AccountEntity implements Serializable, Account {
public AccountEntity(UUID id) {
this.id = id;
}
public AccountEntity (String name) {
this.username = name;
}
public AccountEntity(UUID id, String username, String emailaddress, String firstname, String lastname, String accountPassword, int failureCount, String status, boolean passwordResetOngoing, Date createdOn, String createdBy, Date lastUpdatedOn, String lastUpdatedBy) {
this.id = id;
@ -344,5 +349,5 @@ public class AccountEntity implements Serializable, Account {
public String toString() {
return "de.muehlencord.shared.account.entity.Account[ id=" + id + " ]";
}
}

View File

@ -1,12 +1,21 @@
package de.muehlencord.shared.account.business.config.boundary;
import de.muehlencord.shared.account.business.account.entity.AccountEntity;
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 java.io.Serializable;
import java.util.List;
import java.util.Optional;
import javax.annotation.PostConstruct;
import javax.ejb.Lock;
import javax.ejb.LockType;
import javax.ejb.Singleton;
import javax.ejb.Startup;
import javax.inject.Inject;
import javax.persistence.EntityManager;
import javax.persistence.Query;
import javax.transaction.Transactional;
/**
*
@ -21,46 +30,142 @@ public class ConfigService implements Serializable {
@Inject
EntityManager em;
private String storagePath = null;
private int maxFailedLogins = 5;
// private String storagePath = null;
// private int maxFailedLogins = 5;
@PostConstruct
public void init() {
ConfigEntity configEntity = em.find(ConfigEntity.class, "storage.path");
if (configEntity != null) {
this.storagePath = configEntity.getConfigValue();
}
configEntity = em.find(ConfigEntity.class, "account.maxFailedLogins");
if (configEntity != null) {
this.maxFailedLogins = Integer.parseInt(configEntity.getConfigValue());
// ConfigEntity configEntity = em.find(ConfigEntity.class, "storage.path");
// if (configEntity != null) {
// this.storagePath = configEntity.getConfigValue();
// }
// configEntity = em.find(ConfigEntity.class, "account.maxFailedLogins");
// if (configEntity != null) {
// this.maxFailedLogins = Integer.parseInt(configEntity.getConfigValue());
// }
}
/**
* returns global config key which is not assigned to any. If more than one
* value is defined for the given key, the key assigned to system is
* returned. If more than one key is defined but system key is not defined,
* an exception is thrown.
*
* @param configKey the key to return
* @return the configValue belonging to the given configKey
* @throws
* de.muehlencord.shared.account.business.config.entity.ConfigException if
* more than one value is defined for the given key but none of the values
* is defined for the system user
*/
public String getConfigValue(String configKey) throws ConfigException {
Query query = em.createNamedQuery("ConfigEntity.findByConfigKey");
query.setParameter("configKey", configKey);
List<ConfigEntity> configList = query.getResultList();
if ((configList == null) || (configList.isEmpty())) {
// key is not found in the database at all
return null;
} else if (configList.size() == 1) {
// exact one element found, return this one
return configList.get(0).getConfigValue();
} else {
// if more than one result found, return the one which is assigned to system if present
// if not present, throw exception
Optional<ConfigEntity> firstItem = configList.stream()
.filter(config -> config.getConfigPK().getConfigKeyAccount().getUsername().equals("system"))
.findFirst();
if (firstItem.isPresent()) {
return firstItem.get().getConfigValue();
} else {
throw new ConfigException("ConfigKey " + configKey + " not unique and system value not defined");
}
}
}
public String getConfigValue(String configKey) {
ConfigEntity configEntity = em.find(ConfigEntity.class, configKey);
return (configEntity == null ? null : configEntity.getConfigValue());
public String getConfigValue(String configKey, AccountEntity account) throws ConfigException {
Query query = em.createNamedQuery("ConfigEntity.findByConfigKeyAndAccount");
query.setParameter("configKey", configKey);
query.setParameter("configKeyAccount", account);
List<ConfigEntity> configList = query.getResultList();
if ((configList == null) || (configList.isEmpty())) {
// key is not found in the database at all
return null;
} else if (configList.size() == 1) {
// exact one element found, return this one
return configList.get(0).getConfigValue();
} else {
// more than one value must not happen - this is not possible per the defintion of the datamodel
throw new ConfigException("Cannot have more than one value for the the given key " + configKey + " and the given account " + account.getUsername());
}
}
@Transactional
@Lock(LockType.WRITE)
public boolean updateConfigValue(String configKey, String accountName, String configValue) {
if ((configKey == null) || (configKey.equals ("")) || (configValue == null) || (configValue.equals (""))) {
// null or empty key / values are not possible
return false;
}
AccountEntity account;
if (accountName == null) {
account = getAccount("system");
} else {
account = getAccount(accountName);
}
if (account == null) {
// must not happen we have no account - if accountName is null, then the system account has to be used
return false;
}
ConfigEntityPK pk = new ConfigEntityPK(configKey, account);
ConfigEntity currentEntity = em.find(ConfigEntity.class, pk);
if (currentEntity == null) {
currentEntity = new ConfigEntity(pk);
currentEntity.setConfigValue(configValue);
em.persist(currentEntity);
return true; // config item created - udpate performed
} else {
if ((currentEntity.getConfigValue() != null) && (currentEntity.getConfigValue().equals(configValue))) {
// value is the same - no update
return false;
} else {
currentEntity.setConfigValue(configValue);
em.merge(currentEntity);
return true;
}
}
}
private AccountEntity getAccount(String accountName) {
Query query = em.createNamedQuery("AccountEntity.findByUsername");
query.setParameter("username", accountName);
List<AccountEntity> accountList = query.getResultList();
if ((accountList == null) || (accountList.isEmpty())) {
return null;
} else {
return accountList.get(0);
}
}
/* *** getter *** */
/**
* FIXME remove, this is application specific
*
* @return
* @deprecated replace by getConfigValue ("storage.path")
*/
@Deprecated
public String getStoragePath() {
return storagePath;
}
// @Deprecated
// public String getStoragePath() {
// return storagePath;
// }
/**
* // TODO move to accountControl
*
* @return
* @deprecated replace by getConfigValue ("account.maxFailedLogins")
*/
@Deprecated
public int getMaxFailedLogins() {
return maxFailedLogins;
}
// @Deprecated
// public int getMaxFailedLogins() {
// return maxFailedLogins;
// }
}

View File

@ -1,137 +1,124 @@
package de.muehlencord.shared.account.business.config.entity;
import de.muehlencord.shared.account.business.account.entity.AccountEntity;
import java.io.Serializable;
import javax.persistence.Basic;
import javax.persistence.Cacheable;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
import javax.persistence.QueryHint;
import javax.persistence.Table;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import javax.xml.bind.annotation.XmlRootElement;
import org.hibernate.annotations.Cache;
import org.hibernate.annotations.CacheConcurrencyStrategy;
/**
*
* @author joern.muehlencord
*/
@Entity
@Table(name = "config")
@XmlRootElement
@Cacheable(true)
@Cache(usage = CacheConcurrencyStrategy.TRANSACTIONAL, region = "Configuration")
@NamedQueries({
@NamedQuery(name = "ConfigEntity.findAll", query = "SELECT c FROM ConfigEntity c ORDER BY c.configKey",
hints = {
@QueryHint(name = "org.hibernate.cacheable", value = "true"),
@QueryHint(name = "org.hibernate.cacheRegion", value = "Queries")}),
@NamedQuery(name = "ConfigEntity.findByConfigKey", query = "SELECT c FROM ConfigEntity c WHERE c.configKey = :configKey",
hints = {
@QueryHint(name = "org.hibernate.cacheable", value = "true"),
@QueryHint(name = "org.hibernate.cacheRegion", value = "Queries")}),
@NamedQuery(name = "ConfigEntity.findByConfigValue", query = "SELECT c FROM ConfigEntity c WHERE c.configValue = :configValue",
hints = {
@QueryHint(name = "org.hibernate.cacheable", value = "true"),
@QueryHint(name = "org.hibernate.cacheRegion", value = "Queries")})
})
public class ConfigEntity implements Serializable {
private static final long serialVersionUID = -2013982316933782223L;
@Id
@Basic(optional = false)
@NotNull
@Size(min = 1, max = 100)
@Column(name = "config_key")
private String configKey;
@Basic(optional = true)
@Size(min = 1, max = 200)
@Column(name = "config_key_group")
private String configKeyGroup;
@JoinColumn(name = "config_key_account", referencedColumnName = "id")
@ManyToOne(optional = true)
private AccountEntity configKeyAccount;
@Size(max = 200)
@Column(name = "config_value")
private String configValue;
public ConfigEntity() {
}
public ConfigEntity(String configKey) {
this.configKey = configKey;
}
public ConfigEntity(String configKey, String configValue) {
this.configKey = configKey;
this.configValue = configValue;
}
public String getConfigKey() {
return configKey;
}
public void setConfigKey(String configKey) {
this.configKey = configKey;
}
public String getConfigKeyGroup() {
return configKeyGroup;
}
public void setConfigKeyGroup(String configKeyGroup) {
this.configKeyGroup = configKeyGroup;
}
public AccountEntity getConfigKeyAccount() {
return configKeyAccount;
}
public void setConfigKeyAccount(AccountEntity configKeyAccount) {
this.configKeyAccount = configKeyAccount;
}
public String getConfigValue() {
return configValue;
}
public void setConfigValue(String configValue) {
this.configValue = configValue;
}
@Override
public int hashCode() {
int hash = 0;
hash += (configKey != null ? configKey.hashCode() : 0);
return hash;
}
@Override
public boolean equals(Object object) {
// TODO: Warning - this method won't work in the case the id fields are not set
if (!(object instanceof ConfigEntity)) {
return false;
}
ConfigEntity other = (ConfigEntity) object;
if ((this.configKey == null && other.configKey != null) || (this.configKey != null && !this.configKey.equals(other.configKey))) {
return false;
}
return true;
}
@Override
public String toString() {
return "de.muehlencord.shared.account.entity.Config[ configKey=" + configKey + " ]";
}
}
/*
* 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.entity;
import de.muehlencord.shared.account.business.account.entity.AccountEntity;
import java.io.Serializable;
import javax.persistence.Cacheable;
import javax.persistence.Column;
import javax.persistence.EmbeddedId;
import javax.persistence.Entity;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
import javax.persistence.QueryHint;
import javax.persistence.Table;
import javax.validation.constraints.Size;
import javax.xml.bind.annotation.XmlRootElement;
import org.hibernate.annotations.Cache;
import org.hibernate.annotations.CacheConcurrencyStrategy;
/**
*
* @author Joern Muehlencord <joern at muehlencord.de>
*/
@Entity
@Table(name = "config")
@XmlRootElement
@Cacheable(true)
@Cache(usage = CacheConcurrencyStrategy.TRANSACTIONAL, region = "Configuration")
@NamedQueries({
@NamedQuery(name = "ConfigEntity.findAll", query = "SELECT c FROM ConfigEntity c ORDER BY c.configPK.configKey",
hints = {
@QueryHint(name = "org.hibernate.cacheable", value = "true"),
@QueryHint(name = "org.hibernate.cacheRegion", value = "Queries")}),
@NamedQuery(name = "ConfigEntity.findByConfigKey", query = "SELECT c FROM ConfigEntity c WHERE c.configPK.configKey = :configKey",
hints = {
@QueryHint(name = "org.hibernate.cacheable", value = "true"),
@QueryHint(name = "org.hibernate.cacheRegion", value = "Queries")}),
@NamedQuery(name = "ConfigEntity.findByConfigKeyAndAccount", query = "SELECT c FROM ConfigEntity c WHERE c.configPK.configKey = :configKey AND c.configPK.configKeyAccount = :account",
hints = {
@QueryHint(name = "org.hibernate.cacheable", value = "true"),
@QueryHint(name = "org.hibernate.cacheRegion", value = "Queries")}),
@NamedQuery(name = "ConfigEntity.findByConfigValue", query = "SELECT c FROM ConfigEntity c WHERE c.configValue = :configValue",
hints = {
@QueryHint(name = "org.hibernate.cacheable", value = "true"),
@QueryHint(name = "org.hibernate.cacheRegion", value = "Queries")})
})
public class ConfigEntity implements Serializable {
private static final long serialVersionUID = -2013982316933782223L;
@EmbeddedId
protected ConfigEntityPK configPK;
@Size(max = 200)
@Column(name = "config_value")
private String configValue;
@Size(max = 200)
@Column(name = "config_key_group")
private String configKeyGroup;
public ConfigEntity() {
}
public ConfigEntity(String configKey, AccountEntity account) {
this.configPK = new ConfigEntityPK(configKey, account);
}
public ConfigEntity(ConfigEntityPK configPK) {
this.configPK = configPK;
}
public ConfigEntityPK getConfigPK() {
return configPK;
}
public void setConfigPK(ConfigEntityPK configPK) {
this.configPK = configPK;
}
public String getConfigValue() {
return configValue;
}
public void setConfigValue(String configValue) {
this.configValue = configValue;
}
public String getConfigKeyGroup() {
return configKeyGroup;
}
public void setConfigKeyGroup(String configKeyGroup) {
this.configKeyGroup = configKeyGroup;
}
@Override
public int hashCode() {
int hash = 0;
hash += (configPK != null ? configPK.hashCode() : 0);
return hash;
}
@Override
public boolean equals(Object object) {
// TODO: Warning - this method won't work in the case the id fields are not set
if (!(object instanceof ConfigEntity)) {
return false;
}
ConfigEntity other = (ConfigEntity) object;
if ((this.configPK == null && other.configPK != null) || (this.configPK != null && !this.configPK.equals(other.configPK))) {
return false;
}
return true;
}
@Override
public String toString() {
return "de.muehlencord.shared.account.business.config.entity.Config[ configPK=" + configPK + " ]";
}
}

View File

@ -0,0 +1,89 @@
/*
* 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.entity;
import de.muehlencord.shared.account.business.account.entity.AccountEntity;
import java.io.Serializable;
import javax.persistence.Basic;
import javax.persistence.Column;
import javax.persistence.Embeddable;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
/**
*
* @author Joern Muehlencord <joern at muehlencord.de>
*/
@Embeddable
public class ConfigEntityPK implements Serializable {
private static final long serialVersionUID = 8133795368429249008L;
@Basic(optional = false)
@NotNull
@Size(min = 1, max = 100)
@Column(name = "config_key")
private String configKey;
@JoinColumn(name = "config_key_account", referencedColumnName = "id")
@ManyToOne(optional = false)
private AccountEntity configKeyAccount;
public ConfigEntityPK() {
}
public ConfigEntityPK(String configKey, AccountEntity configKeyAccount) {
this.configKey = configKey;
this.configKeyAccount = configKeyAccount;
}
public String getConfigKey() {
return configKey;
}
public void setConfigKey(String configKey) {
this.configKey = configKey;
}
public AccountEntity getConfigKeyAccount() {
return configKeyAccount;
}
public void setConfigKeyAccount(AccountEntity configKeyAccount) {
this.configKeyAccount = configKeyAccount;
}
@Override
public int hashCode() {
int hash = 0;
hash += (configKey != null ? configKey.hashCode() : 0);
hash += (configKeyAccount != null ? configKeyAccount.hashCode() : 0);
return hash;
}
@Override
public boolean equals(Object object) {
// TODO: Warning - this method won't work in the case the id fields are not set
if (!(object instanceof ConfigEntityPK)) {
return false;
}
ConfigEntityPK other = (ConfigEntityPK) object;
if ((this.configKey == null && other.configKey != null) || (this.configKey != null && !this.configKey.equals(other.configKey))) {
return false;
}
if ((this.configKeyAccount == null && other.configKeyAccount != null) || (this.configKeyAccount != null && !this.configKeyAccount.equals(other.configKeyAccount))) {
return false;
}
return true;
}
@Override
public String toString() {
return "de.muehlencord.shared.account.business.config.entity.ConfigPK[ configKey=" + configKey + ", configKeyAccount=" + configKeyAccount + " ]";
}
}

View File

@ -0,0 +1,43 @@
/*
* 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.entity;
/**
*
* @author Joern Muehlencord <joern at muehlencord.de>
*/
public class ConfigException extends Exception {
private static final long serialVersionUID = 7246584814637280123L;
/**
* Creates a new instance of <code>ConfigException</code> without detail
* message.
*/
public ConfigException() {
}
/**
* Constructs an instance of <code>ConfigException</code> with the specified
* detail message.
*
* @param msg the detail message.
*/
public ConfigException(String msg) {
super(msg);
}
/**
* Constructs an instance of <code>ConfigException</code> with the specified
* detail message and root cause.
*
* @param msg the detail message.
* @param th the root cause
*/
public ConfigException(String msg, Throwable th) {
super(msg, th);
}
}