diff --git a/li.strolch.privilege/src/main/java/li/strolch/privilege/handler/BaseLdapPrivilegeHandler.java b/li.strolch.privilege/src/main/java/li/strolch/privilege/handler/BaseLdapPrivilegeHandler.java index 5110577a1..0e2a5b18f 100644 --- a/li.strolch.privilege/src/main/java/li/strolch/privilege/handler/BaseLdapPrivilegeHandler.java +++ b/li.strolch.privilege/src/main/java/li/strolch/privilege/handler/BaseLdapPrivilegeHandler.java @@ -10,7 +10,6 @@ import java.util.Map; import java.util.Set; import li.strolch.privilege.base.AccessDeniedException; -import li.strolch.privilege.base.InvalidCredentialsException; import li.strolch.privilege.model.UserState; import li.strolch.privilege.model.internal.User; import li.strolch.privilege.model.internal.UserHistory; @@ -28,11 +27,12 @@ public abstract class BaseLdapPrivilegeHandler extends DefaultPrivilegeHandler { @Override public synchronized void initialize(Map parameterMap, EncryptionHandler encryptionHandler, - PersistenceHandler persistenceHandler, UserChallengeHandler userChallengeHandler, - SingleSignOnHandler ssoHandler, Map> policyMap) { + PasswordStrengthHandler passwordStrengthHandler, PersistenceHandler persistenceHandler, + UserChallengeHandler userChallengeHandler, SingleSignOnHandler ssoHandler, + Map> policyMap) { - super.initialize(parameterMap, encryptionHandler, persistenceHandler, userChallengeHandler, ssoHandler, - policyMap); + super.initialize(parameterMap, encryptionHandler, passwordStrengthHandler, persistenceHandler, + userChallengeHandler, ssoHandler, policyMap); this.providerUrl = parameterMap.get("providerUrl"); this.searchBase = parameterMap.get("searchBase"); @@ -42,7 +42,7 @@ public abstract class BaseLdapPrivilegeHandler extends DefaultPrivilegeHandler { @Override protected synchronized User checkCredentialsAndUserState(String username, char[] password) - throws InvalidCredentialsException, AccessDeniedException { + throws AccessDeniedException { // first see if this is a local user User internalUser = this.persistenceHandler.getUser(username); @@ -148,7 +148,7 @@ public abstract class BaseLdapPrivilegeHandler extends DefaultPrivilegeHandler { protected String validateLdapUsername(String username, Attributes attrs) throws NamingException { Attribute sAMAccountName = attrs.get("sAMAccountName"); - if (sAMAccountName == null || !username.toLowerCase().equals(sAMAccountName.get().toString().toLowerCase())) + if (sAMAccountName == null || !username.equalsIgnoreCase(sAMAccountName.get().toString())) throw new AccessDeniedException( "Could not login with user: " + username + this.domain + " on Ldap: Wrong LDAP Data"); diff --git a/li.strolch.privilege/src/main/java/li/strolch/privilege/handler/BasicPasswordStrengthHandler.java b/li.strolch.privilege/src/main/java/li/strolch/privilege/handler/BasicPasswordStrengthHandler.java new file mode 100644 index 000000000..5a4cf4cf7 --- /dev/null +++ b/li.strolch.privilege/src/main/java/li/strolch/privilege/handler/BasicPasswordStrengthHandler.java @@ -0,0 +1,101 @@ +package li.strolch.privilege.handler; + +import static java.lang.Boolean.parseBoolean; +import static java.lang.Integer.parseInt; +import static li.strolch.privilege.i18n.PrivilegeMessages.getString; + +import java.text.MessageFormat; +import java.util.Locale; +import java.util.Map; + +public class BasicPasswordStrengthHandler implements PasswordStrengthHandler { + + protected int minLength; + protected int maxLength; + protected boolean needsNumbers; + protected boolean needsLowerCase; + protected boolean needsUpperCase; + protected boolean needsSpecialChars; + + @Override + public void initialize(Map parameterMap) { + this.minLength = parseInt(parameterMap.getOrDefault("minLength", "8")); + this.maxLength = parseInt(parameterMap.getOrDefault("maxLength", String.valueOf(1024))); + this.needsNumbers = parseBoolean(parameterMap.getOrDefault("needsNumbers", "true")); + this.needsLowerCase = parseBoolean(parameterMap.getOrDefault("needsLowerCase", "true")); + this.needsUpperCase = parseBoolean(parameterMap.getOrDefault("needsUpperCase", "true")); + this.needsSpecialChars = parseBoolean(parameterMap.getOrDefault("needsSpecialChars", "false")); + + if (this.minLength < 8) + throw new IllegalStateException("minLength can not be less than 8"); + if (this.maxLength > 1024) + throw new IllegalStateException("maxLength can not be greater than 1024"); + } + + @Override + public String getDescription(Locale locale) { + String description; + + if (this.maxLength < 100) + description = MessageFormat + .format(getString(locale, "Privilege.passwordLengthBetween"), this.minLength, this.maxLength); + else + description = MessageFormat.format(getString(locale, "Privilege.passwordLengthAtLeast"), this.minLength); + + if (this.needsNumbers) + description += ", " + getString(locale, "Privilege.passwordMustContainNumbers"); + + if (this.needsLowerCase && this.needsUpperCase) + description += ", " + getString(locale, "Privilege.passwordMustContainLowerAndUpperCase"); + else if (this.needsLowerCase) + description += ", " + getString(locale, "Privilege.passwordMustContainLowerCase"); + else + description += ", " + getString(locale, "Privilege.passwordMustContainUpperCase"); + + if (this.needsSpecialChars) + description += ", " + getString(locale, "Privilege.passwordMustContainSpecialCharacters"); + + return description; + } + + @Override + public boolean validateStrength(char[] password) { + if (password.length < this.minLength || password.length > this.maxLength) + return false; + + boolean numbersOk = !this.needsNumbers; + boolean lowerCaseOk = !this.needsLowerCase; + boolean upperCaseOk = !this.needsUpperCase; + boolean specialCharsOk = !this.needsSpecialChars; + + for (char c : password) { + + if (numbersOk && lowerCaseOk && upperCaseOk && specialCharsOk) + return true; + + if (!numbersOk && Character.isDigit(c)) { + numbersOk = true; + continue; + } + + if (!lowerCaseOk && Character.isLowerCase(c)) { + lowerCaseOk = true; + continue; + } + + if (!upperCaseOk && Character.isUpperCase(c)) { + upperCaseOk = true; + continue; + } + + if (!specialCharsOk && isSpecial(c)) + specialCharsOk = true; + } + + return numbersOk && lowerCaseOk && upperCaseOk && specialCharsOk; + } + + public static boolean isSpecial(char c) { + return !(Character.isDigit(c) || Character.isLowerCase(c) || Character.isUpperCase(c)); + } +} diff --git a/li.strolch.privilege/src/main/java/li/strolch/privilege/handler/DefaultPrivilegeHandler.java b/li.strolch.privilege/src/main/java/li/strolch/privilege/handler/DefaultPrivilegeHandler.java index 00a2316eb..8fdd44081 100644 --- a/li.strolch.privilege/src/main/java/li/strolch/privilege/handler/DefaultPrivilegeHandler.java +++ b/li.strolch.privilege/src/main/java/li/strolch/privilege/handler/DefaultPrivilegeHandler.java @@ -84,6 +84,11 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { */ protected EncryptionHandler encryptionHandler; + /** + * The password strength handler is used for validating the strength of a password when being set + */ + protected PasswordStrengthHandler passwordStrengthHandler; + /** * The Single Sign On Handler */ @@ -399,7 +404,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { if (password != null) { // validate password meets basic requirements - validatePassword(password); + validatePassword(certificate.getLocale(), password); // get new salt for user salt = this.encryptionHandler.nextSalt(); @@ -464,7 +469,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { if (password != null) { // validate password meets basic requirements - validatePassword(password); + validatePassword(certificate.getLocale(), password); // get new salt for user salt = this.encryptionHandler.nextSalt(); @@ -779,7 +784,7 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { if (password != null) { // validate password meets basic requirements - validatePassword(password); + validatePassword(certificate.getLocale(), password); // get new salt for user salt = this.encryptionHandler.nextSalt(); @@ -1484,7 +1489,8 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { throws InvalidCredentialsException, AccessDeniedException { // and validate the password - validatePassword(password); + if (password == null || password.length < 3) + throw new InvalidCredentialsException("Password is invalid!"); // get user object User user = this.persistenceHandler.getUser(username); @@ -1755,21 +1761,10 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { return privilegeContext; } - /** - * This simple implementation validates that the password is not null, and that the password string is not empty - * - * @see li.strolch.privilege.handler.PrivilegeHandler#validatePassword(char[]) - */ @Override - public void validatePassword(char[] password) throws PrivilegeException { - - if (password == null || password.length == 0) { - throw new PrivilegeModelException("A password may not be empty!"); //$NON-NLS-1$ - } - - if (password.length < 3) { - throw new PrivilegeModelException("The given password is shorter than 3 characters"); //$NON-NLS-1$ - } + public void validatePassword(Locale locale, char[] password) throws PrivilegeException { + if (!this.passwordStrengthHandler.validateStrength(password)) + throw new PrivilegeException(this.passwordStrengthHandler.getDescription(locale)); } @Override @@ -1811,6 +1806,8 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { * a map containing configuration properties * @param encryptionHandler * the {@link EncryptionHandler} instance for this {@link PrivilegeHandler} + * @param passwordStrengthHandler + * the {@link PasswordStrengthHandler} instance for this {@link PrivilegeHandler} * @param persistenceHandler * the {@link PersistenceHandler} instance for this {@link PrivilegeHandler} * @param userChallengeHandler @@ -1824,14 +1821,16 @@ public class DefaultPrivilegeHandler implements PrivilegeHandler { * if the this method is called multiple times or an initialization exception occurs */ public synchronized void initialize(Map parameterMap, EncryptionHandler encryptionHandler, - PersistenceHandler persistenceHandler, UserChallengeHandler userChallengeHandler, - SingleSignOnHandler ssoHandler, Map> policyMap) { + PasswordStrengthHandler passwordStrengthHandler, PersistenceHandler persistenceHandler, + UserChallengeHandler userChallengeHandler, SingleSignOnHandler ssoHandler, + Map> policyMap) { if (this.initialized) throw new PrivilegeModelException("Already initialized!"); //$NON-NLS-1$ this.policyMap = policyMap; this.encryptionHandler = encryptionHandler; + this.passwordStrengthHandler = passwordStrengthHandler; this.persistenceHandler = persistenceHandler; this.userChallengeHandler = userChallengeHandler; this.ssoHandler = ssoHandler; diff --git a/li.strolch.privilege/src/main/java/li/strolch/privilege/handler/JsonConfigLdapPrivilegeHandler.java b/li.strolch.privilege/src/main/java/li/strolch/privilege/handler/JsonConfigLdapPrivilegeHandler.java index 311761889..36d6e2a8c 100644 --- a/li.strolch.privilege/src/main/java/li/strolch/privilege/handler/JsonConfigLdapPrivilegeHandler.java +++ b/li.strolch.privilege/src/main/java/li/strolch/privilege/handler/JsonConfigLdapPrivilegeHandler.java @@ -30,11 +30,12 @@ public class JsonConfigLdapPrivilegeHandler extends BaseLdapPrivilegeHandler { @Override public synchronized void initialize(Map parameterMap, EncryptionHandler encryptionHandler, - PersistenceHandler persistenceHandler, UserChallengeHandler userChallengeHandler, - SingleSignOnHandler ssoHandler, Map> policyMap) { + PasswordStrengthHandler passwordStrengthHandler, PersistenceHandler persistenceHandler, + UserChallengeHandler userChallengeHandler, SingleSignOnHandler ssoHandler, + Map> policyMap) { - super.initialize(parameterMap, encryptionHandler, persistenceHandler, userChallengeHandler, ssoHandler, - policyMap); + super.initialize(parameterMap, encryptionHandler, passwordStrengthHandler, persistenceHandler, + userChallengeHandler, ssoHandler, policyMap); this.realm = parameterMap.get(REALM); DBC.PRE.assertNotEmpty("realm must be set!", realm); diff --git a/li.strolch.privilege/src/main/java/li/strolch/privilege/handler/PasswordStrengthHandler.java b/li.strolch.privilege/src/main/java/li/strolch/privilege/handler/PasswordStrengthHandler.java new file mode 100644 index 000000000..5ae4e146b --- /dev/null +++ b/li.strolch.privilege/src/main/java/li/strolch/privilege/handler/PasswordStrengthHandler.java @@ -0,0 +1,38 @@ +package li.strolch.privilege.handler; + +import java.util.Locale; +import java.util.Map; + +/** + * The password strength handler allows to plug-in different algorithms for validating the strength of a password + */ +public interface PasswordStrengthHandler { + + /** + * Initialize the concrete {@link PasswordStrengthHandler}. The passed parameter map contains any configuration the + * concrete {@link PasswordStrengthHandler} might need + * + * @param parameterMap + * a map containing configuration properties + */ + void initialize(Map parameterMap); + + /** + * Returns a description what a password must contain in order to be regarded as strong for this concrete + * implementation + * + * @return a description of a strong password + * @param locale + */ + String getDescription(Locale locale); + + /** + * Performs the validation of the given password + * + * @param password + * the password to validate + * + * @return true if the password meets the criteria for a strong password + */ + boolean validateStrength(char[] password); +} diff --git a/li.strolch.privilege/src/main/java/li/strolch/privilege/handler/PrivilegeHandler.java b/li.strolch.privilege/src/main/java/li/strolch/privilege/handler/PrivilegeHandler.java index 265c60a58..9897d345d 100644 --- a/li.strolch.privilege/src/main/java/li/strolch/privilege/handler/PrivilegeHandler.java +++ b/li.strolch.privilege/src/main/java/li/strolch/privilege/handler/PrivilegeHandler.java @@ -775,16 +775,9 @@ public interface PrivilegeHandler { PrivilegeContext validate(Certificate certificate, String source) throws PrivilegeException; /** - * Validate that the given password meets certain requirements. What these requirements are is a decision made by - * the concrete implementation - * - * @param password - * the password to be validated to meet certain requirements - * - * @throws PrivilegeException - * if the password does not implement the requirement of the concrete implementation + * @see li.strolch.privilege.handler.PasswordStrengthHandler#validateStrength(char[]) */ - void validatePassword(char[] password) throws PrivilegeException; + void validatePassword(Locale locale, char[] password) throws PrivilegeException; /** *

diff --git a/li.strolch.privilege/src/main/java/li/strolch/privilege/handler/SimpleLdapPrivilegeHandler.java b/li.strolch.privilege/src/main/java/li/strolch/privilege/handler/SimpleLdapPrivilegeHandler.java index 269ee3f86..4f41254fd 100644 --- a/li.strolch.privilege/src/main/java/li/strolch/privilege/handler/SimpleLdapPrivilegeHandler.java +++ b/li.strolch.privilege/src/main/java/li/strolch/privilege/handler/SimpleLdapPrivilegeHandler.java @@ -26,11 +26,12 @@ public class SimpleLdapPrivilegeHandler extends BaseLdapPrivilegeHandler { @Override public synchronized void initialize(Map parameterMap, EncryptionHandler encryptionHandler, - PersistenceHandler persistenceHandler, UserChallengeHandler userChallengeHandler, - SingleSignOnHandler ssoHandler, Map> policyMap) { + PasswordStrengthHandler passwordStrengthHandler, PersistenceHandler persistenceHandler, + UserChallengeHandler userChallengeHandler, SingleSignOnHandler ssoHandler, + Map> policyMap) { - super.initialize(parameterMap, encryptionHandler, persistenceHandler, userChallengeHandler, ssoHandler, - policyMap); + super.initialize(parameterMap, encryptionHandler, passwordStrengthHandler, persistenceHandler, + userChallengeHandler, ssoHandler, policyMap); this.organisation = parameterMap.getOrDefault(ORGANISATION, ""); this.location = parameterMap.getOrDefault(LOCATION, ""); diff --git a/li.strolch.privilege/src/main/java/li/strolch/privilege/handler/SimplePasswordStrengthHandler.java b/li.strolch.privilege/src/main/java/li/strolch/privilege/handler/SimplePasswordStrengthHandler.java new file mode 100644 index 000000000..3343eedcb --- /dev/null +++ b/li.strolch.privilege/src/main/java/li/strolch/privilege/handler/SimplePasswordStrengthHandler.java @@ -0,0 +1,22 @@ +package li.strolch.privilege.handler; + +import java.util.Locale; +import java.util.Map; + +public class SimplePasswordStrengthHandler implements PasswordStrengthHandler { + + @Override + public void initialize(Map parameterMap) { + // do nothing + } + + @Override + public String getDescription(Locale locale) { + return "Password must be at least 3 characters long"; + } + + @Override + public boolean validateStrength(char[] password) { + return password != null && password.length >= 3; + } +} diff --git a/li.strolch.privilege/src/main/java/li/strolch/privilege/helper/PrivilegeInitializationHelper.java b/li.strolch.privilege/src/main/java/li/strolch/privilege/helper/PrivilegeInitializationHelper.java index ddb36cd39..20d4bb6e3 100644 --- a/li.strolch.privilege/src/main/java/li/strolch/privilege/helper/PrivilegeInitializationHelper.java +++ b/li.strolch.privilege/src/main/java/li/strolch/privilege/helper/PrivilegeInitializationHelper.java @@ -15,6 +15,8 @@ */ package li.strolch.privilege.helper; +import static li.strolch.utils.helper.StringHelper.isEmpty; + import java.io.File; import java.io.InputStream; import java.nio.file.Files; @@ -28,6 +30,8 @@ import li.strolch.privilege.policy.PrivilegePolicy; import li.strolch.privilege.xml.PrivilegeConfigSaxReader; import li.strolch.utils.helper.ClassHelper; import li.strolch.utils.helper.XmlHelper; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * This class implements the initializing of the {@link PrivilegeHandler} by loading an XML file containing the @@ -37,6 +41,8 @@ import li.strolch.utils.helper.XmlHelper; */ public class PrivilegeInitializationHelper { + private static final Logger logger = LoggerFactory.getLogger(PrivilegeInitializationHelper.class); + /** * Initializes the {@link DefaultPrivilegeHandler} from the configuration file * @@ -108,6 +114,23 @@ public class PrivilegeInitializationHelper { throw new PrivilegeException(msg, e); } + // initialize password strength handler + String passwordStrengthHandlerClassName = containerModel.getPasswordStrengthHandlerClassName(); + if (isEmpty(passwordStrengthHandlerClassName)) { + logger.info("No PasswordStrengthHandler defined, using " + SimplePasswordStrengthHandler.class.getName()); + passwordStrengthHandlerClassName = SimplePasswordStrengthHandler.class.getName(); + } + PasswordStrengthHandler passwordStrengthHandler = ClassHelper + .instantiateClass(passwordStrengthHandlerClassName); + parameterMap = containerModel.getPasswordStrengthHandlerParameterMap(); + try { + passwordStrengthHandler.initialize(parameterMap); + } catch (Exception e) { + String msg = "PasswordStrengthHandler {0} could not be initialized"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, passwordStrengthHandlerClassName); + throw new PrivilegeException(msg, e); + } + // initialize persistence handler String persistenceHandlerClassName = containerModel.getPersistenceHandlerClassName(); PersistenceHandler persistenceHandler = ClassHelper.instantiateClass(persistenceHandlerClassName); @@ -164,9 +187,8 @@ public class PrivilegeInitializationHelper { Map> policyMap = containerModel.getPolicies(); try { - privilegeHandler - .initialize(parameterMap, encryptionHandler, persistenceHandler, challengeHandler, ssoHandler, - policyMap); + privilegeHandler.initialize(parameterMap, encryptionHandler, passwordStrengthHandler, persistenceHandler, + challengeHandler, ssoHandler, policyMap); } catch (Exception e) { String msg = "PrivilegeHandler {0} could not be initialized"; //$NON-NLS-1$ msg = MessageFormat.format(msg, privilegeHandler.getClass().getName()); diff --git a/li.strolch.privilege/src/main/java/li/strolch/privilege/helper/XmlConstants.java b/li.strolch.privilege/src/main/java/li/strolch/privilege/helper/XmlConstants.java index c6252492c..a12ac9e8e 100644 --- a/li.strolch.privilege/src/main/java/li/strolch/privilege/helper/XmlConstants.java +++ b/li.strolch.privilege/src/main/java/li/strolch/privilege/helper/XmlConstants.java @@ -69,7 +69,12 @@ public class XmlConstants { public static final String XML_HANDLER_ENCRYPTION = "EncryptionHandler"; /** - * XML_HANDLER_ENCRYPTION = "EncryptionHandler" : + * XML_HANDLER_ENCRYPTION = "PasswordStrengthHandler" : + */ + public static final String XML_HANDLER_PASSWORD_STRENGTH = "PasswordStrengthHandler"; + + /** + * XML_HANDLER_ENCRYPTION = "SsoHandler" : */ public static final String XML_HANDLER_SSO = "SsoHandler"; diff --git a/li.strolch.privilege/src/main/java/li/strolch/privilege/i18n/PrivilegeMessages.java b/li.strolch.privilege/src/main/java/li/strolch/privilege/i18n/PrivilegeMessages.java index bf1bed389..f5be52bc1 100644 --- a/li.strolch.privilege/src/main/java/li/strolch/privilege/i18n/PrivilegeMessages.java +++ b/li.strolch.privilege/src/main/java/li/strolch/privilege/i18n/PrivilegeMessages.java @@ -15,6 +15,7 @@ */ package li.strolch.privilege.i18n; +import java.util.Locale; import java.util.MissingResourceException; import java.util.ResourceBundle; @@ -29,6 +30,14 @@ public class PrivilegeMessages { private PrivilegeMessages() { } + public static String getString(Locale locale, String key) { + try { + return ResourceBundle.getBundle(BUNDLE_NAME, locale).getString(key); + } catch (MissingResourceException e) { + return '!' + key + '!'; + } + } + public static String getString(String key) { try { return RESOURCE_BUNDLE.getString(key); diff --git a/li.strolch.privilege/src/main/java/li/strolch/privilege/model/internal/PrivilegeContainerModel.java b/li.strolch.privilege/src/main/java/li/strolch/privilege/model/internal/PrivilegeContainerModel.java index dacf7265a..479e81342 100644 --- a/li.strolch.privilege/src/main/java/li/strolch/privilege/model/internal/PrivilegeContainerModel.java +++ b/li.strolch.privilege/src/main/java/li/strolch/privilege/model/internal/PrivilegeContainerModel.java @@ -37,12 +37,14 @@ import li.strolch.privilege.policy.PrivilegePolicy; public class PrivilegeContainerModel { private String encryptionHandlerClassName; + private String passwordStrengthHandlerClassName; private String persistenceHandlerClassName; private String userChallengeHandlerClassName; private String ssoHandlerClassName; private String privilegeHandlerClassName; private Map encryptionHandlerParameterMap; + private Map passwordStrengthHandlerParameterMap; private Map persistenceHandlerParameterMap; private Map challengeHandlerParameterMap; private Map ssoHandlerParameterMap; @@ -50,11 +52,12 @@ public class PrivilegeContainerModel { private Map parameterMap; - private Map> policies; + private final Map> policies; public PrivilegeContainerModel() { this.policies = new HashMap<>(); this.encryptionHandlerParameterMap = new HashMap<>(); + this.passwordStrengthHandlerParameterMap = new HashMap<>(); this.persistenceHandlerParameterMap = new HashMap<>(); this.challengeHandlerParameterMap = new HashMap<>(); this.ssoHandlerParameterMap = new HashMap<>(); @@ -77,6 +80,22 @@ public class PrivilegeContainerModel { this.encryptionHandlerClassName = encryptionHandlerClassName; } + public String getPasswordStrengthHandlerClassName() { + return this.passwordStrengthHandlerClassName; + } + + public void setPasswordStrengthHandlerClassName(String passwordStrengthHandlerClassName) { + this.passwordStrengthHandlerClassName = passwordStrengthHandlerClassName; + } + + public Map getPasswordStrengthHandlerParameterMap() { + return this.passwordStrengthHandlerParameterMap; + } + + public void setPasswordStrengthHandlerParameterMap(Map passwordStrengthHandlerParameterMap) { + this.passwordStrengthHandlerParameterMap = passwordStrengthHandlerParameterMap; + } + public Map getEncryptionHandlerParameterMap() { return this.encryptionHandlerParameterMap; } @@ -192,6 +211,10 @@ public class PrivilegeContainerModel { builder.append(this.encryptionHandlerClassName); builder.append(", encryptionHandlerParameterMap="); builder.append(this.encryptionHandlerParameterMap.size()); + builder.append(", passwordStrengthHandlerClassName="); + builder.append(this.passwordStrengthHandlerClassName); + builder.append(", passwordStrengthHandlerParameterMap="); + builder.append(this.passwordStrengthHandlerParameterMap); builder.append(", persistenceHandlerClassName="); builder.append(this.persistenceHandlerClassName); builder.append(", persistenceHandlerParameterMap="); diff --git a/li.strolch.privilege/src/main/java/li/strolch/privilege/xml/PrivilegeConfigSaxReader.java b/li.strolch.privilege/src/main/java/li/strolch/privilege/xml/PrivilegeConfigSaxReader.java index f916a8ade..9ee821f67 100644 --- a/li.strolch.privilege/src/main/java/li/strolch/privilege/xml/PrivilegeConfigSaxReader.java +++ b/li.strolch.privilege/src/main/java/li/strolch/privilege/xml/PrivilegeConfigSaxReader.java @@ -31,9 +31,8 @@ import org.xml.sax.helpers.DefaultHandler; */ public class PrivilegeConfigSaxReader extends DefaultHandler { - private Deque buildersStack = new ArrayDeque<>(); - - private PrivilegeContainerModel containerModel; + private final Deque buildersStack = new ArrayDeque<>(); + private final PrivilegeContainerModel containerModel; public PrivilegeConfigSaxReader(PrivilegeContainerModel containerModel) { this.containerModel = containerModel; @@ -46,12 +45,16 @@ public class PrivilegeConfigSaxReader extends DefaultHandler { @Override public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { - if (qName.equals(XmlConstants.XML_CONTAINER)) { + switch (qName) { + case XmlConstants.XML_CONTAINER: this.buildersStack.push(new ContainerParser()); - } else if (qName.equals(XmlConstants.XML_PARAMETERS)) { + break; + case XmlConstants.XML_PARAMETERS: this.buildersStack.push(new ParametersParser()); - } else if (qName.equals(XmlConstants.XML_POLICIES)) { + break; + case XmlConstants.XML_POLICIES: this.buildersStack.push(new PoliciesParser()); + break; } if (!this.buildersStack.isEmpty()) @@ -71,12 +74,12 @@ public class PrivilegeConfigSaxReader extends DefaultHandler { this.buildersStack.peek().endElement(uri, localName, qName); ElementParser elementParser = null; - if (qName.equals(XmlConstants.XML_CONTAINER)) { - elementParser = this.buildersStack.pop(); - } else if (qName.equals(XmlConstants.XML_PARAMETERS)) { - elementParser = this.buildersStack.pop(); - } else if (qName.equals(XmlConstants.XML_POLICIES)) { + switch (qName) { + case XmlConstants.XML_CONTAINER: + case XmlConstants.XML_PARAMETERS: + case XmlConstants.XML_POLICIES: elementParser = this.buildersStack.pop(); + break; } if (!this.buildersStack.isEmpty() && elementParser != null) @@ -90,28 +93,47 @@ public class PrivilegeConfigSaxReader extends DefaultHandler { @Override public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { - if (qName.equals(XmlConstants.XML_CONTAINER)) { + + switch (qName) { + case XmlConstants.XML_CONTAINER: this.currentElement = qName; - } else if (qName.equals(XmlConstants.XML_HANDLER_ENCRYPTION)) { + break; + case XmlConstants.XML_HANDLER_ENCRYPTION: { this.currentElement = qName; String className = attributes.getValue(XmlConstants.XML_ATTR_CLASS); getContainerModel().setEncryptionHandlerClassName(className); - } else if (qName.equals(XmlConstants.XML_HANDLER_PERSISTENCE)) { + break; + } + case XmlConstants.XML_HANDLER_PASSWORD_STRENGTH: { + this.currentElement = qName; + String className = attributes.getValue(XmlConstants.XML_ATTR_CLASS); + getContainerModel().setPasswordStrengthHandlerClassName(className); + break; + } + case XmlConstants.XML_HANDLER_PERSISTENCE: { this.currentElement = qName; String className = attributes.getValue(XmlConstants.XML_ATTR_CLASS); getContainerModel().setPersistenceHandlerClassName(className); - } else if (qName.equals(XmlConstants.XML_HANDLER_USER_CHALLENGE)) { + break; + } + case XmlConstants.XML_HANDLER_USER_CHALLENGE: { this.currentElement = qName; String className = attributes.getValue(XmlConstants.XML_ATTR_CLASS); getContainerModel().setUserChallengeHandlerClassName(className); - } else if (qName.equals(XmlConstants.XML_HANDLER_SSO)) { + break; + } + case XmlConstants.XML_HANDLER_SSO: { this.currentElement = qName; String className = attributes.getValue(XmlConstants.XML_ATTR_CLASS); getContainerModel().setSsoHandlerClassName(className); - } else if (qName.equals(XmlConstants.XML_HANDLER_PRIVILEGE)) { + break; + } + case XmlConstants.XML_HANDLER_PRIVILEGE: { this.currentElement = qName; String className = attributes.getValue(XmlConstants.XML_ATTR_CLASS); getContainerModel().setPrivilegeHandlerClassName(className); + break; + } } } @@ -122,27 +144,37 @@ public class PrivilegeConfigSaxReader extends DefaultHandler { ParametersParser parametersChild = (ParametersParser) child; - if (this.currentElement.equals(XmlConstants.XML_CONTAINER)) { + switch (this.currentElement) { + case XmlConstants.XML_CONTAINER: getContainerModel().setParameterMap(parametersChild.getParameterMap()); - } else if (this.currentElement.equals(XmlConstants.XML_HANDLER_ENCRYPTION)) { + break; + case XmlConstants.XML_HANDLER_ENCRYPTION: getContainerModel().setEncryptionHandlerParameterMap(parametersChild.getParameterMap()); - } else if (this.currentElement.equals(XmlConstants.XML_HANDLER_PERSISTENCE)) { + break; + case XmlConstants.XML_HANDLER_PASSWORD_STRENGTH: + getContainerModel().setPasswordStrengthHandlerParameterMap(parametersChild.getParameterMap()); + break; + case XmlConstants.XML_HANDLER_PERSISTENCE: getContainerModel().setPersistenceHandlerParameterMap(parametersChild.getParameterMap()); - } else if (this.currentElement.equals(XmlConstants.XML_HANDLER_USER_CHALLENGE)) { + break; + case XmlConstants.XML_HANDLER_USER_CHALLENGE: getContainerModel().setUserChallengeHandlerParameterMap(parametersChild.getParameterMap()); - } else if (this.currentElement.equals(XmlConstants.XML_HANDLER_SSO)) { + break; + case XmlConstants.XML_HANDLER_SSO: getContainerModel().setSsoHandlerParameterMap(parametersChild.getParameterMap()); - } else if (this.currentElement.equals(XmlConstants.XML_HANDLER_PRIVILEGE)) { + break; + case XmlConstants.XML_HANDLER_PRIVILEGE: getContainerModel().setPrivilegeHandlerParameterMap(parametersChild.getParameterMap()); + break; } } } - class ParametersParser extends ElementParserAdapter { + static class ParametersParser extends ElementParserAdapter { // - private Map parameterMap = new HashMap<>(); + private final Map parameterMap = new HashMap<>(); @Override public void startElement(String uri, String localName, String qName, Attributes attributes) diff --git a/li.strolch.privilege/src/main/resources/PrivilegeMessages.properties b/li.strolch.privilege/src/main/resources/PrivilegeMessages.properties index ec013a5e2..4b7b3a77f 100644 --- a/li.strolch.privilege/src/main/resources/PrivilegeMessages.properties +++ b/li.strolch.privilege/src/main/resources/PrivilegeMessages.properties @@ -15,3 +15,10 @@ Privilege.noprivilege.role=User {0} does not have the role {1} Privilege.noprivilege.user=User {0} does not have the privilege {1} Privilege.roleAccessPrivilege.unknownPrivilege=Unhandled privilege {0} for policy {1} Privilege.userAccessPrivilege.unknownPrivilege=Unhandled privilege {0} for policy {1} +Privilege.passwordLengthBetween=Password must be between {0} and {1} characters long +Privilege.passwordLengthAtLeast=Password must be at least {0} characters long +Privilege.passwordMustContainNumbers=must contain numbers +Privilege.passwordMustContainLowerAndUpperCase=must container lower and upper case +Privilege.passwordMustContainLowerCase=must contain lower case +Privilege.passwordMustContainUpperCase=must contain upper case +Privilege.passwordMustContainSpecialCharacters=must contain special characters \ No newline at end of file diff --git a/li.strolch.privilege/src/main/resources/PrivilegeMessages_de.properties b/li.strolch.privilege/src/main/resources/PrivilegeMessages_de.properties new file mode 100644 index 000000000..d1783eb48 --- /dev/null +++ b/li.strolch.privilege/src/main/resources/PrivilegeMessages_de.properties @@ -0,0 +1,24 @@ +Privilege.accessdenied.noprivilege=Benutzer {0} fehlt Privileg {1} benötigt von {2} mit Wert {3} +Privilege.accessdenied.noprivilege.value=Benutzer {0} fehlt Privileg {1} mit Wert {2} benötigt von {3} +Privilege.illegalArgument.nonstring=\ {0} has returned a non-string privilege value\! +Privilege.illegalArgument.nonstrolchrootelement=\ {0} has returned a privilege value which is not a StrolchRootElement\! +Privilege.illegalArgument.nonrole=\ {0} did not return a Role privilege value\! +Privilege.illegalArgument.noncertificate=\ {0} did not return a Certificate privilege value\! +Privilege.illegalArgument.nonuser=\ {0} did not return a User privilege value\! +Privilege.illegalArgument.privilegeNameMismatch=The passed privilege has the name {0} but the restrictable is referencing privilege {1} +Privilege.illegalArgument.nontuple=\ {0} did not return a Tuple privilege value\! +Privilege.privilegeNameEmpty=The PrivilegeName for the Restrictable is null or empty: {0} +Privilege.privilegeNull=Privilege may not be null\! +Privilege.restrictableNull=Restrictable may not be null\! +Privilege.noprivilege=Privileg {0} existiert nicht +Privilege.noprivilege.role=Benutzer {0} fehlt Rolle {1} +Privilege.noprivilege.user=Benutzer {0} fehlt Privileg {1} +Privilege.roleAccessPrivilege.unknownPrivilege=Unbekanntes Privileg {0} für Regel {1} +Privilege.userAccessPrivilege.unknownPrivilege=Unbekanntes Privileg {0} für Regel {1} +Privilege.passwordLengthBetween=Passwort muss zwischen {0} und {1} Zeichen lang sein +Privilege.passwordLengthAtLeast=Passwort muss mindesten {0} Zeichen lang sein +Privilege.passwordMustContainNumbers=muss Zahlen beinhalten +Privilege.passwordMustContainLowerAndUpperCase=muss grosse und kleine Buchstaben beinhalten +Privilege.passwordMustContainLowerCase=muss kleine Buchstaben beinhalten +Privilege.passwordMustContainUpperCase=muss grosse Buchstaben beinhalten +Privilege.passwordMustContainSpecialCharacters=muss Spezialzeichen beinhalten \ No newline at end of file diff --git a/li.strolch.privilege/src/test/java/li/strolch/privilege/test/BasicPasswordStrengthHandlerTest.java b/li.strolch.privilege/src/test/java/li/strolch/privilege/test/BasicPasswordStrengthHandlerTest.java new file mode 100644 index 000000000..92ff43da9 --- /dev/null +++ b/li.strolch.privilege/src/test/java/li/strolch/privilege/test/BasicPasswordStrengthHandlerTest.java @@ -0,0 +1,52 @@ +package li.strolch.privilege.test; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.util.HashMap; +import java.util.Map; + +import li.strolch.privilege.handler.BasicPasswordStrengthHandler; +import li.strolch.privilege.handler.PasswordStrengthHandler; +import org.junit.Test; + +public class BasicPasswordStrengthHandlerTest { + + @Test + public void testPwStrengthBasic() { + PasswordStrengthHandler handler = new BasicPasswordStrengthHandler(); + Map parameters = new HashMap<>(); + parameters.put("minLength", "8"); + parameters.put("maxLength", "1024"); + parameters.put("needsNumbers", "true"); + parameters.put("needsLowerCase", "true"); + parameters.put("needsUpperCase", "true"); + parameters.put("needsSpecialChars", "true"); + handler.initialize(parameters); + + assertTrue(handler.validateStrength("Testing0!".toCharArray())); + assertTrue(handler.validateStrength("Täëing0!".toCharArray())); + assertTrue(handler.validateStrength("Testing0@".toCharArray())); + assertTrue(handler.validateStrength("Testing0¼".toCharArray())); + assertTrue(handler.validateStrength("Testing0|".toCharArray())); + assertTrue(handler.validateStrength("+n4lJ,7&".toCharArray())); + assertTrue(handler.validateStrength("]}`aH1&z".toCharArray())); + assertFalse(handler.validateStrength("Tg0!".toCharArray())); + } + + @Test + public void testPwStrengthOnlyNumbers() { + PasswordStrengthHandler handler = new BasicPasswordStrengthHandler(); + Map parameters = new HashMap<>(); + parameters.put("minLength", "8"); + parameters.put("maxLength", "8"); + parameters.put("needsNumbers", "true"); + parameters.put("needsLowerCase", "false"); + parameters.put("needsUpperCase", "false"); + parameters.put("needsSpecialChars", "false"); + handler.initialize(parameters); + + assertTrue(handler.validateStrength("34534534".toCharArray())); + assertFalse(handler.validateStrength("Testing0!".toCharArray())); + } +}