Merge pull request #3030 from stianst/KEYCLOAK-2824-2
KEYCLOAK-2824 Password Policy SPI
This commit is contained in:
commit
4f1d83b9dc
58 changed files with 2285 additions and 796 deletions
|
@ -0,0 +1,70 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||||
|
* and other contributors as indicated by the @author tags.
|
||||||
|
*
|
||||||
|
* 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 org.keycloak.representations.idm;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||||
|
*/
|
||||||
|
public class PasswordPolicyTypeRepresentation {
|
||||||
|
|
||||||
|
private String id;
|
||||||
|
private String displayName;
|
||||||
|
private String configType;
|
||||||
|
private String defaultValue;
|
||||||
|
private boolean multipleSupported;
|
||||||
|
|
||||||
|
public String getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setId(String id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDisplayName() {
|
||||||
|
return displayName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDisplayName(String displayName) {
|
||||||
|
this.displayName = displayName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getConfigType() {
|
||||||
|
return configType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setConfigType(String configType) {
|
||||||
|
this.configType = configType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDefaultValue() {
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDefaultValue(String defaultValue) {
|
||||||
|
this.defaultValue = defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isMultipleSupported() {
|
||||||
|
return multipleSupported;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMultipleSupported(boolean multipleSupported) {
|
||||||
|
this.multipleSupported = multipleSupported;
|
||||||
|
}
|
||||||
|
}
|
|
@ -17,6 +17,7 @@
|
||||||
|
|
||||||
package org.keycloak.representations.info;
|
package org.keycloak.representations.info;
|
||||||
|
|
||||||
|
import org.keycloak.representations.idm.PasswordPolicyTypeRepresentation;
|
||||||
import org.keycloak.representations.idm.ProtocolMapperRepresentation;
|
import org.keycloak.representations.idm.ProtocolMapperRepresentation;
|
||||||
import org.keycloak.representations.idm.ProtocolMapperTypeRepresentation;
|
import org.keycloak.representations.idm.ProtocolMapperTypeRepresentation;
|
||||||
|
|
||||||
|
@ -43,6 +44,8 @@ public class ServerInfoRepresentation {
|
||||||
private Map<String, List<ProtocolMapperRepresentation>> builtinProtocolMappers;
|
private Map<String, List<ProtocolMapperRepresentation>> builtinProtocolMappers;
|
||||||
private Map<String, List<ClientInstallationRepresentation>> clientInstallations;
|
private Map<String, List<ClientInstallationRepresentation>> clientInstallations;
|
||||||
|
|
||||||
|
private List<PasswordPolicyTypeRepresentation> passwordPolicies;
|
||||||
|
|
||||||
private Map<String, List<String>> enums;
|
private Map<String, List<String>> enums;
|
||||||
|
|
||||||
public SystemInfoRepresentation getSystemInfo() {
|
public SystemInfoRepresentation getSystemInfo() {
|
||||||
|
@ -132,4 +135,13 @@ public class ServerInfoRepresentation {
|
||||||
public void setClientInstallations(Map<String, List<ClientInstallationRepresentation>> clientInstallations) {
|
public void setClientInstallations(Map<String, List<ClientInstallationRepresentation>> clientInstallations) {
|
||||||
this.clientInstallations = clientInstallations;
|
this.clientInstallations = clientInstallations;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<PasswordPolicyTypeRepresentation> getPasswordPolicies() {
|
||||||
|
return passwordPolicies;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPasswordPolicies(List<PasswordPolicyTypeRepresentation> passwordPolicies) {
|
||||||
|
this.passwordPolicies = passwordPolicies;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1223,7 +1223,7 @@ public class RealmAdapter implements RealmModel, JpaModel<RealmEntity> {
|
||||||
@Override
|
@Override
|
||||||
public PasswordPolicy getPasswordPolicy() {
|
public PasswordPolicy getPasswordPolicy() {
|
||||||
if (passwordPolicy == null) {
|
if (passwordPolicy == null) {
|
||||||
passwordPolicy = new PasswordPolicy(realm.getPasswordPolicy());
|
passwordPolicy = PasswordPolicy.parse(session, realm.getPasswordPolicy());
|
||||||
}
|
}
|
||||||
return passwordPolicy;
|
return passwordPolicy;
|
||||||
}
|
}
|
||||||
|
|
|
@ -308,7 +308,7 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> impleme
|
||||||
@Override
|
@Override
|
||||||
public PasswordPolicy getPasswordPolicy() {
|
public PasswordPolicy getPasswordPolicy() {
|
||||||
if (passwordPolicy == null) {
|
if (passwordPolicy == null) {
|
||||||
passwordPolicy = new PasswordPolicy(realm.getPasswordPolicy());
|
passwordPolicy = PasswordPolicy.parse(session, realm.getPasswordPolicy());
|
||||||
}
|
}
|
||||||
return passwordPolicy;
|
return passwordPolicy;
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,7 +18,11 @@
|
||||||
package org.keycloak.hash;
|
package org.keycloak.hash;
|
||||||
|
|
||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
import org.keycloak.models.*;
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
import org.keycloak.models.PasswordPolicy;
|
||||||
|
import org.keycloak.models.RealmModel;
|
||||||
|
import org.keycloak.models.UserCredentialValueModel;
|
||||||
|
import org.keycloak.policy.HashAlgorithmPasswordPolicyProviderFactory;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||||
|
@ -32,17 +36,12 @@ public class PasswordHashManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static UserCredentialValueModel encode(KeycloakSession session, PasswordPolicy passwordPolicy, String rawPassword) {
|
public static UserCredentialValueModel encode(KeycloakSession session, PasswordPolicy passwordPolicy, String rawPassword) {
|
||||||
String algorithm = passwordPolicy.getHashAlgorithm();
|
|
||||||
int iterations = passwordPolicy.getHashIterations();
|
|
||||||
if (iterations < 1) {
|
|
||||||
iterations = 1;
|
|
||||||
}
|
|
||||||
PasswordHashProvider provider = session.getProvider(PasswordHashProvider.class, passwordPolicy.getHashAlgorithm());
|
PasswordHashProvider provider = session.getProvider(PasswordHashProvider.class, passwordPolicy.getHashAlgorithm());
|
||||||
if (provider == null) {
|
if (provider == null) {
|
||||||
log.warnv("Could not find hash provider {0} from password policy, using default provider {1}", algorithm, Constants.DEFAULT_HASH_ALGORITHM);
|
log.warnv("Could not find hash provider {0} from password policy, using default provider {1}", passwordPolicy.getHashAlgorithm(), HashAlgorithmPasswordPolicyProviderFactory.DEFAULT_VALUE);
|
||||||
provider = session.getProvider(PasswordHashProvider.class, Constants.DEFAULT_HASH_ALGORITHM);
|
provider = session.getProvider(PasswordHashProvider.class, HashAlgorithmPasswordPolicyProviderFactory.DEFAULT_VALUE);
|
||||||
}
|
}
|
||||||
return provider.encode(rawPassword, iterations);
|
return provider.encode(rawPassword, passwordPolicy.getHashIterations());
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean verify(KeycloakSession session, RealmModel realm, String password, UserCredentialValueModel credential) {
|
public static boolean verify(KeycloakSession session, RealmModel realm, String password, UserCredentialValueModel credential) {
|
||||||
|
|
|
@ -41,8 +41,6 @@ public interface Constants {
|
||||||
String AUTHZ_UMA_AUTHORIZATION = "uma_authorization";
|
String AUTHZ_UMA_AUTHORIZATION = "uma_authorization";
|
||||||
String[] AUTHZ_DEFAULT_AUTHORIZATION_ROLES = {AUTHZ_UMA_AUTHORIZATION};
|
String[] AUTHZ_DEFAULT_AUTHORIZATION_ROLES = {AUTHZ_UMA_AUTHORIZATION};
|
||||||
|
|
||||||
String DEFAULT_HASH_ALGORITHM = "pbkdf2";
|
|
||||||
|
|
||||||
// 15 minutes
|
// 15 minutes
|
||||||
int DEFAULT_ACCESS_TOKEN_LIFESPAN_FOR_IMPLICIT_FLOW_TIMEOUT = 900;
|
int DEFAULT_ACCESS_TOKEN_LIFESPAN_FOR_IMPLICIT_FLOW_TIMEOUT = 900;
|
||||||
// 30 days
|
// 30 days
|
||||||
|
|
|
@ -17,498 +17,101 @@
|
||||||
|
|
||||||
package org.keycloak.models;
|
package org.keycloak.models;
|
||||||
|
|
||||||
import org.keycloak.hash.PasswordHashManager;
|
import org.keycloak.policy.ForceExpiredPasswordPolicyProviderFactory;
|
||||||
|
import org.keycloak.policy.HashAlgorithmPasswordPolicyProviderFactory;
|
||||||
|
import org.keycloak.policy.HashIterationsPasswordPolicyProviderFactory;
|
||||||
|
import org.keycloak.policy.HistoryPasswordPolicyProviderFactory;
|
||||||
|
import org.keycloak.policy.PasswordPolicyProvider;
|
||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
import java.util.ArrayList;
|
import java.util.HashMap;
|
||||||
import java.util.Collections;
|
import java.util.Map;
|
||||||
import java.util.Comparator;
|
import java.util.Set;
|
||||||
import java.util.LinkedList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.regex.Matcher;
|
|
||||||
import java.util.regex.Pattern;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||||
*/
|
*/
|
||||||
public class PasswordPolicy implements Serializable {
|
public class PasswordPolicy implements Serializable {
|
||||||
|
|
||||||
public static final String INVALID_PASSWORD_MIN_LENGTH_MESSAGE = "invalidPasswordMinLengthMessage";
|
|
||||||
public static final String INVALID_PASSWORD_MIN_DIGITS_MESSAGE = "invalidPasswordMinDigitsMessage";
|
|
||||||
public static final String INVALID_PASSWORD_MIN_LOWER_CASE_CHARS_MESSAGE = "invalidPasswordMinLowerCaseCharsMessage";
|
|
||||||
public static final String INVALID_PASSWORD_MIN_UPPER_CASE_CHARS_MESSAGE = "invalidPasswordMinUpperCaseCharsMessage";
|
|
||||||
public static final String INVALID_PASSWORD_MIN_SPECIAL_CHARS_MESSAGE = "invalidPasswordMinSpecialCharsMessage";
|
|
||||||
public static final String INVALID_PASSWORD_NOT_USERNAME = "invalidPasswordNotUsernameMessage";
|
|
||||||
public static final String INVALID_PASSWORD_REGEX_PATTERN = "invalidPasswordRegexPatternMessage";
|
|
||||||
public static final String INVALID_PASSWORD_HISTORY = "invalidPasswordHistoryMessage";
|
|
||||||
|
|
||||||
private List<Policy> policies;
|
|
||||||
private String policyString;
|
private String policyString;
|
||||||
|
private Map<String, Object> policyConfig;
|
||||||
|
|
||||||
public PasswordPolicy(String policyString) {
|
public static PasswordPolicy empty() {
|
||||||
this.policyString = policyString;
|
return new PasswordPolicy(null, new HashMap<>());
|
||||||
this.policies = new LinkedList<>();
|
}
|
||||||
|
|
||||||
|
public static PasswordPolicy parse(KeycloakSession session, String policyString) {
|
||||||
|
Map<String, Object> policyConfig = new HashMap<>();
|
||||||
|
|
||||||
if (policyString != null && !policyString.trim().isEmpty()) {
|
if (policyString != null && !policyString.trim().isEmpty()) {
|
||||||
for (String policy : policyString.split(" and ")) {
|
for (String policy : policyString.split(" and ")) {
|
||||||
policy = policy.trim();
|
policy = policy.trim();
|
||||||
|
|
||||||
String name;
|
String key;
|
||||||
String arg = null;
|
String config = null;
|
||||||
|
|
||||||
int i = policy.indexOf('(');
|
int i = policy.indexOf('(');
|
||||||
if (i == -1) {
|
if (i == -1) {
|
||||||
name = policy.trim();
|
key = policy.trim();
|
||||||
} else {
|
} else {
|
||||||
name = policy.substring(0, i).trim();
|
key = policy.substring(0, i).trim();
|
||||||
arg = policy.substring(i + 1, policy.length() - 1);
|
config = policy.substring(i + 1, policy.length() - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (name.equals(Length.NAME)) {
|
PasswordPolicyProvider provider = session.getProvider(PasswordPolicyProvider.class, key);
|
||||||
policies.add(new Length(arg));
|
if (provider == null) {
|
||||||
} else if (name.equals(Digits.NAME)) {
|
|
||||||
policies.add(new Digits(arg));
|
|
||||||
} else if (name.equals(LowerCase.NAME)) {
|
|
||||||
policies.add(new LowerCase(arg));
|
|
||||||
} else if (name.equals(UpperCase.NAME)) {
|
|
||||||
policies.add(new UpperCase(arg));
|
|
||||||
} else if (name.equals(SpecialChars.NAME)) {
|
|
||||||
policies.add(new SpecialChars(arg));
|
|
||||||
} else if (name.equals(NotUsername.NAME)) {
|
|
||||||
policies.add(new NotUsername(arg));
|
|
||||||
} else if (name.equals(HashAlgorithm.NAME)) {
|
|
||||||
policies.add(new HashAlgorithm(arg));
|
|
||||||
} else if (name.equals(HashIterations.NAME)) {
|
|
||||||
policies.add(new HashIterations(arg));
|
|
||||||
} else if (name.equals(RegexPatterns.NAME)) {
|
|
||||||
Pattern.compile(arg);
|
|
||||||
policies.add(new RegexPatterns(arg));
|
|
||||||
} else if (name.equals(PasswordHistory.NAME)) {
|
|
||||||
policies.add(new PasswordHistory(arg, this));
|
|
||||||
} else if (name.equals(ForceExpiredPasswordChange.NAME)) {
|
|
||||||
policies.add(new ForceExpiredPasswordChange(arg));
|
|
||||||
} else {
|
|
||||||
throw new IllegalArgumentException("Unsupported policy");
|
throw new IllegalArgumentException("Unsupported policy");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
policyConfig.put(key, provider.parseConfig(config));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return new PasswordPolicy(policyString, policyConfig);
|
||||||
|
}
|
||||||
|
|
||||||
|
private PasswordPolicy(String policyString, Map<String, Object> policyConfig) {
|
||||||
|
this.policyString = policyString;
|
||||||
|
this.policyConfig = policyConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Set<String> getPolicies() {
|
||||||
|
return policyConfig.keySet();
|
||||||
|
}
|
||||||
|
|
||||||
|
public <T> T getPolicyConfig(String key) {
|
||||||
|
return (T) policyConfig.get(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getHashAlgorithm() {
|
public String getHashAlgorithm() {
|
||||||
if (policies == null)
|
if (policyConfig.containsKey(HashAlgorithmPasswordPolicyProviderFactory.ID)) {
|
||||||
return Constants.DEFAULT_HASH_ALGORITHM;
|
return getPolicyConfig(HashAlgorithmPasswordPolicyProviderFactory.ID);
|
||||||
for (Policy p : policies) {
|
} else {
|
||||||
if (p instanceof HashAlgorithm) {
|
return HashAlgorithmPasswordPolicyProviderFactory.DEFAULT_VALUE;
|
||||||
return ((HashAlgorithm) p).algorithm;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
return Constants.DEFAULT_HASH_ALGORITHM;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @return -1 if no hash iterations setting
|
|
||||||
*/
|
|
||||||
public int getHashIterations() {
|
public int getHashIterations() {
|
||||||
if (policies == null)
|
if (policyConfig.containsKey(HashIterationsPasswordPolicyProviderFactory.ID)) {
|
||||||
return -1;
|
return getPolicyConfig(HashIterationsPasswordPolicyProviderFactory.ID);
|
||||||
for (Policy p : policies) {
|
} else {
|
||||||
if (p instanceof HashIterations) {
|
return HashIterationsPasswordPolicyProviderFactory.DEFAULT_VALUE;
|
||||||
return ((HashIterations) p).iterations;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
return -1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @return -1 if no expired passwords setting
|
|
||||||
*/
|
|
||||||
public int getExpiredPasswords() {
|
public int getExpiredPasswords() {
|
||||||
if (policies == null)
|
if (policyConfig.containsKey(HistoryPasswordPolicyProviderFactory.ID)) {
|
||||||
|
return getPolicyConfig(HistoryPasswordPolicyProviderFactory.ID);
|
||||||
|
} else {
|
||||||
return -1;
|
return -1;
|
||||||
for (Policy p : policies) {
|
|
||||||
if (p instanceof PasswordHistory) {
|
|
||||||
return ((PasswordHistory) p).passwordHistoryPolicyValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @return -1 if no force expired password change setting
|
|
||||||
*/
|
|
||||||
public int getDaysToExpirePassword() {
|
|
||||||
if (policies == null)
|
|
||||||
return -1;
|
|
||||||
for (Policy p : policies) {
|
|
||||||
if (p instanceof ForceExpiredPasswordChange) {
|
|
||||||
return ((ForceExpiredPasswordChange) p).daysToExpirePassword;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Error validate(KeycloakSession session, UserModel user, String password) {
|
|
||||||
for (Policy p : policies) {
|
|
||||||
Error error = p.validate(session, user, password);
|
|
||||||
if (error != null) {
|
|
||||||
return error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Error validate(KeycloakSession session, String user, String password) {
|
|
||||||
for (Policy p : policies) {
|
|
||||||
Error error = p.validate(session, user, password);
|
|
||||||
if (error != null) {
|
|
||||||
return error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static interface Policy extends Serializable {
|
|
||||||
public Error validate(KeycloakSession session, UserModel user, String password);
|
|
||||||
public Error validate(KeycloakSession session, String user, String password);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class Error {
|
|
||||||
private String message;
|
|
||||||
private Object[] parameters;
|
|
||||||
|
|
||||||
private Error(String message, Object... parameters) {
|
|
||||||
this.message = message;
|
|
||||||
this.parameters = parameters;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getMessage() {
|
|
||||||
return message;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Object[] getParameters() {
|
|
||||||
return parameters;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class HashAlgorithm implements Policy {
|
public int getDaysToExpirePassword() {
|
||||||
private static final String NAME = "hashAlgorithm";
|
if (policyConfig.containsKey(ForceExpiredPasswordPolicyProviderFactory.ID)) {
|
||||||
private String algorithm;
|
return getPolicyConfig(ForceExpiredPasswordPolicyProviderFactory.ID);
|
||||||
|
|
||||||
public HashAlgorithm(String arg) {
|
|
||||||
algorithm = stringArg(NAME, Constants.DEFAULT_HASH_ALGORITHM, arg);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Error validate(KeycloakSession session, String user, String password) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Error validate(KeycloakSession session, UserModel user, String password) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class HashIterations implements Policy {
|
|
||||||
private static final String NAME = "hashIterations";
|
|
||||||
private int iterations;
|
|
||||||
|
|
||||||
public HashIterations(String arg) {
|
|
||||||
iterations = intArg(NAME, 1, arg);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Error validate(KeycloakSession session, String user, String password) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Error validate(KeycloakSession session, UserModel user, String password) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class NotUsername implements Policy {
|
|
||||||
private static final String NAME = "notUsername";
|
|
||||||
|
|
||||||
public NotUsername(String arg) {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Error validate(KeycloakSession session, String username, String password) {
|
|
||||||
return username.equals(password) ? new Error(INVALID_PASSWORD_NOT_USERNAME) : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Error validate(KeycloakSession session, UserModel user, String password) {
|
|
||||||
return validate(session, user.getUsername(), password);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class Length implements Policy {
|
|
||||||
private static final String NAME = "length";
|
|
||||||
private int min;
|
|
||||||
|
|
||||||
public Length(String arg)
|
|
||||||
{
|
|
||||||
min = intArg(NAME, 8, arg);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Error validate(KeycloakSession session, String username, String password) {
|
|
||||||
return password.length() < min ? new Error(INVALID_PASSWORD_MIN_LENGTH_MESSAGE, min) : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Error validate(KeycloakSession session, UserModel user, String password) {
|
|
||||||
return validate(session, user.getUsername(), password);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class Digits implements Policy {
|
|
||||||
private static final String NAME = "digits";
|
|
||||||
private int min;
|
|
||||||
|
|
||||||
public Digits(String arg)
|
|
||||||
{
|
|
||||||
min = intArg(NAME, 1, arg);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Error validate(KeycloakSession session, String username, String password) {
|
|
||||||
int count = 0;
|
|
||||||
for (char c : password.toCharArray()) {
|
|
||||||
if (Character.isDigit(c)) {
|
|
||||||
count++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return count < min ? new Error(INVALID_PASSWORD_MIN_DIGITS_MESSAGE, min) : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Error validate(KeycloakSession session, UserModel user, String password) {
|
|
||||||
return validate(session, user.getUsername(), password);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class LowerCase implements Policy {
|
|
||||||
private static final String NAME = "lowerCase";
|
|
||||||
private int min;
|
|
||||||
|
|
||||||
public LowerCase(String arg)
|
|
||||||
{
|
|
||||||
min = intArg(NAME, 1, arg);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Error validate(KeycloakSession session, String username, String password) {
|
|
||||||
int count = 0;
|
|
||||||
for (char c : password.toCharArray()) {
|
|
||||||
if (Character.isLowerCase(c)) {
|
|
||||||
count++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return count < min ? new Error(INVALID_PASSWORD_MIN_LOWER_CASE_CHARS_MESSAGE, min) : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Error validate(KeycloakSession session, UserModel user, String password) {
|
|
||||||
return validate(session, user.getUsername(), password);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class UpperCase implements Policy {
|
|
||||||
private static final String NAME = "upperCase";
|
|
||||||
private int min;
|
|
||||||
|
|
||||||
public UpperCase(String arg) {
|
|
||||||
min = intArg(NAME, 1, arg);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Error validate(KeycloakSession session, String username, String password) {
|
|
||||||
int count = 0;
|
|
||||||
for (char c : password.toCharArray()) {
|
|
||||||
if (Character.isUpperCase(c)) {
|
|
||||||
count++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return count < min ? new Error(INVALID_PASSWORD_MIN_UPPER_CASE_CHARS_MESSAGE, min) : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Error validate(KeycloakSession session, UserModel user, String password) {
|
|
||||||
return validate(session, user.getUsername(), password);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class SpecialChars implements Policy {
|
|
||||||
private static final String NAME = "specialChars";
|
|
||||||
private int min;
|
|
||||||
|
|
||||||
public SpecialChars(String arg)
|
|
||||||
{
|
|
||||||
min = intArg(NAME, 1, arg);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Error validate(KeycloakSession session, String username, String password) {
|
|
||||||
int count = 0;
|
|
||||||
for (char c : password.toCharArray()) {
|
|
||||||
if (!Character.isLetterOrDigit(c)) {
|
|
||||||
count++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return count < min ? new Error(INVALID_PASSWORD_MIN_SPECIAL_CHARS_MESSAGE, min) : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Error validate(KeycloakSession session, UserModel user, String password) {
|
|
||||||
return validate(session, user.getUsername(), password);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class RegexPatterns implements Policy {
|
|
||||||
private static final String NAME = "regexPattern";
|
|
||||||
private String regexPattern;
|
|
||||||
|
|
||||||
public RegexPatterns(String arg)
|
|
||||||
{
|
|
||||||
regexPattern = arg;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Error validate(KeycloakSession session, String username, String password) {
|
|
||||||
Pattern pattern = Pattern.compile(regexPattern);
|
|
||||||
Matcher matcher = pattern.matcher(password);
|
|
||||||
if (!matcher.matches()) {
|
|
||||||
return new Error(INVALID_PASSWORD_REGEX_PATTERN, (Object) regexPattern);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Error validate(KeycloakSession session, UserModel user, String password) {
|
|
||||||
return validate(session, user.getUsername(), password);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class PasswordHistory implements Policy {
|
|
||||||
private static final String NAME = "passwordHistory";
|
|
||||||
private final PasswordPolicy passwordPolicy;
|
|
||||||
private int passwordHistoryPolicyValue;
|
|
||||||
|
|
||||||
public PasswordHistory(String arg, PasswordPolicy passwordPolicy)
|
|
||||||
{
|
|
||||||
this.passwordPolicy = passwordPolicy;
|
|
||||||
passwordHistoryPolicyValue = intArg(NAME, 3, arg);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Error validate(KeycloakSession session, String user, String password) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Error validate(KeycloakSession session, UserModel user, String password) {
|
|
||||||
if (passwordHistoryPolicyValue != -1) {
|
|
||||||
UserCredentialValueModel cred = getCredentialValueModel(user, UserCredentialModel.PASSWORD);
|
|
||||||
if (cred != null) {
|
|
||||||
if(PasswordHashManager.verify(session, passwordPolicy, password, cred)) {
|
|
||||||
return new Error(INVALID_PASSWORD_HISTORY, passwordHistoryPolicyValue);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
List<UserCredentialValueModel> passwordExpiredCredentials = getCredentialValueModels(user, passwordHistoryPolicyValue - 1,
|
|
||||||
UserCredentialModel.PASSWORD_HISTORY);
|
|
||||||
for (UserCredentialValueModel credential : passwordExpiredCredentials) {
|
|
||||||
if (PasswordHashManager.verify(session, passwordPolicy, password, credential)) {
|
|
||||||
return new Error(INVALID_PASSWORD_HISTORY, passwordHistoryPolicyValue);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private UserCredentialValueModel getCredentialValueModel(UserModel user, String credType) {
|
|
||||||
for (UserCredentialValueModel model : user.getCredentialsDirectly()) {
|
|
||||||
if (model.getType().equals(credType)) {
|
|
||||||
return model;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<UserCredentialValueModel> getCredentialValueModels(UserModel user, int expiredPasswordsPolicyValue,
|
|
||||||
String credType) {
|
|
||||||
List<UserCredentialValueModel> credentialModels = new ArrayList<UserCredentialValueModel>();
|
|
||||||
for (UserCredentialValueModel model : user.getCredentialsDirectly()) {
|
|
||||||
if (model.getType().equals(credType)) {
|
|
||||||
credentialModels.add(model);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Collections.sort(credentialModels, new Comparator<UserCredentialValueModel>() {
|
|
||||||
public int compare(UserCredentialValueModel credFirst, UserCredentialValueModel credSecond) {
|
|
||||||
if (credFirst.getCreatedDate() > credSecond.getCreatedDate()) {
|
|
||||||
return -1;
|
|
||||||
} else if (credFirst.getCreatedDate() < credSecond.getCreatedDate()) {
|
|
||||||
return 1;
|
|
||||||
} else {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (credentialModels.size() > expiredPasswordsPolicyValue) {
|
|
||||||
return credentialModels.subList(0, expiredPasswordsPolicyValue);
|
|
||||||
}
|
|
||||||
return credentialModels;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class ForceExpiredPasswordChange implements Policy {
|
|
||||||
private static final String NAME = "forceExpiredPasswordChange";
|
|
||||||
private int daysToExpirePassword;
|
|
||||||
|
|
||||||
public ForceExpiredPasswordChange(String arg) {
|
|
||||||
daysToExpirePassword = intArg(NAME, 365, arg);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Error validate(KeycloakSession session, String username, String password) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Error validate(KeycloakSession session, UserModel user, String password) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static int intArg(String policy, int defaultValue, String arg) {
|
|
||||||
if (arg == null) {
|
|
||||||
return defaultValue;
|
|
||||||
} else {
|
} else {
|
||||||
return Integer.parseInt(arg);
|
return -1;
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String stringArg(String policy, String defaultValue, String arg) {
|
|
||||||
if (arg == null) {
|
|
||||||
return defaultValue;
|
|
||||||
} else {
|
|
||||||
return arg;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -516,4 +119,5 @@ public class PasswordPolicy implements Serializable {
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return policyString;
|
return policyString;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,6 +19,8 @@ package org.keycloak.models;
|
||||||
|
|
||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||||
|
import org.keycloak.policy.PasswordPolicyManagerProvider;
|
||||||
|
import org.keycloak.policy.PolicyError;
|
||||||
import org.keycloak.services.managers.UserManager;
|
import org.keycloak.services.managers.UserManager;
|
||||||
import org.keycloak.storage.StorageProviderModel;
|
import org.keycloak.storage.StorageProviderModel;
|
||||||
|
|
||||||
|
@ -493,7 +495,7 @@ public class UserFederationManager implements UserProvider {
|
||||||
public void updateCredential(RealmModel realm, UserModel user, UserCredentialModel credential) {
|
public void updateCredential(RealmModel realm, UserModel user, UserCredentialModel credential) {
|
||||||
if (credential.getType().equals(UserCredentialModel.PASSWORD)) {
|
if (credential.getType().equals(UserCredentialModel.PASSWORD)) {
|
||||||
if (realm.getPasswordPolicy() != null) {
|
if (realm.getPasswordPolicy() != null) {
|
||||||
PasswordPolicy.Error error = realm.getPasswordPolicy().validate(session, user, credential.getValue());
|
PolicyError error = session.getProvider(PasswordPolicyManagerProvider.class).validate(user, credential.getValue());
|
||||||
if (error != null) throw new ModelException(error.getMessage(), error.getParameters());
|
if (error != null) throw new ModelException(error.getMessage(), error.getParameters());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,19 +17,18 @@
|
||||||
|
|
||||||
package org.keycloak.models.utils;
|
package org.keycloak.models.utils;
|
||||||
|
|
||||||
|
import org.keycloak.common.util.Time;
|
||||||
import org.keycloak.hash.PasswordHashManager;
|
import org.keycloak.hash.PasswordHashManager;
|
||||||
import org.keycloak.jose.jws.JWSInput;
|
import org.keycloak.jose.jws.JWSInput;
|
||||||
import org.keycloak.jose.jws.JWSInputException;
|
import org.keycloak.jose.jws.JWSInputException;
|
||||||
import org.keycloak.jose.jws.crypto.RSAProvider;
|
import org.keycloak.jose.jws.crypto.RSAProvider;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.OTPPolicy;
|
import org.keycloak.models.OTPPolicy;
|
||||||
import org.keycloak.models.PasswordPolicy;
|
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.UserCredentialModel;
|
import org.keycloak.models.UserCredentialModel;
|
||||||
import org.keycloak.models.UserCredentialValueModel;
|
import org.keycloak.models.UserCredentialValueModel;
|
||||||
import org.keycloak.models.UserModel;
|
import org.keycloak.models.UserModel;
|
||||||
import org.keycloak.representations.PasswordToken;
|
import org.keycloak.representations.PasswordToken;
|
||||||
import org.keycloak.common.util.Time;
|
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
@ -39,15 +38,6 @@ import java.util.List;
|
||||||
*/
|
*/
|
||||||
public class CredentialValidation {
|
public class CredentialValidation {
|
||||||
|
|
||||||
private static int hashIterations(RealmModel realm) {
|
|
||||||
PasswordPolicy policy = realm.getPasswordPolicy();
|
|
||||||
if (policy != null) {
|
|
||||||
return policy.getHashIterations();
|
|
||||||
}
|
|
||||||
return -1;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Will update password if hash iteration policy has changed
|
* Will update password if hash iteration policy has changed
|
||||||
*
|
*
|
||||||
|
@ -78,8 +68,7 @@ public class CredentialValidation {
|
||||||
boolean validated = PasswordHashManager.verify(session, realm, unhashedCredValue, credential);
|
boolean validated = PasswordHashManager.verify(session, realm, unhashedCredValue, credential);
|
||||||
|
|
||||||
if (validated) {
|
if (validated) {
|
||||||
int iterations = hashIterations(realm);
|
if (realm.getPasswordPolicy().getHashIterations() != credential.getHashIterations()) {
|
||||||
if (iterations > -1 && iterations != credential.getHashIterations()) {
|
|
||||||
|
|
||||||
UserCredentialValueModel newCred = PasswordHashManager.encode(session, realm, unhashedCredValue);
|
UserCredentialValueModel newCred = PasswordHashManager.encode(session, realm, unhashedCredValue);
|
||||||
user.updateCredentialDirectly(newCred);
|
user.updateCredentialDirectly(newCred);
|
||||||
|
|
|
@ -196,7 +196,7 @@ public class RepresentationToModel {
|
||||||
newRealm.addRequiredCredential(CredentialRepresentation.PASSWORD);
|
newRealm.addRequiredCredential(CredentialRepresentation.PASSWORD);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (rep.getPasswordPolicy() != null) newRealm.setPasswordPolicy(new PasswordPolicy(rep.getPasswordPolicy()));
|
if (rep.getPasswordPolicy() != null) newRealm.setPasswordPolicy(PasswordPolicy.parse(session, rep.getPasswordPolicy()));
|
||||||
if (rep.getOtpPolicyType() != null) newRealm.setOTPPolicy(toPolicy(rep));
|
if (rep.getOtpPolicyType() != null) newRealm.setOTPPolicy(toPolicy(rep));
|
||||||
else newRealm.setOTPPolicy(OTPPolicy.DEFAULT_POLICY);
|
else newRealm.setOTPPolicy(OTPPolicy.DEFAULT_POLICY);
|
||||||
|
|
||||||
|
@ -661,7 +661,7 @@ public class RepresentationToModel {
|
||||||
return url != null ? url.replace(target, replacement) : null;
|
return url != null ? url.replace(target, replacement) : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void updateRealm(RealmRepresentation rep, RealmModel realm) {
|
public static void updateRealm(RealmRepresentation rep, RealmModel realm, KeycloakSession session) {
|
||||||
if (rep.getRealm() != null) {
|
if (rep.getRealm() != null) {
|
||||||
renameRealm(realm, rep.getRealm());
|
renameRealm(realm, rep.getRealm());
|
||||||
}
|
}
|
||||||
|
@ -709,7 +709,7 @@ public class RepresentationToModel {
|
||||||
if (rep.isAdminEventsDetailsEnabled() != null) realm.setAdminEventsDetailsEnabled(rep.isAdminEventsDetailsEnabled());
|
if (rep.isAdminEventsDetailsEnabled() != null) realm.setAdminEventsDetailsEnabled(rep.isAdminEventsDetailsEnabled());
|
||||||
|
|
||||||
|
|
||||||
if (rep.getPasswordPolicy() != null) realm.setPasswordPolicy(new PasswordPolicy(rep.getPasswordPolicy()));
|
if (rep.getPasswordPolicy() != null) realm.setPasswordPolicy(PasswordPolicy.parse(session, rep.getPasswordPolicy()));
|
||||||
if (rep.getOtpPolicyType() != null) realm.setOTPPolicy(toPolicy(rep));
|
if (rep.getOtpPolicyType() != null) realm.setOTPPolicy(toPolicy(rep));
|
||||||
|
|
||||||
if (rep.getDefaultRoles() != null) {
|
if (rep.getDefaultRoles() != null) {
|
||||||
|
|
|
@ -0,0 +1,74 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||||
|
* and other contributors as indicated by the @author tags.
|
||||||
|
*
|
||||||
|
* 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 org.keycloak.policy;
|
||||||
|
|
||||||
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
import org.keycloak.models.PasswordPolicy;
|
||||||
|
import org.keycloak.models.UserModel;
|
||||||
|
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||||
|
*/
|
||||||
|
public class DefaultPasswordPolicyManagerProvider implements PasswordPolicyManagerProvider {
|
||||||
|
|
||||||
|
private KeycloakSession session;
|
||||||
|
|
||||||
|
public DefaultPasswordPolicyManagerProvider(KeycloakSession session) {
|
||||||
|
this.session = session;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PolicyError validate(UserModel user, String password) {
|
||||||
|
for (PasswordPolicyProvider p : getProviders(session)) {
|
||||||
|
PolicyError policyError = p.validate(user, password);
|
||||||
|
if (policyError != null) {
|
||||||
|
return policyError;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PolicyError validate(String user, String password) {
|
||||||
|
for (PasswordPolicyProvider p : getProviders(session)) {
|
||||||
|
PolicyError policyError = p.validate(user, password);
|
||||||
|
if (policyError != null) {
|
||||||
|
return policyError;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<PasswordPolicyProvider> getProviders(KeycloakSession session) {
|
||||||
|
LinkedList<PasswordPolicyProvider> list = new LinkedList<>();
|
||||||
|
PasswordPolicy policy = session.getContext().getRealm().getPasswordPolicy();
|
||||||
|
for (String id : policy.getPolicies()) {
|
||||||
|
PasswordPolicyProvider provider = session.getProvider(PasswordPolicyProvider.class, id);
|
||||||
|
list.add(provider);
|
||||||
|
}
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,51 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||||
|
* and other contributors as indicated by the @author tags.
|
||||||
|
*
|
||||||
|
* 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 org.keycloak.policy;
|
||||||
|
|
||||||
|
import org.keycloak.Config;
|
||||||
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
import org.keycloak.models.KeycloakSessionFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||||
|
*/
|
||||||
|
public class DefaultPasswordPolicyManagerProviderFactory implements PasswordPolicyManagerProviderFactory {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PasswordPolicyManagerProvider create(KeycloakSession session) {
|
||||||
|
return new DefaultPasswordPolicyManagerProvider(session);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void init(Config.Scope config) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void postInit(KeycloakSessionFactory factory) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getId() {
|
||||||
|
return "default";
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,62 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||||
|
* and other contributors as indicated by the @author tags.
|
||||||
|
*
|
||||||
|
* 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 org.keycloak.policy;
|
||||||
|
|
||||||
|
import org.keycloak.models.KeycloakContext;
|
||||||
|
import org.keycloak.models.UserModel;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||||
|
*/
|
||||||
|
public class DigitsPasswordPolicyProvider implements PasswordPolicyProvider {
|
||||||
|
|
||||||
|
private static final String ERROR_MESSAGE = "invalidPasswordMinDigitsMessage";
|
||||||
|
|
||||||
|
private KeycloakContext context;
|
||||||
|
|
||||||
|
public DigitsPasswordPolicyProvider(KeycloakContext context) {
|
||||||
|
this.context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PolicyError validate(String username, String password) {
|
||||||
|
int min = context.getRealm().getPasswordPolicy().getPolicyConfig(DigitsPasswordPolicyProviderFactory.ID);
|
||||||
|
int count = 0;
|
||||||
|
for (char c : password.toCharArray()) {
|
||||||
|
if (Character.isDigit(c)) {
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return count < min ? new PolicyError(ERROR_MESSAGE, min) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PolicyError validate(UserModel user, String password) {
|
||||||
|
return validate(user.getUsername(), password);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object parseConfig(String value) {
|
||||||
|
return value != null ? Integer.parseInt(value) : 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,73 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||||
|
* and other contributors as indicated by the @author tags.
|
||||||
|
*
|
||||||
|
* 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 org.keycloak.policy;
|
||||||
|
|
||||||
|
import org.keycloak.Config;
|
||||||
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
import org.keycloak.models.KeycloakSessionFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||||
|
*/
|
||||||
|
public class DigitsPasswordPolicyProviderFactory implements PasswordPolicyProviderFactory {
|
||||||
|
|
||||||
|
static final String ID = "digits";
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getId() {
|
||||||
|
return ID;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PasswordPolicyProvider create(KeycloakSession session) {
|
||||||
|
return new DigitsPasswordPolicyProvider(session.getContext());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void init(Config.Scope config) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void postInit(KeycloakSessionFactory factory) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getDisplayName() {
|
||||||
|
return "Digits";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getConfigType() {
|
||||||
|
return PasswordPolicyProvider.INT_CONFIG_TYPE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getDefaultConfigValue() {
|
||||||
|
return "1";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isMultiplSupported() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,90 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||||
|
* and other contributors as indicated by the @author tags.
|
||||||
|
*
|
||||||
|
* 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 org.keycloak.policy;
|
||||||
|
|
||||||
|
import org.keycloak.Config;
|
||||||
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
import org.keycloak.models.KeycloakSessionFactory;
|
||||||
|
import org.keycloak.models.UserModel;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||||
|
*/
|
||||||
|
public class ForceExpiredPasswordPolicyProviderFactory implements PasswordPolicyProviderFactory, PasswordPolicyProvider {
|
||||||
|
|
||||||
|
public static final String ID = "forceExpiredPasswordChange";
|
||||||
|
public static final int DEFAULT_VALUE = 365;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PasswordPolicyProvider create(KeycloakSession session) {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void init(Config.Scope config) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void postInit(KeycloakSessionFactory factory) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getId() {
|
||||||
|
return ID;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PolicyError validate(UserModel user, String password) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PolicyError validate(String user, String password) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getDisplayName() {
|
||||||
|
return "Expire Password";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getConfigType() {
|
||||||
|
return PasswordPolicyProvider.STRING_CONFIG_TYPE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getDefaultConfigValue() {
|
||||||
|
return String.valueOf(DEFAULT_VALUE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isMultiplSupported() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object parseConfig(String value) {
|
||||||
|
return value != null ? Integer.parseInt(value) : DEFAULT_VALUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,91 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||||
|
* and other contributors as indicated by the @author tags.
|
||||||
|
*
|
||||||
|
* 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 org.keycloak.policy;
|
||||||
|
|
||||||
|
import org.keycloak.Config;
|
||||||
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
import org.keycloak.models.KeycloakSessionFactory;
|
||||||
|
import org.keycloak.models.UserModel;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||||
|
*/
|
||||||
|
public class HashAlgorithmPasswordPolicyProviderFactory implements PasswordPolicyProviderFactory, PasswordPolicyProvider {
|
||||||
|
|
||||||
|
public static final String DEFAULT_VALUE = "pbkdf2";
|
||||||
|
|
||||||
|
public static final String ID = "hashAlgorithm";
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PasswordPolicyProvider create(KeycloakSession session) {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void init(Config.Scope config) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void postInit(KeycloakSessionFactory factory) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getId() {
|
||||||
|
return ID;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PolicyError validate(UserModel user, String password) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PolicyError validate(String user, String password) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getDisplayName() {
|
||||||
|
return "Hashing Algorithm";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getConfigType() {
|
||||||
|
return PasswordPolicyProvider.STRING_CONFIG_TYPE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getDefaultConfigValue() {
|
||||||
|
return DEFAULT_VALUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isMultiplSupported() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object parseConfig(String value) {
|
||||||
|
return value != null ? value : DEFAULT_VALUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,91 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||||
|
* and other contributors as indicated by the @author tags.
|
||||||
|
*
|
||||||
|
* 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 org.keycloak.policy;
|
||||||
|
|
||||||
|
import org.keycloak.Config;
|
||||||
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
import org.keycloak.models.KeycloakSessionFactory;
|
||||||
|
import org.keycloak.models.UserModel;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||||
|
*/
|
||||||
|
public class HashIterationsPasswordPolicyProviderFactory implements PasswordPolicyProvider, PasswordPolicyProviderFactory {
|
||||||
|
|
||||||
|
public static final int DEFAULT_VALUE = 20000;
|
||||||
|
|
||||||
|
public static final String ID = "hashIterations";
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PasswordPolicyProvider create(KeycloakSession session) {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void init(Config.Scope config) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void postInit(KeycloakSessionFactory factory) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getId() {
|
||||||
|
return ID;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PolicyError validate(UserModel user, String password) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PolicyError validate(String user, String password) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object parseConfig(String value) {
|
||||||
|
return value != null ? Integer.parseInt(value) : DEFAULT_VALUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getDisplayName() {
|
||||||
|
return "Hashing Iterations";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getConfigType() {
|
||||||
|
return PasswordPolicyProvider.INT_CONFIG_TYPE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getDefaultConfigValue() {
|
||||||
|
return String.valueOf(DEFAULT_VALUE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isMultiplSupported() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,117 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||||
|
* and other contributors as indicated by the @author tags.
|
||||||
|
*
|
||||||
|
* 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 org.keycloak.policy;
|
||||||
|
|
||||||
|
import org.keycloak.hash.PasswordHashManager;
|
||||||
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
import org.keycloak.models.PasswordPolicy;
|
||||||
|
import org.keycloak.models.UserCredentialModel;
|
||||||
|
import org.keycloak.models.UserCredentialValueModel;
|
||||||
|
import org.keycloak.models.UserModel;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||||
|
*/
|
||||||
|
public class HistoryPasswordPolicyProvider implements PasswordPolicyProvider {
|
||||||
|
|
||||||
|
private static final String ERROR_MESSAGE = "invalidPasswordHistoryMessage";
|
||||||
|
|
||||||
|
private KeycloakSession session;
|
||||||
|
|
||||||
|
public HistoryPasswordPolicyProvider(KeycloakSession session) {
|
||||||
|
this.session = session;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PolicyError validate(String username, String password) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PolicyError validate(UserModel user, String password) {
|
||||||
|
PasswordPolicy policy = session.getContext().getRealm().getPasswordPolicy();
|
||||||
|
int passwordHistoryPolicyValue = policy.getPolicyConfig(HistoryPasswordPolicyProviderFactory.ID);
|
||||||
|
if (passwordHistoryPolicyValue != -1) {
|
||||||
|
UserCredentialValueModel cred = getCredentialValueModel(user, UserCredentialModel.PASSWORD);
|
||||||
|
if (cred != null) {
|
||||||
|
if(PasswordHashManager.verify(session, policy, password, cred)) {
|
||||||
|
return new PolicyError(ERROR_MESSAGE, passwordHistoryPolicyValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
List<UserCredentialValueModel> passwordExpiredCredentials = getCredentialValueModels(user, passwordHistoryPolicyValue - 1,
|
||||||
|
UserCredentialModel.PASSWORD_HISTORY);
|
||||||
|
for (UserCredentialValueModel credential : passwordExpiredCredentials) {
|
||||||
|
if (PasswordHashManager.verify(session, policy, password, credential)) {
|
||||||
|
return new PolicyError(ERROR_MESSAGE, passwordHistoryPolicyValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private UserCredentialValueModel getCredentialValueModel(UserModel user, String credType) {
|
||||||
|
for (UserCredentialValueModel model : user.getCredentialsDirectly()) {
|
||||||
|
if (model.getType().equals(credType)) {
|
||||||
|
return model;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<UserCredentialValueModel> getCredentialValueModels(UserModel user, int expiredPasswordsPolicyValue, String credType) {
|
||||||
|
List<UserCredentialValueModel> credentialModels = new ArrayList<UserCredentialValueModel>();
|
||||||
|
for (UserCredentialValueModel model : user.getCredentialsDirectly()) {
|
||||||
|
if (model.getType().equals(credType)) {
|
||||||
|
credentialModels.add(model);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Collections.sort(credentialModels, new Comparator<UserCredentialValueModel>() {
|
||||||
|
public int compare(UserCredentialValueModel credFirst, UserCredentialValueModel credSecond) {
|
||||||
|
if (credFirst.getCreatedDate() > credSecond.getCreatedDate()) {
|
||||||
|
return -1;
|
||||||
|
} else if (credFirst.getCreatedDate() < credSecond.getCreatedDate()) {
|
||||||
|
return 1;
|
||||||
|
} else {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (credentialModels.size() > expiredPasswordsPolicyValue) {
|
||||||
|
return credentialModels.subList(0, expiredPasswordsPolicyValue);
|
||||||
|
}
|
||||||
|
return credentialModels;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object parseConfig(String value) {
|
||||||
|
return value != null ? Integer.parseInt(value) : HistoryPasswordPolicyProviderFactory.DEFAULT_VALUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,74 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||||
|
* and other contributors as indicated by the @author tags.
|
||||||
|
*
|
||||||
|
* 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 org.keycloak.policy;
|
||||||
|
|
||||||
|
import org.keycloak.Config;
|
||||||
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
import org.keycloak.models.KeycloakSessionFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||||
|
*/
|
||||||
|
public class HistoryPasswordPolicyProviderFactory implements PasswordPolicyProviderFactory {
|
||||||
|
|
||||||
|
public static final String ID = "passwordHistory";
|
||||||
|
public static final Integer DEFAULT_VALUE = 3;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getId() {
|
||||||
|
return ID;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PasswordPolicyProvider create(KeycloakSession session) {
|
||||||
|
return new HistoryPasswordPolicyProvider(session);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void init(Config.Scope config) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void postInit(KeycloakSessionFactory factory) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getDisplayName() {
|
||||||
|
return "Not Recently Used";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getConfigType() {
|
||||||
|
return PasswordPolicyProvider.INT_CONFIG_TYPE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getDefaultConfigValue() {
|
||||||
|
return String.valueOf(HistoryPasswordPolicyProviderFactory.DEFAULT_VALUE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isMultiplSupported() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,56 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||||
|
* and other contributors as indicated by the @author tags.
|
||||||
|
*
|
||||||
|
* 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 org.keycloak.policy;
|
||||||
|
|
||||||
|
import org.keycloak.models.KeycloakContext;
|
||||||
|
import org.keycloak.models.UserModel;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||||
|
*/
|
||||||
|
public class LengthPasswordPolicyProvider implements PasswordPolicyProvider {
|
||||||
|
|
||||||
|
private static final String ERROR_MESSAGE = "invalidPasswordMinLengthMessage";
|
||||||
|
|
||||||
|
private KeycloakContext context;
|
||||||
|
|
||||||
|
public LengthPasswordPolicyProvider(KeycloakContext context) {
|
||||||
|
this.context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PolicyError validate(String username, String password) {
|
||||||
|
int min = context.getRealm().getPasswordPolicy().getPolicyConfig(LengthPasswordPolicyProviderFactory.ID);
|
||||||
|
return password.length() < min ? new PolicyError(ERROR_MESSAGE, min) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PolicyError validate(UserModel user, String password) {
|
||||||
|
return validate(user.getUsername(), password);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object parseConfig(String value) {
|
||||||
|
return value != null ? Integer.parseInt(value) : 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,73 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||||
|
* and other contributors as indicated by the @author tags.
|
||||||
|
*
|
||||||
|
* 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 org.keycloak.policy;
|
||||||
|
|
||||||
|
import org.keycloak.Config;
|
||||||
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
import org.keycloak.models.KeycloakSessionFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||||
|
*/
|
||||||
|
public class LengthPasswordPolicyProviderFactory implements PasswordPolicyProviderFactory {
|
||||||
|
|
||||||
|
static final String ID = "length";
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getId() {
|
||||||
|
return ID;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PasswordPolicyProvider create(KeycloakSession session) {
|
||||||
|
return new LengthPasswordPolicyProvider(session.getContext());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void init(Config.Scope config) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void postInit(KeycloakSessionFactory factory) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getDisplayName() {
|
||||||
|
return "Minimum Length";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getConfigType() {
|
||||||
|
return PasswordPolicyProvider.INT_CONFIG_TYPE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getDefaultConfigValue() {
|
||||||
|
return "8";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isMultiplSupported() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,62 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||||
|
* and other contributors as indicated by the @author tags.
|
||||||
|
*
|
||||||
|
* 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 org.keycloak.policy;
|
||||||
|
|
||||||
|
import org.keycloak.models.KeycloakContext;
|
||||||
|
import org.keycloak.models.UserModel;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||||
|
*/
|
||||||
|
public class LowerCasePasswordPolicyProvider implements PasswordPolicyProvider {
|
||||||
|
|
||||||
|
private static final String ERROR_MESSAGE = "invalidPasswordMinLowerCaseCharsMessage";
|
||||||
|
|
||||||
|
private KeycloakContext context;
|
||||||
|
|
||||||
|
public LowerCasePasswordPolicyProvider(KeycloakContext context) {
|
||||||
|
this.context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PolicyError validate(String username, String password) {
|
||||||
|
int min = context.getRealm().getPasswordPolicy().getPolicyConfig(LowerCasePasswordPolicyProviderFactory.ID);
|
||||||
|
int count = 0;
|
||||||
|
for (char c : password.toCharArray()) {
|
||||||
|
if (Character.isLowerCase(c)) {
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return count < min ? new PolicyError(ERROR_MESSAGE, min) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PolicyError validate(UserModel user, String password) {
|
||||||
|
return validate(user.getUsername(), password);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object parseConfig(String value) {
|
||||||
|
return value != null ? Integer.parseInt(value) : 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,73 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||||
|
* and other contributors as indicated by the @author tags.
|
||||||
|
*
|
||||||
|
* 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 org.keycloak.policy;
|
||||||
|
|
||||||
|
import org.keycloak.Config;
|
||||||
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
import org.keycloak.models.KeycloakSessionFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||||
|
*/
|
||||||
|
public class LowerCasePasswordPolicyProviderFactory implements PasswordPolicyProviderFactory {
|
||||||
|
|
||||||
|
public static final String ID = "lowerCase";
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PasswordPolicyProvider create(KeycloakSession session) {
|
||||||
|
return new LowerCasePasswordPolicyProvider(session.getContext());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void init(Config.Scope config) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void postInit(KeycloakSessionFactory factory) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getDisplayName() {
|
||||||
|
return "Lowercase Characters";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getConfigType() {
|
||||||
|
return PasswordPolicyProvider.INT_CONFIG_TYPE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getDefaultConfigValue() {
|
||||||
|
return "1";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isMultiplSupported() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getId() {
|
||||||
|
return ID;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,55 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||||
|
* and other contributors as indicated by the @author tags.
|
||||||
|
*
|
||||||
|
* 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 org.keycloak.policy;
|
||||||
|
|
||||||
|
import org.keycloak.models.KeycloakContext;
|
||||||
|
import org.keycloak.models.UserModel;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||||
|
*/
|
||||||
|
public class NotUsernamePasswordPolicyProvider implements PasswordPolicyProvider {
|
||||||
|
|
||||||
|
private static final String ERROR_MESSAGE = "invalidPasswordNotUsernameMessage";
|
||||||
|
|
||||||
|
private KeycloakContext context;
|
||||||
|
|
||||||
|
public NotUsernamePasswordPolicyProvider(KeycloakContext context) {
|
||||||
|
this.context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PolicyError validate(String username, String password) {
|
||||||
|
return username.equals(password) ? new PolicyError(ERROR_MESSAGE) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PolicyError validate(UserModel user, String password) {
|
||||||
|
return validate(user.getUsername(), password);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object parseConfig(String value) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,73 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||||
|
* and other contributors as indicated by the @author tags.
|
||||||
|
*
|
||||||
|
* 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 org.keycloak.policy;
|
||||||
|
|
||||||
|
import org.keycloak.Config;
|
||||||
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
import org.keycloak.models.KeycloakSessionFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||||
|
*/
|
||||||
|
public class NotUsernamePasswordPolicyProviderFactory implements PasswordPolicyProviderFactory {
|
||||||
|
|
||||||
|
static final String ID = "notUsername";
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getId() {
|
||||||
|
return ID;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PasswordPolicyProvider create(KeycloakSession session) {
|
||||||
|
return new NotUsernamePasswordPolicyProvider(session.getContext());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void init(Config.Scope config) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void postInit(KeycloakSessionFactory factory) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getDisplayName() {
|
||||||
|
return "Not Username";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getConfigType() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getDefaultConfigValue() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isMultiplSupported() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||||
|
* and other contributors as indicated by the @author tags.
|
||||||
|
*
|
||||||
|
* 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 org.keycloak.policy;
|
||||||
|
|
||||||
|
import org.keycloak.models.UserModel;
|
||||||
|
import org.keycloak.provider.Provider;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:roelof.naude@epiuse.com">Roelof Naude</a>
|
||||||
|
*/
|
||||||
|
public interface PasswordPolicyManagerProvider extends Provider {
|
||||||
|
|
||||||
|
PolicyError validate(UserModel user, String password);
|
||||||
|
PolicyError validate(String user, String password);
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,27 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||||
|
* and other contributors as indicated by the @author tags.
|
||||||
|
*
|
||||||
|
* 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 org.keycloak.policy;
|
||||||
|
|
||||||
|
import org.keycloak.provider.ProviderFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:roelof.naude@epiuse.com">Roelof Naude</a>
|
||||||
|
*/
|
||||||
|
public interface PasswordPolicyManagerProviderFactory extends ProviderFactory<PasswordPolicyManagerProvider> {
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,49 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||||
|
* and other contributors as indicated by the @author tags.
|
||||||
|
*
|
||||||
|
* 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 org.keycloak.policy;
|
||||||
|
|
||||||
|
import org.keycloak.provider.Provider;
|
||||||
|
import org.keycloak.provider.ProviderFactory;
|
||||||
|
import org.keycloak.provider.Spi;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||||
|
*/
|
||||||
|
public class PasswordPolicyManagerSpi implements Spi {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isInternal() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return "password-policy-manager";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Class<? extends Provider> getProviderClass() {
|
||||||
|
return PasswordPolicyManagerProvider.class;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Class<? extends ProviderFactory> getProviderFactoryClass() {
|
||||||
|
return PasswordPolicyManagerProviderFactory.class;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,35 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||||
|
* and other contributors as indicated by the @author tags.
|
||||||
|
*
|
||||||
|
* 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 org.keycloak.policy;
|
||||||
|
|
||||||
|
import org.keycloak.models.UserModel;
|
||||||
|
import org.keycloak.provider.Provider;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:roelof.naude@epiuse.com">Roelof Naude</a>
|
||||||
|
*/
|
||||||
|
public interface PasswordPolicyProvider extends Provider {
|
||||||
|
|
||||||
|
String STRING_CONFIG_TYPE = "String";
|
||||||
|
String INT_CONFIG_TYPE = "int";
|
||||||
|
|
||||||
|
PolicyError validate(UserModel user, String password);
|
||||||
|
PolicyError validate(String user, String password);
|
||||||
|
Object parseConfig(String value);
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,32 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||||
|
* and other contributors as indicated by the @author tags.
|
||||||
|
*
|
||||||
|
* 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 org.keycloak.policy;
|
||||||
|
|
||||||
|
import org.keycloak.provider.ProviderFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:roelof.naude@epiuse.com">Roelof Naude</a>
|
||||||
|
*/
|
||||||
|
public interface PasswordPolicyProviderFactory extends ProviderFactory<PasswordPolicyProvider> {
|
||||||
|
|
||||||
|
String getDisplayName();
|
||||||
|
String getConfigType();
|
||||||
|
String getDefaultConfigValue();
|
||||||
|
boolean isMultiplSupported();
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,48 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||||
|
* and other contributors as indicated by the @author tags.
|
||||||
|
*
|
||||||
|
* 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 org.keycloak.policy;
|
||||||
|
|
||||||
|
import org.keycloak.provider.Provider;
|
||||||
|
import org.keycloak.provider.ProviderFactory;
|
||||||
|
import org.keycloak.provider.Spi;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:roelof.naude@epiuse.com">Roelof Naude</a>
|
||||||
|
*/
|
||||||
|
public class PasswordPolicySpi implements Spi {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isInternal() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return "password-policy";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Class<? extends Provider> getProviderClass() {
|
||||||
|
return PasswordPolicyProvider.class;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Class<? extends ProviderFactory> getProviderFactoryClass() {
|
||||||
|
return PasswordPolicyProviderFactory.class;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,39 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||||
|
* and other contributors as indicated by the @author tags.
|
||||||
|
*
|
||||||
|
* 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 org.keycloak.policy;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||||
|
*/
|
||||||
|
public final class PolicyError {
|
||||||
|
private String message;
|
||||||
|
private Object[] parameters;
|
||||||
|
|
||||||
|
public PolicyError(String message, Object... parameters) {
|
||||||
|
this.message = message;
|
||||||
|
this.parameters = parameters;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getMessage() {
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Object[] getParameters() {
|
||||||
|
return parameters;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,66 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||||
|
* and other contributors as indicated by the @author tags.
|
||||||
|
*
|
||||||
|
* 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 org.keycloak.policy;
|
||||||
|
|
||||||
|
import org.keycloak.models.KeycloakContext;
|
||||||
|
import org.keycloak.models.UserModel;
|
||||||
|
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||||
|
*/
|
||||||
|
public class RegexPatternsPasswordPolicyProvider implements PasswordPolicyProvider {
|
||||||
|
|
||||||
|
private static final String ERROR_MESSAGE = "invalidPasswordRegexPatternMessage";
|
||||||
|
|
||||||
|
private KeycloakContext context;
|
||||||
|
|
||||||
|
public RegexPatternsPasswordPolicyProvider(KeycloakContext context) {
|
||||||
|
this.context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PolicyError validate(String username, String password) {
|
||||||
|
Pattern pattern = context.getRealm().getPasswordPolicy().getPolicyConfig(RegexPatternsPasswordPolicyProviderFactory.ID);
|
||||||
|
Matcher matcher = pattern.matcher(password);
|
||||||
|
if (!matcher.matches()) {
|
||||||
|
return new PolicyError(ERROR_MESSAGE, pattern.pattern());
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PolicyError validate(UserModel user, String password) {
|
||||||
|
return validate(user.getUsername(), password);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object parseConfig(String value) {
|
||||||
|
if (value == null) {
|
||||||
|
throw new IllegalArgumentException("Config required");
|
||||||
|
}
|
||||||
|
return Pattern.compile(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,73 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||||
|
* and other contributors as indicated by the @author tags.
|
||||||
|
*
|
||||||
|
* 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 org.keycloak.policy;
|
||||||
|
|
||||||
|
import org.keycloak.Config;
|
||||||
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
import org.keycloak.models.KeycloakSessionFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||||
|
*/
|
||||||
|
public class RegexPatternsPasswordPolicyProviderFactory implements PasswordPolicyProviderFactory {
|
||||||
|
|
||||||
|
static final String ID = "regexPattern";
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getId() {
|
||||||
|
return ID;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PasswordPolicyProvider create(KeycloakSession session) {
|
||||||
|
return new RegexPatternsPasswordPolicyProvider(session.getContext());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void init(Config.Scope config) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void postInit(KeycloakSessionFactory factory) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getDisplayName() {
|
||||||
|
return "Regular Expression";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getConfigType() {
|
||||||
|
return PasswordPolicyProvider.STRING_CONFIG_TYPE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getDefaultConfigValue() {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isMultiplSupported() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,62 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||||
|
* and other contributors as indicated by the @author tags.
|
||||||
|
*
|
||||||
|
* 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 org.keycloak.policy;
|
||||||
|
|
||||||
|
import org.keycloak.models.KeycloakContext;
|
||||||
|
import org.keycloak.models.UserModel;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||||
|
*/
|
||||||
|
public class SpecialCharsPasswordPolicyProvider implements PasswordPolicyProvider {
|
||||||
|
|
||||||
|
private static final String ERROR_MESSAGE = "invalidPasswordMinSpecialCharsMessage";
|
||||||
|
|
||||||
|
private KeycloakContext context;
|
||||||
|
|
||||||
|
public SpecialCharsPasswordPolicyProvider(KeycloakContext context) {
|
||||||
|
this.context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PolicyError validate(String username, String password) {
|
||||||
|
int min = context.getRealm().getPasswordPolicy().getPolicyConfig(SpecialCharsPasswordPolicyProviderFactory.ID);
|
||||||
|
int count = 0;
|
||||||
|
for (char c : password.toCharArray()) {
|
||||||
|
if (!Character.isLetterOrDigit(c)) {
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return count < min ? new PolicyError(ERROR_MESSAGE, min) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PolicyError validate(UserModel user, String password) {
|
||||||
|
return validate(user.getUsername(), password);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object parseConfig(String value) {
|
||||||
|
return value != null ? Integer.parseInt(value) : 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,73 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||||
|
* and other contributors as indicated by the @author tags.
|
||||||
|
*
|
||||||
|
* 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 org.keycloak.policy;
|
||||||
|
|
||||||
|
import org.keycloak.Config;
|
||||||
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
import org.keycloak.models.KeycloakSessionFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||||
|
*/
|
||||||
|
public class SpecialCharsPasswordPolicyProviderFactory implements PasswordPolicyProviderFactory {
|
||||||
|
|
||||||
|
public static final String ID = "specialChars";
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PasswordPolicyProvider create(KeycloakSession session) {
|
||||||
|
return new SpecialCharsPasswordPolicyProvider(session.getContext());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void init(Config.Scope config) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void postInit(KeycloakSessionFactory factory) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getDisplayName() {
|
||||||
|
return "Special Characters";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getConfigType() {
|
||||||
|
return PasswordPolicyProvider.INT_CONFIG_TYPE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getDefaultConfigValue() {
|
||||||
|
return "1";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isMultiplSupported() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getId() {
|
||||||
|
return ID;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,62 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||||
|
* and other contributors as indicated by the @author tags.
|
||||||
|
*
|
||||||
|
* 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 org.keycloak.policy;
|
||||||
|
|
||||||
|
import org.keycloak.models.KeycloakContext;
|
||||||
|
import org.keycloak.models.UserModel;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||||
|
*/
|
||||||
|
public class UpperCasePasswordPolicyProvider implements PasswordPolicyProvider {
|
||||||
|
|
||||||
|
private static final String ERROR_MESSAGE = "invalidPasswordMinUpperCaseCharsMessage";
|
||||||
|
|
||||||
|
private KeycloakContext context;
|
||||||
|
|
||||||
|
public UpperCasePasswordPolicyProvider(KeycloakContext context) {
|
||||||
|
this.context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PolicyError validate(String username, String password) {
|
||||||
|
int min = context.getRealm().getPasswordPolicy().getPolicyConfig(UpperCasePasswordPolicyProviderFactory.ID);
|
||||||
|
int count = 0;
|
||||||
|
for (char c : password.toCharArray()) {
|
||||||
|
if (Character.isUpperCase(c)) {
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return count < min ? new PolicyError(ERROR_MESSAGE, min) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PolicyError validate(UserModel user, String password) {
|
||||||
|
return validate(user.getUsername(), password);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object parseConfig(String value) {
|
||||||
|
return value != null ? Integer.parseInt(value) : 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,73 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||||
|
* and other contributors as indicated by the @author tags.
|
||||||
|
*
|
||||||
|
* 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 org.keycloak.policy;
|
||||||
|
|
||||||
|
import org.keycloak.Config;
|
||||||
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
import org.keycloak.models.KeycloakSessionFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||||
|
*/
|
||||||
|
public class UpperCasePasswordPolicyProviderFactory implements PasswordPolicyProviderFactory {
|
||||||
|
|
||||||
|
public static final String ID = "upperCase";
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PasswordPolicyProvider create(KeycloakSession session) {
|
||||||
|
return new UpperCasePasswordPolicyProvider(session.getContext());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void init(Config.Scope config) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void postInit(KeycloakSessionFactory factory) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getDisplayName() {
|
||||||
|
return "Uppercase Characters";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getConfigType() {
|
||||||
|
return PasswordPolicyProvider.INT_CONFIG_TYPE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getDefaultConfigValue() {
|
||||||
|
return "1";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isMultiplSupported() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getId() {
|
||||||
|
return ID;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -36,6 +36,17 @@ public class ProviderConfigProperty {
|
||||||
protected String type;
|
protected String type;
|
||||||
protected Object defaultValue;
|
protected Object defaultValue;
|
||||||
|
|
||||||
|
public ProviderConfigProperty() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public ProviderConfigProperty(String name, String label, String helpText, String type, Object defaultValue) {
|
||||||
|
this.name = name;
|
||||||
|
this.label = label;
|
||||||
|
this.helpText = helpText;
|
||||||
|
this.type = type;
|
||||||
|
this.defaultValue = defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
public String getName() {
|
public String getName() {
|
||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
#
|
||||||
|
# Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||||
|
# and other contributors as indicated by the @author tags.
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
#
|
||||||
|
|
||||||
|
org.keycloak.policy.DefaultPasswordPolicyManagerProviderFactory
|
|
@ -0,0 +1,28 @@
|
||||||
|
#
|
||||||
|
# Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||||
|
# and other contributors as indicated by the @author tags.
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
#
|
||||||
|
|
||||||
|
org.keycloak.policy.DigitsPasswordPolicyProviderFactory
|
||||||
|
org.keycloak.policy.ForceExpiredPasswordPolicyProviderFactory
|
||||||
|
org.keycloak.policy.HashAlgorithmPasswordPolicyProviderFactory
|
||||||
|
org.keycloak.policy.HashIterationsPasswordPolicyProviderFactory
|
||||||
|
org.keycloak.policy.HistoryPasswordPolicyProviderFactory
|
||||||
|
org.keycloak.policy.LengthPasswordPolicyProviderFactory
|
||||||
|
org.keycloak.policy.LowerCasePasswordPolicyProviderFactory
|
||||||
|
org.keycloak.policy.NotUsernamePasswordPolicyProviderFactory
|
||||||
|
org.keycloak.policy.RegexPatternsPasswordPolicyProviderFactory
|
||||||
|
org.keycloak.policy.SpecialCharsPasswordPolicyProviderFactory
|
||||||
|
org.keycloak.policy.UpperCasePasswordPolicyProviderFactory
|
|
@ -60,4 +60,5 @@ org.keycloak.authorization.store.StoreFactorySpi
|
||||||
org.keycloak.authorization.AuthorizationSpi
|
org.keycloak.authorization.AuthorizationSpi
|
||||||
org.keycloak.models.cache.authorization.CachedStoreFactorySpi
|
org.keycloak.models.cache.authorization.CachedStoreFactorySpi
|
||||||
org.keycloak.protocol.oidc.TokenIntrospectionSpi
|
org.keycloak.protocol.oidc.TokenIntrospectionSpi
|
||||||
|
org.keycloak.policy.PasswordPolicySpi
|
||||||
|
org.keycloak.policy.PasswordPolicyManagerSpi
|
|
@ -1,168 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
|
||||||
* and other contributors as indicated by the @author tags.
|
|
||||||
*
|
|
||||||
* 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 org.keycloak.models;
|
|
||||||
|
|
||||||
import org.junit.Assert;
|
|
||||||
import org.junit.Test;
|
|
||||||
|
|
||||||
import java.util.regex.PatternSyntaxException;
|
|
||||||
|
|
||||||
import static org.junit.Assert.fail;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
|
||||||
*/
|
|
||||||
public class PasswordPolicyTest {
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testLength() {
|
|
||||||
PasswordPolicy policy = new PasswordPolicy("length");
|
|
||||||
Assert.assertEquals("invalidPasswordMinLengthMessage", policy.validate(null, "jdoe", "1234567").getMessage());
|
|
||||||
Assert.assertArrayEquals(new Object[]{8}, policy.validate(null, "jdoe", "1234567").getParameters());
|
|
||||||
Assert.assertNull(policy.validate(null, "jdoe", "12345678"));
|
|
||||||
|
|
||||||
policy = new PasswordPolicy("length(4)");
|
|
||||||
Assert.assertEquals("invalidPasswordMinLengthMessage", policy.validate(null, "jdoe", "123").getMessage());
|
|
||||||
Assert.assertArrayEquals(new Object[]{4}, policy.validate(null, "jdoe", "123").getParameters());
|
|
||||||
Assert.assertNull(policy.validate(null, "jdoe", "1234"));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testDigits() {
|
|
||||||
PasswordPolicy policy = new PasswordPolicy("digits");
|
|
||||||
Assert.assertEquals("invalidPasswordMinDigitsMessage", policy.validate(null, "jdoe", "abcd").getMessage());
|
|
||||||
Assert.assertArrayEquals(new Object[]{1}, policy.validate(null, "jdoe", "abcd").getParameters());
|
|
||||||
Assert.assertNull(policy.validate(null, "jdoe", "abcd1"));
|
|
||||||
|
|
||||||
policy = new PasswordPolicy("digits(2)");
|
|
||||||
Assert.assertEquals("invalidPasswordMinDigitsMessage", policy.validate(null, "jdoe", "abcd1").getMessage());
|
|
||||||
Assert.assertArrayEquals(new Object[]{2}, policy.validate(null, "jdoe", "abcd1").getParameters());
|
|
||||||
Assert.assertNull(policy.validate(null, "jdoe", "abcd12"));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testLowerCase() {
|
|
||||||
PasswordPolicy policy = new PasswordPolicy("lowerCase");
|
|
||||||
Assert.assertEquals("invalidPasswordMinLowerCaseCharsMessage", policy.validate(null, "jdoe", "ABCD1234").getMessage());
|
|
||||||
Assert.assertArrayEquals(new Object[]{1}, policy.validate(null, "jdoe", "ABCD1234").getParameters());
|
|
||||||
Assert.assertNull(policy.validate(null, "jdoe", "ABcD1234"));
|
|
||||||
|
|
||||||
policy = new PasswordPolicy("lowerCase(2)");
|
|
||||||
Assert.assertEquals("invalidPasswordMinLowerCaseCharsMessage", policy.validate(null, "jdoe", "ABcD1234").getMessage());
|
|
||||||
Assert.assertArrayEquals(new Object[]{2}, policy.validate(null, "jdoe", "ABcD1234").getParameters());
|
|
||||||
Assert.assertNull(policy.validate(null, "jdoe", "aBcD1234"));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testUpperCase() {
|
|
||||||
PasswordPolicy policy = new PasswordPolicy("upperCase");
|
|
||||||
Assert.assertEquals("invalidPasswordMinUpperCaseCharsMessage", policy.validate(null, "jdoe", "abcd1234").getMessage());
|
|
||||||
Assert.assertArrayEquals(new Object[]{1}, policy.validate(null, "jdoe", "abcd1234").getParameters());
|
|
||||||
Assert.assertNull(policy.validate(null, "jdoe", "abCd1234"));
|
|
||||||
|
|
||||||
policy = new PasswordPolicy("upperCase(2)");
|
|
||||||
Assert.assertEquals("invalidPasswordMinUpperCaseCharsMessage", policy.validate(null, "jdoe", "abCd1234").getMessage());
|
|
||||||
Assert.assertArrayEquals(new Object[]{2}, policy.validate(null, "jdoe", "abCd1234").getParameters());
|
|
||||||
Assert.assertNull(policy.validate(null, "jdoe", "AbCd1234"));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testSpecialChars() {
|
|
||||||
PasswordPolicy policy = new PasswordPolicy("specialChars");
|
|
||||||
Assert.assertEquals("invalidPasswordMinSpecialCharsMessage", policy.validate(null, "jdoe", "abcd1234").getMessage());
|
|
||||||
Assert.assertArrayEquals(new Object[]{1}, policy.validate(null, "jdoe", "abcd1234").getParameters());
|
|
||||||
Assert.assertNull(policy.validate(null, "jdoe", "ab&d1234"));
|
|
||||||
|
|
||||||
policy = new PasswordPolicy("specialChars(2)");
|
|
||||||
Assert.assertEquals("invalidPasswordMinSpecialCharsMessage", policy.validate(null, "jdoe", "ab&d1234").getMessage());
|
|
||||||
Assert.assertArrayEquals(new Object[]{2}, policy.validate(null, "jdoe", "ab&d1234").getParameters());
|
|
||||||
Assert.assertNull(policy.validate(null, "jdoe", "ab&d-234"));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testNotUsername() {
|
|
||||||
PasswordPolicy policy = new PasswordPolicy("notUsername");
|
|
||||||
Assert.assertEquals("invalidPasswordNotUsernameMessage", policy.validate(null, "jdoe", "jdoe").getMessage());
|
|
||||||
Assert.assertNull(policy.validate(null, "jdoe", "ab&d1234"));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testInvalidPolicyName() {
|
|
||||||
try {
|
|
||||||
PasswordPolicy policy = new PasswordPolicy("noSuchPolicy");
|
|
||||||
Assert.fail("Expected exception");
|
|
||||||
} catch (IllegalArgumentException e) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testRegexPatterns() {
|
|
||||||
PasswordPolicy policy = null;
|
|
||||||
try {
|
|
||||||
policy = new PasswordPolicy("regexPattern");
|
|
||||||
fail("Expected NullPointerException: Regex Pattern cannot be null.");
|
|
||||||
} catch (NullPointerException e) {
|
|
||||||
// Expected NPE as regex pattern is null.
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
policy = new PasswordPolicy("regexPattern(*)");
|
|
||||||
fail("Expected PatternSyntaxException: Regex Pattern cannot be null.");
|
|
||||||
} catch (PatternSyntaxException e) {
|
|
||||||
// Expected PSE as regex pattern(or any of its token) is not quantifiable.
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
policy = new PasswordPolicy("regexPattern(*,**)");
|
|
||||||
fail("Expected PatternSyntaxException: Regex Pattern cannot be null.");
|
|
||||||
} catch (PatternSyntaxException e) {
|
|
||||||
// Expected PSE as regex pattern(or any of its token) is not quantifiable.
|
|
||||||
}
|
|
||||||
|
|
||||||
//Fails to match one of the regex pattern
|
|
||||||
policy = new PasswordPolicy("regexPattern(jdoe) and regexPattern(j*d)");
|
|
||||||
Assert.assertEquals("invalidPasswordRegexPatternMessage", policy.validate(null, "jdoe", "jdoe").getMessage());
|
|
||||||
|
|
||||||
////Fails to match all of the regex patterns
|
|
||||||
policy = new PasswordPolicy("regexPattern(j*p) and regexPattern(j*d) and regexPattern(adoe)");
|
|
||||||
Assert.assertEquals("invalidPasswordRegexPatternMessage", policy.validate(null, "jdoe", "jdoe").getMessage());
|
|
||||||
|
|
||||||
policy = new PasswordPolicy("regexPattern([a-z][a-z][a-z][a-z][0-9])");
|
|
||||||
Assert.assertEquals("invalidPasswordRegexPatternMessage", policy.validate(null, "jdoe", "jdoe").getMessage());
|
|
||||||
|
|
||||||
policy = new PasswordPolicy("regexPattern(jdoe)");
|
|
||||||
Assert.assertNull(policy.validate(null, "jdoe", "jdoe"));
|
|
||||||
|
|
||||||
policy = new PasswordPolicy("regexPattern([a-z][a-z][a-z][a-z][0-9])");
|
|
||||||
Assert.assertNull(policy.validate(null, "jdoe", "jdoe0"));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testComplex() {
|
|
||||||
PasswordPolicy policy = new PasswordPolicy("length(8) and digits(2) and lowerCase(2) and upperCase(2) and specialChars(2) and notUsername()");
|
|
||||||
Assert.assertNotNull(policy.validate(null, "jdoe", "12aaBB&"));
|
|
||||||
Assert.assertNotNull(policy.validate(null, "jdoe", "aaaaBB&-"));
|
|
||||||
Assert.assertNotNull(policy.validate(null, "jdoe", "12AABB&-"));
|
|
||||||
Assert.assertNotNull(policy.validate(null, "jdoe", "12aabb&-"));
|
|
||||||
Assert.assertNotNull(policy.validate(null, "jdoe", "12aaBBcc"));
|
|
||||||
Assert.assertNotNull(policy.validate(null, "12aaBB&-", "12aaBB&-"));
|
|
||||||
|
|
||||||
Assert.assertNull(policy.validate(null, "jdoe", "12aaBB&-"));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -33,6 +33,8 @@ import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.UserCredentialModel;
|
import org.keycloak.models.UserCredentialModel;
|
||||||
import org.keycloak.models.UserModel;
|
import org.keycloak.models.UserModel;
|
||||||
import org.keycloak.models.utils.FormMessage;
|
import org.keycloak.models.utils.FormMessage;
|
||||||
|
import org.keycloak.policy.PasswordPolicyManagerProvider;
|
||||||
|
import org.keycloak.policy.PolicyError;
|
||||||
import org.keycloak.provider.ProviderConfigProperty;
|
import org.keycloak.provider.ProviderConfigProperty;
|
||||||
import org.keycloak.representations.idm.CredentialRepresentation;
|
import org.keycloak.representations.idm.CredentialRepresentation;
|
||||||
import org.keycloak.services.messages.Messages;
|
import org.keycloak.services.messages.Messages;
|
||||||
|
@ -70,7 +72,7 @@ public class RegistrationPassword implements FormAction, FormActionFactory {
|
||||||
errors.add(new FormMessage(RegistrationPage.FIELD_PASSWORD_CONFIRM, Messages.INVALID_PASSWORD_CONFIRM));
|
errors.add(new FormMessage(RegistrationPage.FIELD_PASSWORD_CONFIRM, Messages.INVALID_PASSWORD_CONFIRM));
|
||||||
}
|
}
|
||||||
if (formData.getFirst(RegistrationPage.FIELD_PASSWORD) != null) {
|
if (formData.getFirst(RegistrationPage.FIELD_PASSWORD) != null) {
|
||||||
PasswordPolicy.Error err = context.getRealm().getPasswordPolicy().validate(context.getSession(), context.getRealm().isRegistrationEmailAsUsername() ? formData.getFirst(RegistrationPage.FIELD_EMAIL) : formData.getFirst(RegistrationPage.FIELD_USERNAME), formData.getFirst(RegistrationPage.FIELD_PASSWORD));
|
PolicyError err = context.getSession().getProvider(PasswordPolicyManagerProvider.class).validate(context.getRealm().isRegistrationEmailAsUsername() ? formData.getFirst(RegistrationPage.FIELD_EMAIL) : formData.getFirst(RegistrationPage.FIELD_USERNAME), formData.getFirst(RegistrationPage.FIELD_PASSWORD));
|
||||||
if (err != null)
|
if (err != null)
|
||||||
errors.add(new FormMessage(RegistrationPage.FIELD_PASSWORD, err.getMessage(), err.getParameters()));
|
errors.add(new FormMessage(RegistrationPage.FIELD_PASSWORD, err.getMessage(), err.getParameters()));
|
||||||
}
|
}
|
||||||
|
|
|
@ -84,6 +84,8 @@ public class ApplianceBootstrap {
|
||||||
|
|
||||||
public void createMasterRealmUser(String username, String password) {
|
public void createMasterRealmUser(String username, String password) {
|
||||||
RealmModel realm = session.realms().getRealm(Config.getAdminRealm());
|
RealmModel realm = session.realms().getRealm(Config.getAdminRealm());
|
||||||
|
session.getContext().setRealm(realm);
|
||||||
|
|
||||||
if (session.users().getUsersCount(realm) > 0) {
|
if (session.users().getUsersCount(realm) > 0) {
|
||||||
throw new IllegalStateException("Can't create initial user as users already exists");
|
throw new IllegalStateException("Can't create initial user as users already exists");
|
||||||
}
|
}
|
||||||
|
|
|
@ -220,7 +220,7 @@ public class RealmManager implements RealmImporter {
|
||||||
|
|
||||||
realm.setEventsListeners(Collections.singleton("jboss-logging"));
|
realm.setEventsListeners(Collections.singleton("jboss-logging"));
|
||||||
|
|
||||||
realm.setPasswordPolicy(new PasswordPolicy("hashIterations(20000)"));
|
realm.setPasswordPolicy(PasswordPolicy.parse(session, "hashIterations(20000)"));
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean removeRealm(RealmModel realm) {
|
public boolean removeRealm(RealmModel realm) {
|
||||||
|
|
|
@ -280,7 +280,7 @@ public class RealmAdminResource {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
RepresentationToModel.updateRealm(rep, realm);
|
RepresentationToModel.updateRealm(rep, realm, session);
|
||||||
|
|
||||||
// Refresh periodic sync tasks for configured federationProviders
|
// Refresh periodic sync tasks for configured federationProviders
|
||||||
List<UserFederationProviderModel> federationProviders = realm.getUserFederationProviders();
|
List<UserFederationProviderModel> federationProviders = realm.getUserFederationProviders();
|
||||||
|
|
|
@ -35,6 +35,11 @@ import org.keycloak.broker.provider.IdentityProvider;
|
||||||
import org.keycloak.broker.provider.IdentityProviderFactory;
|
import org.keycloak.broker.provider.IdentityProviderFactory;
|
||||||
import org.keycloak.events.EventType;
|
import org.keycloak.events.EventType;
|
||||||
import org.keycloak.events.admin.OperationType;
|
import org.keycloak.events.admin.OperationType;
|
||||||
|
import org.keycloak.models.PasswordPolicy;
|
||||||
|
import org.keycloak.policy.PasswordPolicyProvider;
|
||||||
|
import org.keycloak.policy.PasswordPolicyProviderFactory;
|
||||||
|
import org.keycloak.provider.*;
|
||||||
|
import org.keycloak.representations.idm.PasswordPolicyTypeRepresentation;
|
||||||
import org.keycloak.theme.Theme;
|
import org.keycloak.theme.Theme;
|
||||||
import org.keycloak.theme.ThemeProvider;
|
import org.keycloak.theme.ThemeProvider;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
@ -44,10 +49,6 @@ import org.keycloak.protocol.ClientInstallationProvider;
|
||||||
import org.keycloak.protocol.LoginProtocol;
|
import org.keycloak.protocol.LoginProtocol;
|
||||||
import org.keycloak.protocol.LoginProtocolFactory;
|
import org.keycloak.protocol.LoginProtocolFactory;
|
||||||
import org.keycloak.protocol.ProtocolMapper;
|
import org.keycloak.protocol.ProtocolMapper;
|
||||||
import org.keycloak.provider.ProviderConfigProperty;
|
|
||||||
import org.keycloak.provider.ProviderFactory;
|
|
||||||
import org.keycloak.provider.ServerInfoAwareProviderFactory;
|
|
||||||
import org.keycloak.provider.Spi;
|
|
||||||
import org.keycloak.representations.idm.ConfigPropertyRepresentation;
|
import org.keycloak.representations.idm.ConfigPropertyRepresentation;
|
||||||
import org.keycloak.representations.idm.ProtocolMapperRepresentation;
|
import org.keycloak.representations.idm.ProtocolMapperRepresentation;
|
||||||
import org.keycloak.representations.idm.ProtocolMapperTypeRepresentation;
|
import org.keycloak.representations.idm.ProtocolMapperTypeRepresentation;
|
||||||
|
@ -88,6 +89,7 @@ public class ServerInfoAdminResource {
|
||||||
setProtocolMapperTypes(info);
|
setProtocolMapperTypes(info);
|
||||||
setBuiltinProtocolMappers(info);
|
setBuiltinProtocolMappers(info);
|
||||||
setClientInstallations(info);
|
setClientInstallations(info);
|
||||||
|
setPasswordPolicies(info);
|
||||||
info.setEnums(ENUMS);
|
info.setEnums(ENUMS);
|
||||||
return info;
|
return info;
|
||||||
}
|
}
|
||||||
|
@ -248,6 +250,20 @@ public class ServerInfoAdminResource {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void setPasswordPolicies(ServerInfoRepresentation info) {
|
||||||
|
info.setPasswordPolicies(new LinkedList<>());
|
||||||
|
for (ProviderFactory f : session.getKeycloakSessionFactory().getProviderFactories(PasswordPolicyProvider.class)) {
|
||||||
|
PasswordPolicyProviderFactory factory = (PasswordPolicyProviderFactory) f;
|
||||||
|
PasswordPolicyTypeRepresentation rep = new PasswordPolicyTypeRepresentation();
|
||||||
|
rep.setId(factory.getId());
|
||||||
|
rep.setDisplayName(factory.getDisplayName());
|
||||||
|
rep.setConfigType(factory.getConfigType());
|
||||||
|
rep.setDefaultValue(factory.getDefaultConfigValue());
|
||||||
|
rep.setMultipleSupported(factory.isMultiplSupported());
|
||||||
|
info.getPasswordPolicies().add(rep);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static Map<String, List<String>> createEnumsMap(Class... enums) {
|
private static Map<String, List<String>> createEnumsMap(Class... enums) {
|
||||||
Map<String, List<String>> m = new HashMap<>();
|
Map<String, List<String>> m = new HashMap<>();
|
||||||
for (Class e : enums) {
|
for (Class e : enums) {
|
||||||
|
|
|
@ -22,6 +22,8 @@ import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.PasswordPolicy;
|
import org.keycloak.models.PasswordPolicy;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.utils.FormMessage;
|
import org.keycloak.models.utils.FormMessage;
|
||||||
|
import org.keycloak.policy.PasswordPolicyManagerProvider;
|
||||||
|
import org.keycloak.policy.PolicyError;
|
||||||
import org.keycloak.representations.idm.CredentialRepresentation;
|
import org.keycloak.representations.idm.CredentialRepresentation;
|
||||||
import org.keycloak.services.messages.Messages;
|
import org.keycloak.services.messages.Messages;
|
||||||
|
|
||||||
|
@ -73,7 +75,7 @@ public class Validation {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (formData.getFirst(FIELD_PASSWORD) != null) {
|
if (formData.getFirst(FIELD_PASSWORD) != null) {
|
||||||
PasswordPolicy.Error err = policy.validate(session, realm.isRegistrationEmailAsUsername()?formData.getFirst(FIELD_EMAIL):formData.getFirst(FIELD_USERNAME), formData.getFirst(FIELD_PASSWORD));
|
PolicyError err = session.getProvider(PasswordPolicyManagerProvider.class).validate(realm.isRegistrationEmailAsUsername() ? formData.getFirst(FIELD_EMAIL) : formData.getFirst(FIELD_USERNAME), formData.getFirst(FIELD_PASSWORD));
|
||||||
if (err != null)
|
if (err != null)
|
||||||
errors.add(new FormMessage(FIELD_PASSWORD, err.getMessage(), err.getParameters()));
|
errors.add(new FormMessage(FIELD_PASSWORD, err.getMessage(), err.getParameters()));
|
||||||
}
|
}
|
||||||
|
|
|
@ -351,8 +351,7 @@ public class ResourceOwnerPasswordCredentialsGrantTest extends AbstractKeycloakT
|
||||||
public void grantAccessTokenExpiredPassword() throws Exception {
|
public void grantAccessTokenExpiredPassword() throws Exception {
|
||||||
|
|
||||||
RealmResource realmResource = adminClient.realm("test");
|
RealmResource realmResource = adminClient.realm("test");
|
||||||
RealmManager.realm(realmResource).passwordPolicy(
|
RealmManager.realm(realmResource).passwordPolicy("forceExpiredPasswordChange(1)");
|
||||||
new PasswordPolicy("forceExpiredPasswordChange(1)").toString());
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
setTimeOffset(60 * 60 * 48);
|
setTimeOffset(60 * 60 * 48);
|
||||||
|
@ -376,7 +375,7 @@ public class ResourceOwnerPasswordCredentialsGrantTest extends AbstractKeycloakT
|
||||||
.user((String) null)
|
.user((String) null)
|
||||||
.assertEvent();
|
.assertEvent();
|
||||||
} finally {
|
} finally {
|
||||||
RealmManager.realm(realmResource).passwordPolicy(new PasswordPolicy("").toString());
|
RealmManager.realm(realmResource).passwordPolicy("");
|
||||||
UserManager.realm(realmResource).username("test-user@localhost")
|
UserManager.realm(realmResource).username("test-user@localhost")
|
||||||
.removeRequiredAction(UserModel.RequiredAction.UPDATE_PASSWORD.toString());
|
.removeRequiredAction(UserModel.RequiredAction.UPDATE_PASSWORD.toString());
|
||||||
}
|
}
|
||||||
|
|
|
@ -164,11 +164,11 @@ public class AdapterTest extends AbstractModelTest {
|
||||||
Assert.assertTrue(userProvider.validCredentials(session, realmModel, user, UserCredentialModel.password("geheim")));
|
Assert.assertTrue(userProvider.validCredentials(session, realmModel, user, UserCredentialModel.password("geheim")));
|
||||||
List<UserCredentialValueModel> creds = user.getCredentialsDirectly();
|
List<UserCredentialValueModel> creds = user.getCredentialsDirectly();
|
||||||
Assert.assertEquals(creds.get(0).getHashIterations(), 20000);
|
Assert.assertEquals(creds.get(0).getHashIterations(), 20000);
|
||||||
realmModel.setPasswordPolicy(new PasswordPolicy("hashIterations(200)"));
|
realmModel.setPasswordPolicy(PasswordPolicy.parse(realmManager.getSession(), "hashIterations(200)"));
|
||||||
Assert.assertTrue(userProvider.validCredentials(session, realmModel, user, UserCredentialModel.password("geheim")));
|
Assert.assertTrue(userProvider.validCredentials(session, realmModel, user, UserCredentialModel.password("geheim")));
|
||||||
creds = user.getCredentialsDirectly();
|
creds = user.getCredentialsDirectly();
|
||||||
Assert.assertEquals(creds.get(0).getHashIterations(), 200);
|
Assert.assertEquals(creds.get(0).getHashIterations(), 200);
|
||||||
realmModel.setPasswordPolicy(new PasswordPolicy("hashIterations(1)"));
|
realmModel.setPasswordPolicy(PasswordPolicy.parse(realmManager.getSession(), "hashIterations(1)"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
|
@ -42,7 +42,7 @@ public class ModelTest extends AbstractModelTest {
|
||||||
realm.setSslRequired(SslRequired.EXTERNAL);
|
realm.setSslRequired(SslRequired.EXTERNAL);
|
||||||
realm.setVerifyEmail(true);
|
realm.setVerifyEmail(true);
|
||||||
realm.setAccessTokenLifespan(1000);
|
realm.setAccessTokenLifespan(1000);
|
||||||
realm.setPasswordPolicy(new PasswordPolicy("length"));
|
realm.setPasswordPolicy(PasswordPolicy.parse(realmManager.getSession(), "length"));
|
||||||
realm.setAccessCodeLifespan(1001);
|
realm.setAccessCodeLifespan(1001);
|
||||||
realm.setAccessCodeLifespanUserAction(1002);
|
realm.setAccessCodeLifespanUserAction(1002);
|
||||||
KeycloakModelUtils.generateRealmKeys(realm);
|
KeycloakModelUtils.generateRealmKeys(realm);
|
||||||
|
|
|
@ -0,0 +1,185 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||||
|
* and other contributors as indicated by the @author tags.
|
||||||
|
*
|
||||||
|
* 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 org.keycloak.testsuite.model;
|
||||||
|
|
||||||
|
import org.junit.Assert;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.keycloak.models.PasswordPolicy;
|
||||||
|
import org.keycloak.models.RealmModel;
|
||||||
|
import org.keycloak.policy.PasswordPolicyManagerProvider;
|
||||||
|
|
||||||
|
import java.util.regex.PatternSyntaxException;
|
||||||
|
|
||||||
|
import static org.junit.Assert.fail;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||||
|
*/
|
||||||
|
public class PasswordPolicyTest extends AbstractModelTest {
|
||||||
|
|
||||||
|
private RealmModel realmModel;
|
||||||
|
private PasswordPolicyManagerProvider policyManager;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void before() throws Exception {
|
||||||
|
super.before();
|
||||||
|
realmModel = realmManager.createRealm("JUGGLER");
|
||||||
|
session.getContext().setRealm(realmModel);
|
||||||
|
policyManager = session.getProvider(PasswordPolicyManagerProvider.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testLength() {
|
||||||
|
realmModel.setPasswordPolicy(PasswordPolicy.parse(session, "length"));
|
||||||
|
|
||||||
|
Assert.assertEquals("invalidPasswordMinLengthMessage", policyManager.validate("jdoe", "1234567").getMessage());
|
||||||
|
Assert.assertArrayEquals(new Object[]{8}, policyManager.validate("jdoe", "1234567").getParameters());
|
||||||
|
Assert.assertNull(policyManager.validate("jdoe", "12345678"));
|
||||||
|
|
||||||
|
realmModel.setPasswordPolicy(PasswordPolicy.parse(session, "length(4)"));
|
||||||
|
|
||||||
|
Assert.assertEquals("invalidPasswordMinLengthMessage", policyManager.validate("jdoe", "123").getMessage());
|
||||||
|
Assert.assertArrayEquals(new Object[]{4}, policyManager.validate("jdoe", "123").getParameters());
|
||||||
|
Assert.assertNull(policyManager.validate("jdoe", "1234"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDigits() {
|
||||||
|
realmModel.setPasswordPolicy(PasswordPolicy.parse(session, "digits"));
|
||||||
|
Assert.assertEquals("invalidPasswordMinDigitsMessage", policyManager.validate("jdoe", "abcd").getMessage());
|
||||||
|
Assert.assertArrayEquals(new Object[]{1}, policyManager.validate("jdoe", "abcd").getParameters());
|
||||||
|
Assert.assertNull(policyManager.validate("jdoe", "abcd1"));
|
||||||
|
|
||||||
|
realmModel.setPasswordPolicy(PasswordPolicy.parse(session, "digits(2)"));
|
||||||
|
Assert.assertEquals("invalidPasswordMinDigitsMessage", policyManager.validate("jdoe", "abcd1").getMessage());
|
||||||
|
Assert.assertArrayEquals(new Object[]{2}, policyManager.validate("jdoe", "abcd1").getParameters());
|
||||||
|
Assert.assertNull(policyManager.validate("jdoe", "abcd12"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testLowerCase() {
|
||||||
|
realmModel.setPasswordPolicy(PasswordPolicy.parse(session, "lowerCase"));
|
||||||
|
Assert.assertEquals("invalidPasswordMinLowerCaseCharsMessage", policyManager.validate("jdoe", "ABCD1234").getMessage());
|
||||||
|
Assert.assertArrayEquals(new Object[]{1}, policyManager.validate("jdoe", "ABCD1234").getParameters());
|
||||||
|
Assert.assertNull(policyManager.validate("jdoe", "ABcD1234"));
|
||||||
|
|
||||||
|
realmModel.setPasswordPolicy(PasswordPolicy.parse(session, "lowerCase(2)"));
|
||||||
|
Assert.assertEquals("invalidPasswordMinLowerCaseCharsMessage", policyManager.validate("jdoe", "ABcD1234").getMessage());
|
||||||
|
Assert.assertArrayEquals(new Object[]{2}, policyManager.validate("jdoe", "ABcD1234").getParameters());
|
||||||
|
Assert.assertNull(policyManager.validate("jdoe", "aBcD1234"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testUpperCase() {
|
||||||
|
realmModel.setPasswordPolicy(PasswordPolicy.parse(session, "upperCase"));
|
||||||
|
Assert.assertEquals("invalidPasswordMinUpperCaseCharsMessage", policyManager.validate("jdoe", "abcd1234").getMessage());
|
||||||
|
Assert.assertArrayEquals(new Object[]{1}, policyManager.validate("jdoe", "abcd1234").getParameters());
|
||||||
|
Assert.assertNull(policyManager.validate("jdoe", "abCd1234"));
|
||||||
|
|
||||||
|
realmModel.setPasswordPolicy(PasswordPolicy.parse(session, "upperCase(2)"));
|
||||||
|
Assert.assertEquals("invalidPasswordMinUpperCaseCharsMessage", policyManager.validate("jdoe", "abCd1234").getMessage());
|
||||||
|
Assert.assertArrayEquals(new Object[]{2}, policyManager.validate("jdoe", "abCd1234").getParameters());
|
||||||
|
Assert.assertNull(policyManager.validate("jdoe", "AbCd1234"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSpecialChars() {
|
||||||
|
realmModel.setPasswordPolicy(PasswordPolicy.parse(session, "specialChars"));
|
||||||
|
Assert.assertEquals("invalidPasswordMinSpecialCharsMessage", policyManager.validate("jdoe", "abcd1234").getMessage());
|
||||||
|
Assert.assertArrayEquals(new Object[]{1}, policyManager.validate("jdoe", "abcd1234").getParameters());
|
||||||
|
Assert.assertNull(policyManager.validate("jdoe", "ab&d1234"));
|
||||||
|
|
||||||
|
realmModel.setPasswordPolicy(PasswordPolicy.parse(session, "specialChars(2)"));
|
||||||
|
Assert.assertEquals("invalidPasswordMinSpecialCharsMessage", policyManager.validate("jdoe", "ab&d1234").getMessage());
|
||||||
|
Assert.assertArrayEquals(new Object[]{2}, policyManager.validate("jdoe", "ab&d1234").getParameters());
|
||||||
|
Assert.assertNull(policyManager.validate("jdoe", "ab&d-234"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testNotUsername() {
|
||||||
|
realmModel.setPasswordPolicy(PasswordPolicy.parse(session, "notUsername"));
|
||||||
|
Assert.assertEquals("invalidPasswordNotUsernameMessage", policyManager.validate("jdoe", "jdoe").getMessage());
|
||||||
|
Assert.assertNull(policyManager.validate("jdoe", "ab&d1234"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testInvalidPolicyName() {
|
||||||
|
try {
|
||||||
|
realmModel.setPasswordPolicy(PasswordPolicy.parse(session, "noSuchPolicy"));
|
||||||
|
Assert.fail("Expected exception");
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testRegexPatterns() {
|
||||||
|
PasswordPolicy policy = null;
|
||||||
|
try {
|
||||||
|
realmModel.setPasswordPolicy(PasswordPolicy.parse(session, "regexPattern"));
|
||||||
|
fail("Expected NullPointerException: Regex Pattern cannot be null.");
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
// Expected NPE as regex pattern is null.
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
realmModel.setPasswordPolicy(PasswordPolicy.parse(session, "regexPattern(*)"));
|
||||||
|
fail("Expected PatternSyntaxException: Regex Pattern cannot be null.");
|
||||||
|
} catch (PatternSyntaxException e) {
|
||||||
|
// Expected PSE as regex pattern(or any of its token) is not quantifiable.
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
realmModel.setPasswordPolicy(PasswordPolicy.parse(session, "regexPattern(*,**)"));
|
||||||
|
fail("Expected PatternSyntaxException: Regex Pattern cannot be null.");
|
||||||
|
} catch (PatternSyntaxException e) {
|
||||||
|
// Expected PSE as regex pattern(or any of its token) is not quantifiable.
|
||||||
|
}
|
||||||
|
|
||||||
|
//Fails to match one of the regex pattern
|
||||||
|
realmModel.setPasswordPolicy(PasswordPolicy.parse(session, "regexPattern(jdoe) and regexPattern(j*d)"));
|
||||||
|
Assert.assertEquals("invalidPasswordRegexPatternMessage", policyManager.validate("jdoe", "jdoe").getMessage());
|
||||||
|
|
||||||
|
////Fails to match all of the regex patterns
|
||||||
|
realmModel.setPasswordPolicy(PasswordPolicy.parse(session, "regexPattern(j*p) and regexPattern(j*d) and regexPattern(adoe)"));
|
||||||
|
Assert.assertEquals("invalidPasswordRegexPatternMessage", policyManager.validate("jdoe", "jdoe").getMessage());
|
||||||
|
|
||||||
|
realmModel.setPasswordPolicy(PasswordPolicy.parse(session, "regexPattern([a-z][a-z][a-z][a-z][0-9])"));
|
||||||
|
Assert.assertEquals("invalidPasswordRegexPatternMessage", policyManager.validate("jdoe", "jdoe").getMessage());
|
||||||
|
|
||||||
|
realmModel.setPasswordPolicy(PasswordPolicy.parse(session, "regexPattern(jdoe)"));
|
||||||
|
Assert.assertNull(policyManager.validate("jdoe", "jdoe"));
|
||||||
|
|
||||||
|
realmModel.setPasswordPolicy(PasswordPolicy.parse(session, "regexPattern([a-z][a-z][a-z][a-z][0-9])"));
|
||||||
|
Assert.assertNull(policyManager.validate("jdoe", "jdoe0"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testComplex() {
|
||||||
|
realmModel.setPasswordPolicy(PasswordPolicy.parse(session, "length(8) and digits(2) and lowerCase(2) and upperCase(2) and specialChars(2) and notUsername()"));
|
||||||
|
Assert.assertNotNull(policyManager.validate("jdoe", "12aaBB&"));
|
||||||
|
Assert.assertNotNull(policyManager.validate("jdoe", "aaaaBB&-"));
|
||||||
|
Assert.assertNotNull(policyManager.validate("jdoe", "12AABB&-"));
|
||||||
|
Assert.assertNotNull(policyManager.validate("jdoe", "12aabb&-"));
|
||||||
|
Assert.assertNotNull(policyManager.validate("jdoe", "12aaBBcc"));
|
||||||
|
Assert.assertNotNull(policyManager.validate("12aaBB&-", "12aaBB&-"));
|
||||||
|
|
||||||
|
Assert.assertNull(policyManager.validate("jdoe", "12aaBB&-"));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -1639,6 +1639,9 @@ module.config([ '$routeProvider', function($routeProvider) {
|
||||||
resolve : {
|
resolve : {
|
||||||
realm : function(RealmLoader) {
|
realm : function(RealmLoader) {
|
||||||
return RealmLoader();
|
return RealmLoader();
|
||||||
|
},
|
||||||
|
serverInfo : function(ServerInfoLoader) {
|
||||||
|
return ServerInfoLoader();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
controller : 'RealmPasswordPolicyCtrl'
|
controller : 'RealmPasswordPolicyCtrl'
|
||||||
|
|
|
@ -429,68 +429,84 @@ module.controller('RealmCacheCtrl', function($scope, realm, RealmClearUserCache,
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
module.controller('RealmPasswordPolicyCtrl', function($scope, Realm, realm, $http, $location, Dialog, Notifications, PasswordPolicy) {
|
module.controller('RealmPasswordPolicyCtrl', function($scope, Realm, realm, $http, $location, $route, Dialog, Notifications, serverInfo) {
|
||||||
console.log('RealmPasswordPolicyCtrl');
|
var parse = function(policyString) {
|
||||||
|
var policies = [];
|
||||||
$scope.realm = realm;
|
if (!policyString || policyString.length == 0){
|
||||||
|
return policies;
|
||||||
var oldCopy = angular.copy($scope.realm);
|
|
||||||
|
|
||||||
$scope.allPolicies = PasswordPolicy.allPolicies;
|
|
||||||
$scope.policyMessages = PasswordPolicy.policyMessages;
|
|
||||||
|
|
||||||
$scope.policy = PasswordPolicy.parse(realm.passwordPolicy);
|
|
||||||
var oldPolicy = angular.copy($scope.policy);
|
|
||||||
|
|
||||||
$scope.addPolicy = function(policy){
|
|
||||||
if (!$scope.policy) {
|
|
||||||
$scope.policy = [];
|
|
||||||
}
|
}
|
||||||
if (policy.name === 'regexPattern') {
|
|
||||||
for (var i in $scope.allPolicies) {
|
var policyArray = policyString.split(" and ");
|
||||||
var p = $scope.allPolicies[i];
|
|
||||||
if (p.name === 'regexPattern') {
|
for (var i = 0; i < policyArray.length; i ++){
|
||||||
$scope.allPolicies[i] = { name: 'regexPattern', value: '' };
|
var policyToken = policyArray[i];
|
||||||
|
var id;
|
||||||
|
var value;
|
||||||
|
if (policyToken.indexOf('(') == -1) {
|
||||||
|
id = policyToken.trim();
|
||||||
|
} else {
|
||||||
|
id = policyToken.substring(0, policyToken.indexOf('('));
|
||||||
|
value = policyToken.substring(policyToken.indexOf('(') + 1, policyToken.indexOf(')')).trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var j = 0; j < serverInfo.passwordPolicies.length; j++) {
|
||||||
|
if (serverInfo.passwordPolicies[j].id == id) {
|
||||||
|
var p = serverInfo.passwordPolicies[j];
|
||||||
|
p.value = value && value || p.defaultValue;
|
||||||
|
policies.push(p);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return policies;
|
||||||
|
};
|
||||||
|
|
||||||
|
var toString = function(policies) {
|
||||||
|
if (!policies || policies.length == 0) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
var policyString = "";
|
||||||
|
for (var i = 0; i < policies.length; i++) {
|
||||||
|
policyString += policies[i].id;
|
||||||
|
if (policies[i].value && policies[i].value != policies[i].defaultValue) {
|
||||||
|
policyString += '(' + policies[i].value + ')';
|
||||||
|
}
|
||||||
|
policyString += " and ";
|
||||||
|
}
|
||||||
|
policyString = policyString.substring(0, policyString.length - 5);
|
||||||
|
return policyString;
|
||||||
|
}
|
||||||
|
|
||||||
|
$scope.realm = realm;
|
||||||
|
$scope.serverInfo = serverInfo;
|
||||||
|
$scope.changed = false; $scope.policy = parse(realm.passwordPolicy);
|
||||||
|
|
||||||
|
$scope.addPolicy = function(policy){
|
||||||
|
policy.value = policy.defaultValue;
|
||||||
|
if (!$scope.policy) {
|
||||||
|
$scope.policy = [];
|
||||||
|
}
|
||||||
$scope.policy.push(policy);
|
$scope.policy.push(policy);
|
||||||
|
$scope.changed = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
$scope.removePolicy = function(index){
|
$scope.removePolicy = function(index){
|
||||||
$scope.policy.splice(index, 1);
|
$scope.policy.splice(index, 1);
|
||||||
|
$scope.changed = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
$scope.changed = false;
|
|
||||||
|
|
||||||
$scope.$watch('realm', function() {
|
|
||||||
if (!angular.equals($scope.realm, oldCopy)) {
|
|
||||||
$scope.changed = true;
|
|
||||||
}
|
|
||||||
}, true);
|
|
||||||
|
|
||||||
$scope.$watch('policy', function(oldVal, newVal) {
|
|
||||||
if (!angular.equals($scope.policy, oldPolicy)) {
|
|
||||||
$scope.realm.passwordPolicy = PasswordPolicy.toString($scope.policy);
|
|
||||||
$scope.changed = true;
|
|
||||||
}
|
|
||||||
}, true);
|
|
||||||
|
|
||||||
$scope.save = function() {
|
$scope.save = function() {
|
||||||
$scope.changed = false;
|
$scope.changed = false;
|
||||||
|
$scope.realm.passwordPolicy = toString($scope.policy);
|
||||||
|
console.debug($scope.realm.passwordPolicy);
|
||||||
|
|
||||||
Realm.update($scope.realm, function () {
|
Realm.update($scope.realm, function () {
|
||||||
$location.url("/realms/" + realm.realm + "/authentication/password-policy");
|
$location.url("/realms/" + realm.realm + "/authentication/password-policy");
|
||||||
Notifications.success("Your changes have been saved to the realm.");
|
Notifications.success("Your changes have been saved to the realm.");
|
||||||
oldCopy = angular.copy($scope.realm);
|
|
||||||
oldPolicy = angular.copy($scope.policy);
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.reset = function() {
|
$scope.reset = function() {
|
||||||
$scope.realm = angular.copy(oldCopy);
|
$route.reload();
|
||||||
$scope.policy = angular.copy(oldPolicy);
|
|
||||||
$scope.changed = false;
|
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -1238,90 +1238,6 @@ module.factory('TimeUnit2', function() {
|
||||||
return t;
|
return t;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
module.factory('PasswordPolicy', function() {
|
|
||||||
var p = {};
|
|
||||||
|
|
||||||
p.policyMessages = {
|
|
||||||
hashAlgorithm: "Default hashing algorithm. Default is 'pbkdf2'.",
|
|
||||||
hashIterations: "Number of hashing iterations. Default is 1. Recommended is 50000.",
|
|
||||||
length: "Minimal password length (integer type). Default value is 8.",
|
|
||||||
digits: "Minimal number (integer type) of digits in password. Default value is 1.",
|
|
||||||
lowerCase: "Minimal number (integer type) of lowercase characters in password. Default value is 1.",
|
|
||||||
upperCase: "Minimal number (integer type) of uppercase characters in password. Default value is 1.",
|
|
||||||
specialChars: "Minimal number (integer type) of special characters in password. Default value is 1.",
|
|
||||||
notUsername: "Block passwords that are equal to the username",
|
|
||||||
regexPattern: "Block passwords that do not match the regex pattern (string type).",
|
|
||||||
passwordHistory: "Block passwords that are equal to previous passwords. Default value is 3.",
|
|
||||||
forceExpiredPasswordChange: "Force password change when password credential is expired. Default value is 365 days."
|
|
||||||
}
|
|
||||||
|
|
||||||
p.allPolicies = [
|
|
||||||
{ name: 'hashAlgorithm', value: 'pbkdf2' },
|
|
||||||
{ name: 'hashIterations', value: 1 },
|
|
||||||
{ name: 'length', value: 8 },
|
|
||||||
{ name: 'digits', value: 1 },
|
|
||||||
{ name: 'lowerCase', value: 1 },
|
|
||||||
{ name: 'upperCase', value: 1 },
|
|
||||||
{ name: 'specialChars', value: 1 },
|
|
||||||
{ name: 'notUsername', value: 1 },
|
|
||||||
{ name: 'regexPattern', value: ''},
|
|
||||||
{ name: 'passwordHistory', value: 3 },
|
|
||||||
{ name: 'forceExpiredPasswordChange', value: 365 }
|
|
||||||
];
|
|
||||||
|
|
||||||
p.parse = function(policyString) {
|
|
||||||
var policies = [];
|
|
||||||
var re, policyEntry;
|
|
||||||
|
|
||||||
if (!policyString || policyString.length == 0){
|
|
||||||
return policies;
|
|
||||||
}
|
|
||||||
|
|
||||||
var policyArray = policyString.split(" and ");
|
|
||||||
|
|
||||||
for (var i = 0; i < policyArray.length; i ++){
|
|
||||||
var policyToken = policyArray[i];
|
|
||||||
|
|
||||||
if(policyToken.indexOf('hashAlgorithm') === 0 || policyToken.indexOf('regexPattern') === 0) {
|
|
||||||
re = /(\w+)\((.*)\)/;
|
|
||||||
policyEntry = re.exec(policyToken);
|
|
||||||
if (null !== policyEntry) {
|
|
||||||
policies.push({ name: policyEntry[1], value: policyEntry[2] });
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
re = /(\w+)\(*(\d*)\)*/;
|
|
||||||
policyEntry = re.exec(policyToken);
|
|
||||||
if (null !== policyEntry) {
|
|
||||||
policies.push({ name: policyEntry[1], value: parseInt(policyEntry[2]) });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return policies;
|
|
||||||
};
|
|
||||||
|
|
||||||
p.toString = function(policies) {
|
|
||||||
if (!policies || policies.length == 0) {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
var policyString = "";
|
|
||||||
|
|
||||||
for (var i = 0; i < policies.length; i++) {
|
|
||||||
policyString += policies[i].name;
|
|
||||||
if ( policies[i].value ){
|
|
||||||
policyString += '(' + policies[i].value + ')';
|
|
||||||
}
|
|
||||||
policyString += " and ";
|
|
||||||
}
|
|
||||||
|
|
||||||
policyString = policyString.substring(0, policyString.length - 5);
|
|
||||||
|
|
||||||
return policyString;
|
|
||||||
};
|
|
||||||
|
|
||||||
return p;
|
|
||||||
});
|
|
||||||
|
|
||||||
module.filter('removeSelectedPolicies', function() {
|
module.filter('removeSelectedPolicies', function() {
|
||||||
return function(policies, selectedPolicies) {
|
return function(policies, selectedPolicies) {
|
||||||
var result = [];
|
var result = [];
|
||||||
|
@ -1329,7 +1245,7 @@ module.filter('removeSelectedPolicies', function() {
|
||||||
var policy = policies[i];
|
var policy = policies[i];
|
||||||
var policyAvailable = true;
|
var policyAvailable = true;
|
||||||
for(var j in selectedPolicies) {
|
for(var j in selectedPolicies) {
|
||||||
if(policy.name === selectedPolicies[j].name && policy.name !== 'regexPattern') {
|
if(policy.id === selectedPolicies[j].id && !policy.multipleSupported) {
|
||||||
policyAvailable = false;
|
policyAvailable = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,12 +7,12 @@
|
||||||
<table class="table table-striped table-bordered">
|
<table class="table table-striped table-bordered">
|
||||||
<caption class="hidden">{{:: 'table-of-password-policies' | translate}}</caption>
|
<caption class="hidden">{{:: 'table-of-password-policies' | translate}}</caption>
|
||||||
<thead>
|
<thead>
|
||||||
<tr ng-show="(allPolicies|removeSelectedPolicies:policy).length > 0">
|
<tr ng-show="(serverInfo.passwordPolicies|removeSelectedPolicies:policy).length > 0">
|
||||||
<th colspan="5" class="kc-table-actions">
|
<th colspan="5" class="kc-table-actions">
|
||||||
<div class="pull-right">
|
<div class="pull-right">
|
||||||
<div>
|
<div>
|
||||||
<select class="form-control" ng-model="selectedPolicy"
|
<select class="form-control" ng-model="selectedPolicy"
|
||||||
ng-options="(p.name|capitalize) for p in (allPolicies|removeSelectedPolicies:policy)"
|
ng-options="policy as policy.displayName for policy in (serverInfo.passwordPolicies|removeSelectedPolicies:policy) track by policy.id"
|
||||||
data-ng-change="addPolicy(selectedPolicy); selectedPolicy = null">
|
data-ng-change="addPolicy(selectedPolicy); selectedPolicy = null">
|
||||||
<option value="" disabled selected>{{:: 'add-policy.placeholder' | translate}}</option>
|
<option value="" disabled selected>{{:: 'add-policy.placeholder' | translate}}</option>
|
||||||
</select>
|
</select>
|
||||||
|
@ -28,10 +28,9 @@
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr ng-repeat="p in policy">
|
<tr ng-repeat="p in policy">
|
||||||
<td>{{p.name|capitalize}}</td>
|
<td>{{p.displayName}}</td>
|
||||||
<td>
|
<td>
|
||||||
<input class="form-control" ng-model="p.value" ng-show="p.name != 'notUsername' "
|
<input type="text" class="form-control" ng-model="p.value" ng-show="p.configType" data-ng-required="!p.configType && !p.defaultValue">
|
||||||
placeholder="{{:: 'no-value-assigned.placeholder' | translate}}" min="1" required>
|
|
||||||
</td>
|
</td>
|
||||||
<td class="kc-action-cell" ng-click="removePolicy($index)">{{:: 'delete' | translate}}</td>
|
<td class="kc-action-cell" ng-click="removePolicy($index)">{{:: 'delete' | translate}}</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
Loading…
Reference in a new issue