mirror of
https://github.com/keycloak/keycloak.git
synced 2026-05-26 13:50:48 +00:00
Rename blacklist to denylist in password policy
Replaces "blacklist" terminology with "denylist" across the password policy implementation as part of adopting more inclusive naming conventions. Changes include class names, method names, variable names, comments, and test fixtures. Breaking changes have been intentionally avoided, this PR only includes internal renames that do not impact operators or public APIs. Closes #48865 Signed-off-by: Faseela K <faseela.k@est.tech>
This commit is contained in:
@@ -67,7 +67,7 @@
|
||||
<groupId>net.java.dev.jna</groupId>
|
||||
<artifactId>jna</artifactId>
|
||||
</dependency>
|
||||
<!-- This is here because BloomFilter is used in the Javadoc within BlacklistPasswordPolicyProviderFactory -->
|
||||
<!-- This is here because BloomFilter is used in the Javadoc within DenylistPasswordPolicyProviderFactory -->
|
||||
<dependency>
|
||||
<groupId>com.google.guava</groupId>
|
||||
<artifactId>guava</artifactId>
|
||||
|
||||
+2
-2
@@ -74,7 +74,7 @@ import org.keycloak.connections.jpa.JpaConnectionSpi;
|
||||
import org.keycloak.connections.jpa.updater.liquibase.LiquibaseJpaUpdaterProviderFactory;
|
||||
import org.keycloak.connections.jpa.updater.liquibase.conn.DefaultLiquibaseConnectionProvider;
|
||||
import org.keycloak.infinispan.util.InfinispanUtils;
|
||||
import org.keycloak.policy.BlacklistPasswordPolicyProviderFactory;
|
||||
import org.keycloak.policy.DenylistPasswordPolicyProviderFactory;
|
||||
import org.keycloak.protocol.ProtocolMapperSpi;
|
||||
import org.keycloak.protocol.oidc.mappers.DeployedScriptOIDCProtocolMapper;
|
||||
import org.keycloak.protocol.saml.mappers.DeployedScriptSAMLProtocolMapper;
|
||||
@@ -211,7 +211,7 @@ class KeycloakProcessor {
|
||||
LiquibaseJpaUpdaterProviderFactory.class,
|
||||
FilesKeystoreVaultProviderFactory.class,
|
||||
FilesPlainTextVaultProviderFactory.class,
|
||||
BlacklistPasswordPolicyProviderFactory.class,
|
||||
DenylistPasswordPolicyProviderFactory.class,
|
||||
ClasspathThemeResourceProviderFactory.class,
|
||||
JarThemeProviderFactory.class);
|
||||
|
||||
|
||||
+3
-3
@@ -21,7 +21,7 @@ import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
import org.keycloak.policy.BlacklistPasswordPolicyProviderFactory;
|
||||
import org.keycloak.policy.DenylistPasswordPolicyProviderFactory;
|
||||
|
||||
import picocli.CommandLine.Command;
|
||||
import picocli.CommandLine.Option;
|
||||
@@ -33,7 +33,7 @@ import picocli.CommandLine.Parameters;
|
||||
header = BuildPasswordDenylist.HEADER,
|
||||
sortOptions = false,
|
||||
description = "%n" + BuildPasswordDenylist.HEADER
|
||||
+ "%n%nKeycloak's password-blacklist policy rejects passwords found in a plaintext denylist file."
|
||||
+ "%n%nKeycloak's password-denylist policy rejects passwords found in a plaintext denylist file."
|
||||
+ " For large lists, loading from plaintext on every startup or reload can take seconds."
|
||||
+ " Run this command once after creating or updating DENYLIST_FILE to generate a pre-computed"
|
||||
+ " .bloom file. To use it, configure the password policy with the .bloom filename instead of"
|
||||
@@ -99,7 +99,7 @@ public class BuildPasswordDenylist extends AbstractCommand {
|
||||
|
||||
try {
|
||||
long startMs = System.currentTimeMillis();
|
||||
BlacklistPasswordPolicyProviderFactory.buildBloomFile(inputFile, outputFile, fpp);
|
||||
DenylistPasswordPolicyProviderFactory.buildBloomFile(inputFile, outputFile, fpp);
|
||||
long elapsedMs = System.currentTimeMillis() - startMs;
|
||||
long outputSizeBytes = Files.size(outputFile);
|
||||
String sizeStr;
|
||||
|
||||
+4
-4
@@ -19,20 +19,20 @@ package org.keycloak.quarkus.runtime.policy;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import org.keycloak.policy.BlacklistPasswordPolicyProviderFactory;
|
||||
import org.keycloak.policy.DenylistPasswordPolicyProviderFactory;
|
||||
import org.keycloak.quarkus.runtime.Environment;
|
||||
|
||||
/**
|
||||
* <p>Quarkus implementation of the BlacklistPasswordPolicyProviderFactory. The
|
||||
* <p>Quarkus implementation of the DenylistPasswordPolicyProviderFactory. The
|
||||
* default path for the list files is calculated using the quarkus environment
|
||||
* class, in order to obtain the correct <em>data</em> directory.
|
||||
*
|
||||
* @author rmartinc
|
||||
*/
|
||||
public class QuarkusBlacklistPasswordPolicyProviderFactory extends BlacklistPasswordPolicyProviderFactory {
|
||||
public class QuarkusDenylistPasswordPolicyProviderFactory extends DenylistPasswordPolicyProviderFactory {
|
||||
|
||||
@Override
|
||||
public String getDefaultBlacklistsBasePath() {
|
||||
public String getDefaultDenylistsBasePath() {
|
||||
return Environment.getDataDir().map(d -> d + File.separator + PASSWORD_BLACKLISTS_FOLDER).orElse(null);
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -15,4 +15,4 @@
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
org.keycloak.quarkus.runtime.policy.QuarkusBlacklistPasswordPolicyProviderFactory
|
||||
org.keycloak.quarkus.runtime.policy.QuarkusDenylistPasswordPolicyProviderFactory
|
||||
|
||||
+15
-15
@@ -3,23 +3,23 @@ package org.keycloak.policy;
|
||||
import org.keycloak.models.KeycloakContext;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.policy.BlacklistPasswordPolicyProviderFactory.FileBasedPasswordBlacklist;
|
||||
import org.keycloak.policy.BlacklistPasswordPolicyProviderFactory.PasswordBlacklist;
|
||||
import org.keycloak.policy.DenylistPasswordPolicyProviderFactory.FileBasedPasswordDenylist;
|
||||
import org.keycloak.policy.DenylistPasswordPolicyProviderFactory.PasswordDenylist;
|
||||
|
||||
/**
|
||||
* Checks a password against a configured password blacklist.
|
||||
* Checks a password against a configured password denylist.
|
||||
*
|
||||
* @author <a href="mailto:thomas.darimont@gmail.com">Thomas Darimont</a>
|
||||
*/
|
||||
public class BlacklistPasswordPolicyProvider implements PasswordPolicyProvider {
|
||||
public class DenylistPasswordPolicyProvider implements PasswordPolicyProvider {
|
||||
|
||||
public static final String ERROR_MESSAGE = "invalidPasswordBlacklistedMessage";
|
||||
|
||||
private final KeycloakContext context;
|
||||
|
||||
private final BlacklistPasswordPolicyProviderFactory factory;
|
||||
private final DenylistPasswordPolicyProviderFactory factory;
|
||||
|
||||
public BlacklistPasswordPolicyProvider(KeycloakContext context, BlacklistPasswordPolicyProviderFactory factory) {
|
||||
public DenylistPasswordPolicyProvider(KeycloakContext context, DenylistPasswordPolicyProviderFactory factory) {
|
||||
this.context = context;
|
||||
this.factory = factory;
|
||||
}
|
||||
@@ -34,18 +34,18 @@ public class BlacklistPasswordPolicyProvider implements PasswordPolicyProvider {
|
||||
@Override
|
||||
public PolicyError validate(String username, String password) {
|
||||
|
||||
Object policyConfig = context.getRealm().getPasswordPolicy().getPolicyConfig(BlacklistPasswordPolicyProviderFactory.ID);
|
||||
Object policyConfig = context.getRealm().getPasswordPolicy().getPolicyConfig(DenylistPasswordPolicyProviderFactory.ID);
|
||||
if (policyConfig == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!(policyConfig instanceof PasswordBlacklist)) {
|
||||
if (!(policyConfig instanceof PasswordDenylist)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
PasswordBlacklist blacklist = (FileBasedPasswordBlacklist) policyConfig;
|
||||
PasswordDenylist denylist = (FileBasedPasswordDenylist) policyConfig;
|
||||
|
||||
if (!blacklist.contains(password.toLowerCase())) {
|
||||
if (!denylist.contains(password.toLowerCase())) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -58,7 +58,7 @@ public class BlacklistPasswordPolicyProvider implements PasswordPolicyProvider {
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the allowed configuration for a {@link BlacklistPasswordPolicyProvider}.
|
||||
* Parses the allowed configuration for a {@link DenylistPasswordPolicyProvider}.
|
||||
* Supported syntax is {@¢ode passwordBlacklist(fileName)}
|
||||
*
|
||||
* Example configurations:
|
||||
@@ -66,17 +66,17 @@ public class BlacklistPasswordPolicyProvider implements PasswordPolicyProvider {
|
||||
* <li>{@code passwordBlacklist(test-password-blacklist.txt)}</li>
|
||||
* </ul>
|
||||
*
|
||||
* @param blacklistName
|
||||
* @param denylistName
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public Object parseConfig(String blacklistName) {
|
||||
public Object parseConfig(String denylistName) {
|
||||
|
||||
if (blacklistName == null) {
|
||||
if (denylistName == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return factory.resolvePasswordBlacklist(blacklistName);
|
||||
return factory.resolvePasswordDenylist(denylistName);
|
||||
}
|
||||
|
||||
@Override
|
||||
+57
-57
@@ -46,14 +46,14 @@ import com.google.common.hash.Funnels;
|
||||
import org.jboss.logging.Logger;
|
||||
|
||||
/**
|
||||
* Creates {@link BlacklistPasswordPolicyProvider} instances.
|
||||
* Creates {@link DenylistPasswordPolicyProvider} instances.
|
||||
* <p>
|
||||
* Password blacklists are simple text files where every line is a blacklisted password delimited by a newline character {@code \n}.
|
||||
* <p>Blacklists can be configured via the <em>Authentication: Password Policy</em> section in the admin-console.
|
||||
* A blacklist-file is referred to by its name in the policy configuration.
|
||||
* Password denylists are simple text files where every line is a denylisted password delimited by a newline character {@code \n}.
|
||||
* <p>Denylists can be configured via the <em>Authentication: Password Policy</em> section in the admin-console.
|
||||
* A denylist-file is referred to by its name in the policy configuration.
|
||||
*
|
||||
* <h1>Blacklist location</h1>
|
||||
* <p>Users can provide custom blacklists by adding a blacklist password file to the configured blacklist folder.
|
||||
* <h1>Denylist location</h1>
|
||||
* <p>Users can provide custom denylists by adding a denylist password file to the configured denylist folder.
|
||||
* <p>
|
||||
* <p>The location of the password-blacklists folder is derived as follows</p>
|
||||
* <ol>
|
||||
@@ -62,15 +62,15 @@ import org.jboss.logging.Logger;
|
||||
* <li>otherwise {@code $KC_HOME/data/password-blacklists/} if nothing else is configured</li>
|
||||
* </ol>
|
||||
*
|
||||
* To configure the blacklist folder via CLI use {@code --spi-password-policy-password-blacklist-blacklists-path=/path/to/blacklistsFolder}
|
||||
* To configure the denylist folder via CLI use {@code --spi-password-policy-password-blacklist-blacklists-path=/path/to/denylistsFolder}
|
||||
*
|
||||
* <p>Note that the preferred way for configuration is to copy the password file to the {@code $KC_HOME/data/password-blacklists/} folder</p>
|
||||
* <p>A password blacklist with the filename {@code 10_million_passwords.txt}
|
||||
* <p>A password denylist with the filename {@code 10_million_passwords.txt}
|
||||
* that is located beneath {@code $KC_HOME/data/keycloak/blacklists/} can be referred to as {@code 10_million_passwords.txt} in the <em>Authentication: Password Policy</em> configuration.
|
||||
*
|
||||
* <h1>False positives</h1>
|
||||
* <p>
|
||||
* The current implementation uses a probabilistic data-structure called {@link BloomFilter} which allows for fast and memory efficient containment checks, e.g. whether a given password is contained in a blacklist,
|
||||
* The current implementation uses a probabilistic data-structure called {@link BloomFilter} which allows for fast and memory efficient containment checks, e.g. whether a given password is contained in a denylist,
|
||||
* with the possibility for false positives. By default a false positive probability {@link #DEFAULT_FALSE_POSITIVE_PROBABILITY} is used.
|
||||
*
|
||||
* To change the false positive probability via CLI configuration use {@code --spi-password-policy-password-blacklist-false-positive-probability=0.00001}
|
||||
@@ -78,9 +78,9 @@ import org.jboss.logging.Logger;
|
||||
*
|
||||
* @author <a href="mailto:thomas.darimont@gmail.com">Thomas Darimont</a>
|
||||
*/
|
||||
public class BlacklistPasswordPolicyProviderFactory implements PasswordPolicyProviderFactory {
|
||||
public class DenylistPasswordPolicyProviderFactory implements PasswordPolicyProviderFactory {
|
||||
|
||||
private static final Logger LOG = Logger.getLogger(BlacklistPasswordPolicyProviderFactory.class);
|
||||
private static final Logger LOG = Logger.getLogger(DenylistPasswordPolicyProviderFactory.class);
|
||||
|
||||
public static final String ID = "passwordBlacklist";
|
||||
|
||||
@@ -100,22 +100,22 @@ public class BlacklistPasswordPolicyProviderFactory implements PasswordPolicyPro
|
||||
|
||||
public static final String PASSWORD_BLACKLISTS_FOLDER = "password-blacklists" + File.separator;
|
||||
|
||||
private final ConcurrentMap<String, FileBasedPasswordBlacklist> blacklistRegistry = new ConcurrentHashMap<>();
|
||||
private final ConcurrentMap<String, FileBasedPasswordDenylist> denylistRegistry = new ConcurrentHashMap<>();
|
||||
|
||||
private volatile Path blacklistsBasePath;
|
||||
private volatile Path denylistsBasePath;
|
||||
|
||||
private Config.Scope config;
|
||||
|
||||
@Override
|
||||
public PasswordPolicyProvider create(KeycloakSession session) {
|
||||
if (this.blacklistsBasePath == null) {
|
||||
if (this.denylistsBasePath == null) {
|
||||
synchronized (this) {
|
||||
if (this.blacklistsBasePath == null) {
|
||||
this.blacklistsBasePath = FileBasedPasswordBlacklist.detectBlacklistsBasePath(config, this::getDefaultBlacklistsBasePath);
|
||||
if (this.denylistsBasePath == null) {
|
||||
this.denylistsBasePath = FileBasedPasswordDenylist.detectDenylistsBasePath(config, this::getDefaultDenylistsBasePath);
|
||||
}
|
||||
}
|
||||
}
|
||||
return new BlacklistPasswordPolicyProvider(session.getContext(), this);
|
||||
return new DenylistPasswordPolicyProvider(session.getContext(), this);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -164,28 +164,28 @@ public class BlacklistPasswordPolicyProviderFactory implements PasswordPolicyPro
|
||||
* @return The default path used by the provider to lookup the lists
|
||||
* when no other configuration is in place.
|
||||
*/
|
||||
public String getDefaultBlacklistsBasePath() {
|
||||
public String getDefaultDenylistsBasePath() {
|
||||
return System.getProperty(JBOSS_SERVER_DATA_DIR) + File.separator + PASSWORD_BLACKLISTS_FOLDER;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves and potentially registers a {@link PasswordBlacklist} for the given {@code blacklistName}.
|
||||
* Resolves and potentially registers a {@link PasswordDenylist} for the given {@code denylistName}.
|
||||
*
|
||||
* @param blacklistName
|
||||
* @param denylistName
|
||||
* @return
|
||||
*/
|
||||
public PasswordBlacklist resolvePasswordBlacklist(String blacklistName) {
|
||||
public PasswordDenylist resolvePasswordDenylist(String denylistName) {
|
||||
|
||||
Objects.requireNonNull(blacklistName, "blacklistName");
|
||||
Objects.requireNonNull(denylistName, "denylistName");
|
||||
|
||||
String listName = blacklistName.trim();
|
||||
String listName = denylistName.trim();
|
||||
if (listName.isEmpty()) {
|
||||
throw new IllegalArgumentException("Password blacklist name must not be empty!");
|
||||
throw new IllegalArgumentException("Password denylist name must not be empty!");
|
||||
}
|
||||
|
||||
return blacklistRegistry.computeIfAbsent(listName, (name) -> {
|
||||
return denylistRegistry.computeIfAbsent(listName, (name) -> {
|
||||
double fpp = getFalsePositiveProbability();
|
||||
return new FileBasedPasswordBlacklist(this.blacklistsBasePath, name, fpp, getCheckIntervalSeconds() * 1000L);
|
||||
return new FileBasedPasswordDenylist(this.denylistsBasePath, name, fpp, getCheckIntervalSeconds() * 1000L);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -278,19 +278,19 @@ public class BlacklistPasswordPolicyProviderFactory implements PasswordPolicyPro
|
||||
}
|
||||
|
||||
/**
|
||||
* A {@link PasswordBlacklist} describes a list of too easy to guess
|
||||
* A {@link PasswordDenylist} describes a list of too easy to guess
|
||||
* or potentially leaked passwords that users should not be able to use.
|
||||
*/
|
||||
public interface PasswordBlacklist {
|
||||
public interface PasswordDenylist {
|
||||
|
||||
|
||||
/**
|
||||
* @return the logical name of the {@link PasswordBlacklist}
|
||||
* @return the logical name of the {@link PasswordDenylist}
|
||||
*/
|
||||
String getName();
|
||||
|
||||
/**
|
||||
* Checks whether a given {@code password} is contained in this {@link PasswordBlacklist}.
|
||||
* Checks whether a given {@code password} is contained in this {@link PasswordDenylist}.
|
||||
*
|
||||
* @param password
|
||||
* @return
|
||||
@@ -299,31 +299,31 @@ public class BlacklistPasswordPolicyProviderFactory implements PasswordPolicyPro
|
||||
}
|
||||
|
||||
/**
|
||||
* A {@link FileBasedPasswordBlacklist} uses password-blacklist files as
|
||||
* to construct a {@link PasswordBlacklist}.
|
||||
* A {@link FileBasedPasswordDenylist} uses password-denylist files
|
||||
* to construct a {@link PasswordDenylist}.
|
||||
* <p>
|
||||
* This implementation uses a dynamically sized {@link BloomFilter}
|
||||
* with a provided default false positive probability.
|
||||
*
|
||||
* @see BloomFilter
|
||||
*/
|
||||
public static class FileBasedPasswordBlacklist implements PasswordBlacklist {
|
||||
public static class FileBasedPasswordDenylist implements PasswordDenylist {
|
||||
|
||||
private static final int BUFFER_SIZE_IN_BYTES = 512 * 1024;
|
||||
|
||||
/**
|
||||
* The name of the blacklist filename.
|
||||
* The name of the denylist filename.
|
||||
*/
|
||||
private final String name;
|
||||
|
||||
/**
|
||||
* The concrete path to the password-blacklist file.
|
||||
* The concrete path to the password-denylist file.
|
||||
*/
|
||||
private final Path path;
|
||||
|
||||
private final double falsePositiveProbability;
|
||||
|
||||
private volatile BloomFilter<String> blacklist;
|
||||
private volatile BloomFilter<String> denylist;
|
||||
|
||||
private final long checkIntervalMillis;
|
||||
|
||||
@@ -333,7 +333,7 @@ public class BlacklistPasswordPolicyProviderFactory implements PasswordPolicyPro
|
||||
|
||||
private long lastSizeBytes;
|
||||
|
||||
public FileBasedPasswordBlacklist(Path blacklistBasePath, String name, double falsePositiveProbability, long checkIntervalMillis) {
|
||||
public FileBasedPasswordDenylist(Path denylistBasePath, String name, double falsePositiveProbability, long checkIntervalMillis) {
|
||||
|
||||
if (name.contains("/")) {
|
||||
// disallow '/' to avoid accidental filesystem traversal
|
||||
@@ -341,17 +341,17 @@ public class BlacklistPasswordPolicyProviderFactory implements PasswordPolicyPro
|
||||
}
|
||||
|
||||
this.name = name;
|
||||
this.path = blacklistBasePath.resolve(name);
|
||||
this.path = denylistBasePath.resolve(name);
|
||||
this.falsePositiveProbability = falsePositiveProbability;
|
||||
this.checkIntervalMillis = checkIntervalMillis;
|
||||
|
||||
if (!Files.exists(this.path)) {
|
||||
throw new IllegalArgumentException("Password blacklist " + name + " not found!");
|
||||
throw new IllegalArgumentException("Password denylist " + name + " not found!");
|
||||
}
|
||||
|
||||
this.lastModifiedMillis = path.toFile().lastModified();
|
||||
this.lastSizeBytes = path.toFile().length();
|
||||
this.blacklist = load();
|
||||
this.denylist = load();
|
||||
this.lastCheckedMillis = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
@@ -365,7 +365,7 @@ public class BlacklistPasswordPolicyProviderFactory implements PasswordPolicyPro
|
||||
|
||||
public boolean contains(String password) {
|
||||
reloadIfNeeded();
|
||||
return blacklist.mightContain(password);
|
||||
return denylist.mightContain(password);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -389,12 +389,12 @@ public class BlacklistPasswordPolicyProviderFactory implements PasswordPolicyPro
|
||||
long currentModified = Files.getLastModifiedTime(path).toMillis();
|
||||
long currentSize = Files.size(path);
|
||||
if (currentModified != lastModifiedMillis || currentSize != lastSizeBytes) {
|
||||
blacklist = load();
|
||||
denylist = load();
|
||||
lastModifiedMillis = currentModified;
|
||||
lastSizeBytes = currentSize;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LOG.warnf("Failed to reload blacklist %s, continuing with cached version: %s", name, e.getMessage());
|
||||
LOG.warnf("Failed to reload denylist %s, continuing with cached version: %s", name, e.getMessage());
|
||||
}
|
||||
lastCheckedMillis = now;
|
||||
}
|
||||
@@ -452,10 +452,10 @@ public class BlacklistPasswordPolicyProviderFactory implements PasswordPolicyPro
|
||||
*/
|
||||
private BloomFilter<String> loadFromPlaintext() {
|
||||
try {
|
||||
LOG.infof("Loading blacklist start: name=%s path=%s", name, path);
|
||||
LOG.infof("Loading denylist start: name=%s path=%s", name, path);
|
||||
long loadStartMillis = System.currentTimeMillis();
|
||||
|
||||
long passwordCount = countPasswordsInBlacklistFile();
|
||||
long passwordCount = countPasswordsInDenylistFile();
|
||||
double fpp = getFalsePositiveProbability();
|
||||
|
||||
BloomFilter<String> filter = BloomFilter.create(
|
||||
@@ -467,12 +467,12 @@ public class BlacklistPasswordPolicyProviderFactory implements PasswordPolicyPro
|
||||
|
||||
double expectedFfp = filter.expectedFpp();
|
||||
long loadTimeMillis = System.currentTimeMillis() - loadStartMillis;
|
||||
LOG.infof("Loading blacklist finished: name=%s passwords=%s path=%s falsePositiveProbability=%s expectedFalsePositiveProbability=%s loadTime=%dms",
|
||||
LOG.infof("Loading denylist finished: name=%s passwords=%s path=%s falsePositiveProbability=%s expectedFalsePositiveProbability=%s loadTime=%dms",
|
||||
name, passwordCount, path, fpp, expectedFfp, loadTimeMillis);
|
||||
|
||||
return filter;
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Loading blacklist failed: Could not load password blacklist path=" + path, e);
|
||||
throw new RuntimeException("Loading denylist failed: Could not load password denylist path=" + path, e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -483,16 +483,16 @@ public class BlacklistPasswordPolicyProviderFactory implements PasswordPolicyPro
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines password blacklist size to correctly size the {@link BloomFilter} backing this blacklist.
|
||||
* Determines password denylist size to correctly size the {@link BloomFilter} backing this denylist.
|
||||
*
|
||||
* @return number of passwords found in the blacklist file
|
||||
* @return number of passwords found in the denylist file
|
||||
* @throws IOException
|
||||
*/
|
||||
private long countPasswordsInBlacklistFile() throws IOException {
|
||||
private long countPasswordsInDenylistFile() throws IOException {
|
||||
|
||||
/*
|
||||
* TODO find a more efficient way to determine the password count,
|
||||
* e.g. require a header-line in the password-blacklist file
|
||||
* e.g. require a header-line in the password-denylist file
|
||||
*/
|
||||
try (BufferedReader br = newReader(path)) {
|
||||
return br.lines().count();
|
||||
@@ -504,7 +504,7 @@ public class BlacklistPasswordPolicyProviderFactory implements PasswordPolicyPro
|
||||
}
|
||||
|
||||
/**
|
||||
* Discovers password blacklists location.
|
||||
* Discovers password denylists location.
|
||||
* <p>
|
||||
* The following discovery options are currently implemented:
|
||||
* <p>
|
||||
@@ -516,10 +516,10 @@ public class BlacklistPasswordPolicyProviderFactory implements PasswordPolicyPro
|
||||
*
|
||||
* @param config spi config
|
||||
* @param defaultPathSupplier default path to use if not specified in a system prop or configuration
|
||||
* @return the detected blacklist path
|
||||
* @throws IllegalStateException if no blacklist folder could be detected
|
||||
* @return the detected denylist path
|
||||
* @throws IllegalStateException if no denylist folder could be detected
|
||||
*/
|
||||
private static Path detectBlacklistsBasePath(Config.Scope config, Supplier<String> defaultPathSupplier) {
|
||||
private static Path detectDenylistsBasePath(Config.Scope config, Supplier<String> defaultPathSupplier) {
|
||||
|
||||
String pathFromSysProperty = System.getProperty(SYSTEM_PROPERTY);
|
||||
if (pathFromSysProperty != null) {
|
||||
@@ -538,7 +538,7 @@ public class BlacklistPasswordPolicyProviderFactory implements PasswordPolicyPro
|
||||
|
||||
if (!Files.exists(Paths.get(pathFromJbossDataPath))) {
|
||||
if (!Paths.get(pathFromJbossDataPath).toFile().mkdirs()) {
|
||||
LOG.errorf("Could not create folder for password blacklists: %s", pathFromJbossDataPath);
|
||||
LOG.errorf("Could not create folder for password denylists: %s", pathFromJbossDataPath);
|
||||
}
|
||||
}
|
||||
return ensureExists(Paths.get(pathFromJbossDataPath));
|
||||
@@ -552,7 +552,7 @@ public class BlacklistPasswordPolicyProviderFactory implements PasswordPolicyPro
|
||||
return path;
|
||||
}
|
||||
|
||||
throw new IllegalStateException("Password blacklists location does not exist: " + path);
|
||||
throw new IllegalStateException("Password denylists location does not exist: " + path);
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -28,7 +28,7 @@ org.keycloak.policy.NotContainsUsernamePasswordPolicyProviderFactory
|
||||
org.keycloak.policy.RegexPatternsPasswordPolicyProviderFactory
|
||||
org.keycloak.policy.SpecialCharsPasswordPolicyProviderFactory
|
||||
org.keycloak.policy.UpperCasePasswordPolicyProviderFactory
|
||||
org.keycloak.policy.BlacklistPasswordPolicyProviderFactory
|
||||
org.keycloak.policy.DenylistPasswordPolicyProviderFactory
|
||||
org.keycloak.policy.NotEmailPasswordPolicyProviderFactory
|
||||
org.keycloak.policy.RecoveryCodesWarningThresholdPasswordPolicyProviderFactory
|
||||
org.keycloak.policy.MaxAuthAgePasswordPolicyProviderFactory
|
||||
|
||||
+52
-60
@@ -6,41 +6,41 @@ import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.nio.file.attribute.FileTime;
|
||||
|
||||
import org.keycloak.policy.BlacklistPasswordPolicyProviderFactory.FileBasedPasswordBlacklist;
|
||||
import org.keycloak.policy.DenylistPasswordPolicyProviderFactory.FileBasedPasswordDenylist;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.TemporaryFolder;
|
||||
|
||||
public class BlacklistPasswordPolicyProviderTest {
|
||||
public class DenylistPasswordPolicyProviderTest {
|
||||
|
||||
@Rule
|
||||
public TemporaryFolder tempFolder = new TemporaryFolder();
|
||||
|
||||
@Test
|
||||
public void testPasswordLookupIsCaseInsensitive() {
|
||||
FileBasedPasswordBlacklist blacklist =
|
||||
new FileBasedPasswordBlacklist(Paths.get("src/test/java/org/keycloak/policy"), "short_blacklist.txt",
|
||||
BlacklistPasswordPolicyProviderFactory.DEFAULT_FALSE_POSITIVE_PROBABILITY,
|
||||
BlacklistPasswordPolicyProviderFactory.DEFAULT_CHECK_INTERVAL_SECONDS * 1000L);
|
||||
FileBasedPasswordDenylist denylist =
|
||||
new FileBasedPasswordDenylist(Paths.get("src/test/java/org/keycloak/policy"), "short_denylist.txt",
|
||||
DenylistPasswordPolicyProviderFactory.DEFAULT_FALSE_POSITIVE_PROBABILITY,
|
||||
DenylistPasswordPolicyProviderFactory.DEFAULT_CHECK_INTERVAL_SECONDS * 1000L);
|
||||
|
||||
// passwords in the deny list are stored in lower case; lookups must be case-insensitive
|
||||
Assert.assertFalse(blacklist.contains("1Password!"));
|
||||
Assert.assertTrue(blacklist.contains("1Password!".toLowerCase()));
|
||||
Assert.assertFalse(denylist.contains("1Password!"));
|
||||
Assert.assertTrue(denylist.contains("1Password!".toLowerCase()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReloadOnFileMtimeChange() throws Exception {
|
||||
Path file = tempFolder.newFile("blacklist.txt").toPath();
|
||||
Path file = tempFolder.newFile("denylist.txt").toPath();
|
||||
Files.writeString(file, "oldpassword\n");
|
||||
|
||||
FileBasedPasswordBlacklist blacklist =
|
||||
new FileBasedPasswordBlacklist(tempFolder.getRoot().toPath(), "blacklist.txt",
|
||||
BlacklistPasswordPolicyProviderFactory.DEFAULT_FALSE_POSITIVE_PROBABILITY, 1); // Use 1 msec check interval for not to delay the test too much.
|
||||
FileBasedPasswordDenylist denylist =
|
||||
new FileBasedPasswordDenylist(tempFolder.getRoot().toPath(), "denylist.txt",
|
||||
DenylistPasswordPolicyProviderFactory.DEFAULT_FALSE_POSITIVE_PROBABILITY, 1); // Use 1 msec check interval for not to delay the test too much.
|
||||
|
||||
Assert.assertTrue(blacklist.contains("oldpassword"));
|
||||
Assert.assertFalse(blacklist.contains("newpassword"));
|
||||
Assert.assertTrue(denylist.contains("oldpassword"));
|
||||
Assert.assertFalse(denylist.contains("newpassword"));
|
||||
|
||||
// Wait for the check interval to elapse.
|
||||
// Rewrite the file, and bump the mtime by 1 sec to ensure a detectable change regardless of the filesystem's mtime granularity.
|
||||
@@ -48,22 +48,22 @@ public class BlacklistPasswordPolicyProviderTest {
|
||||
Files.writeString(file, "newpassword\n");
|
||||
Files.setLastModifiedTime(file, FileTime.fromMillis(Files.getLastModifiedTime(file).toMillis() + 1000));
|
||||
|
||||
Assert.assertFalse(blacklist.contains("oldpassword"));
|
||||
Assert.assertTrue(blacklist.contains("newpassword"));
|
||||
Assert.assertFalse(denylist.contains("oldpassword"));
|
||||
Assert.assertTrue(denylist.contains("newpassword"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReloadOnFileSizeChange() throws Exception {
|
||||
Path file = tempFolder.newFile("blacklist.txt").toPath();
|
||||
Path file = tempFolder.newFile("denylist.txt").toPath();
|
||||
Files.writeString(file, "oldpassword\n");
|
||||
FileTime originalMtime = Files.getLastModifiedTime(file);
|
||||
|
||||
FileBasedPasswordBlacklist blacklist =
|
||||
new FileBasedPasswordBlacklist(tempFolder.getRoot().toPath(), "blacklist.txt",
|
||||
BlacklistPasswordPolicyProviderFactory.DEFAULT_FALSE_POSITIVE_PROBABILITY, 1); // Use 1 msec check interval for not to delay the test too much.
|
||||
FileBasedPasswordDenylist denylist =
|
||||
new FileBasedPasswordDenylist(tempFolder.getRoot().toPath(), "denylist.txt",
|
||||
DenylistPasswordPolicyProviderFactory.DEFAULT_FALSE_POSITIVE_PROBABILITY, 1); // Use 1 msec check interval for not to delay the test too much.
|
||||
|
||||
Assert.assertTrue(blacklist.contains("oldpassword"));
|
||||
Assert.assertFalse(blacklist.contains("newpassword"));
|
||||
Assert.assertTrue(denylist.contains("oldpassword"));
|
||||
Assert.assertFalse(denylist.contains("newpassword"));
|
||||
|
||||
// Wait for the check interval to elapse.
|
||||
// Rewrite the content while preserving the original mtime to simulate filesystems
|
||||
@@ -72,41 +72,39 @@ public class BlacklistPasswordPolicyProviderTest {
|
||||
Files.writeString(file, "newpassword\nanotherpassword\n");
|
||||
Files.setLastModifiedTime(file, originalMtime);
|
||||
|
||||
Assert.assertFalse(blacklist.contains("oldpassword"));
|
||||
Assert.assertTrue(blacklist.contains("newpassword"));
|
||||
Assert.assertFalse(denylist.contains("oldpassword"));
|
||||
Assert.assertTrue(denylist.contains("newpassword"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReloadCheckIntervalZero() throws Exception {
|
||||
Path file = tempFolder.newFile("blacklist.txt").toPath();
|
||||
Path file = tempFolder.newFile("denylist.txt").toPath();
|
||||
Files.writeString(file, "oldpassword\n");
|
||||
|
||||
FileBasedPasswordBlacklist blacklist =
|
||||
new FileBasedPasswordBlacklist(tempFolder.getRoot().toPath(), "blacklist.txt",
|
||||
BlacklistPasswordPolicyProviderFactory.DEFAULT_FALSE_POSITIVE_PROBABILITY, 0);
|
||||
FileBasedPasswordDenylist denylist =
|
||||
new FileBasedPasswordDenylist(tempFolder.getRoot().toPath(), "denylist.txt",
|
||||
DenylistPasswordPolicyProviderFactory.DEFAULT_FALSE_POSITIVE_PROBABILITY, 0);
|
||||
|
||||
Assert.assertTrue(blacklist.contains("oldpassword"));
|
||||
Assert.assertTrue(denylist.contains("oldpassword"));
|
||||
|
||||
Files.writeString(file, "newpassword\n");
|
||||
Files.setLastModifiedTime(file, FileTime.fromMillis(Files.getLastModifiedTime(file).toMillis() + 1000));
|
||||
|
||||
Thread.sleep(2);
|
||||
Assert.assertTrue(blacklist.contains("oldpassword"));
|
||||
Assert.assertFalse(blacklist.contains("newpassword"));
|
||||
Assert.assertTrue(denylist.contains("oldpassword"));
|
||||
Assert.assertFalse(denylist.contains("newpassword"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLoadFromBloomFile() throws Exception {
|
||||
// Write plaintext denylist and pre-compute .bloom alongside it
|
||||
Path txtFile = tempFolder.newFile("denylist.txt").toPath();
|
||||
Files.writeString(txtFile, "secret123\nbadpassword\n");
|
||||
writeBloomFile(tempFolder.getRoot().toPath(), "denylist.txt",
|
||||
BlacklistPasswordPolicyProviderFactory.DEFAULT_FALSE_POSITIVE_PROBABILITY);
|
||||
DenylistPasswordPolicyProviderFactory.DEFAULT_FALSE_POSITIVE_PROBABILITY);
|
||||
|
||||
// Admin explicitly configures the .bloom file
|
||||
FileBasedPasswordBlacklist denylist =
|
||||
new FileBasedPasswordBlacklist(tempFolder.getRoot().toPath(), "denylist.txt.bloom",
|
||||
BlacklistPasswordPolicyProviderFactory.DEFAULT_FALSE_POSITIVE_PROBABILITY, 0);
|
||||
FileBasedPasswordDenylist denylist =
|
||||
new FileBasedPasswordDenylist(tempFolder.getRoot().toPath(), "denylist.txt.bloom",
|
||||
DenylistPasswordPolicyProviderFactory.DEFAULT_FALSE_POSITIVE_PROBABILITY, 0);
|
||||
|
||||
Assert.assertTrue("Should find password from .bloom file", denylist.contains("secret123"));
|
||||
Assert.assertTrue("Should find password from .bloom file", denylist.contains("badpassword"));
|
||||
@@ -115,13 +113,11 @@ public class BlacklistPasswordPolicyProviderTest {
|
||||
|
||||
@Test(expected = RuntimeException.class)
|
||||
public void testCorruptBloomFileThrows() throws Exception {
|
||||
// Write a corrupt (non-Guava) .bloom file
|
||||
Path bloomFile = tempFolder.newFile("denylist.txt.bloom").toPath();
|
||||
Files.writeString(bloomFile, "this is not a valid bloom filter binary");
|
||||
|
||||
// Admin configured .bloom explicitly — must fail hard on corrupt file, no fallback
|
||||
new FileBasedPasswordBlacklist(tempFolder.getRoot().toPath(), "denylist.txt.bloom",
|
||||
BlacklistPasswordPolicyProviderFactory.DEFAULT_FALSE_POSITIVE_PROBABILITY, 0);
|
||||
new FileBasedPasswordDenylist(tempFolder.getRoot().toPath(), "denylist.txt.bloom",
|
||||
DenylistPasswordPolicyProviderFactory.DEFAULT_FALSE_POSITIVE_PROBABILITY, 0);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -129,21 +125,19 @@ public class BlacklistPasswordPolicyProviderTest {
|
||||
Path txtFile = tempFolder.newFile("denylist.txt").toPath();
|
||||
Files.writeString(txtFile, "initialpassword\n");
|
||||
writeBloomFile(tempFolder.getRoot().toPath(), "denylist.txt",
|
||||
BlacklistPasswordPolicyProviderFactory.DEFAULT_FALSE_POSITIVE_PROBABILITY);
|
||||
DenylistPasswordPolicyProviderFactory.DEFAULT_FALSE_POSITIVE_PROBABILITY);
|
||||
|
||||
// Admin explicitly configures the .bloom file
|
||||
FileBasedPasswordBlacklist denylist =
|
||||
new FileBasedPasswordBlacklist(tempFolder.getRoot().toPath(), "denylist.txt.bloom",
|
||||
BlacklistPasswordPolicyProviderFactory.DEFAULT_FALSE_POSITIVE_PROBABILITY, 1);
|
||||
FileBasedPasswordDenylist denylist =
|
||||
new FileBasedPasswordDenylist(tempFolder.getRoot().toPath(), "denylist.txt.bloom",
|
||||
DenylistPasswordPolicyProviderFactory.DEFAULT_FALSE_POSITIVE_PROBABILITY, 1);
|
||||
|
||||
Assert.assertTrue(denylist.contains("initialpassword"));
|
||||
Assert.assertFalse(denylist.contains("updatedpassword"));
|
||||
|
||||
// Update the plaintext file, regenerate and bump mtime on .bloom
|
||||
Thread.sleep(2);
|
||||
Files.writeString(txtFile, "updatedpassword\n");
|
||||
writeBloomFile(tempFolder.getRoot().toPath(), "denylist.txt",
|
||||
BlacklistPasswordPolicyProviderFactory.DEFAULT_FALSE_POSITIVE_PROBABILITY);
|
||||
DenylistPasswordPolicyProviderFactory.DEFAULT_FALSE_POSITIVE_PROBABILITY);
|
||||
Path bloomFile = txtFile.resolveSibling("denylist.txt.bloom");
|
||||
Files.setLastModifiedTime(bloomFile, FileTime.fromMillis(Files.getLastModifiedTime(bloomFile).toMillis() + 1000));
|
||||
|
||||
@@ -157,8 +151,8 @@ public class BlacklistPasswordPolicyProviderTest {
|
||||
Files.writeString(txtFile, "alpha\nbeta\ngamma\n");
|
||||
|
||||
Path bloomFile = txtFile.resolveSibling("denylist.txt.bloom");
|
||||
BlacklistPasswordPolicyProviderFactory.buildBloomFile(
|
||||
txtFile, bloomFile, BlacklistPasswordPolicyProviderFactory.DEFAULT_FALSE_POSITIVE_PROBABILITY);
|
||||
DenylistPasswordPolicyProviderFactory.buildBloomFile(
|
||||
txtFile, bloomFile, DenylistPasswordPolicyProviderFactory.DEFAULT_FALSE_POSITIVE_PROBABILITY);
|
||||
Assert.assertTrue("bloom file must be created", Files.exists(bloomFile));
|
||||
Assert.assertTrue("bloom file must have non-zero size", Files.size(bloomFile) > 0);
|
||||
}
|
||||
@@ -166,9 +160,9 @@ public class BlacklistPasswordPolicyProviderTest {
|
||||
@Test(expected = IOException.class)
|
||||
public void testBuildBloomFileMissingInputThrows() throws Exception {
|
||||
Path missing = tempFolder.getRoot().toPath().resolve("nonexistent.txt");
|
||||
BlacklistPasswordPolicyProviderFactory.buildBloomFile(
|
||||
DenylistPasswordPolicyProviderFactory.buildBloomFile(
|
||||
missing, missing.resolveSibling("nonexistent.txt.bloom"),
|
||||
BlacklistPasswordPolicyProviderFactory.DEFAULT_FALSE_POSITIVE_PROBABILITY);
|
||||
DenylistPasswordPolicyProviderFactory.DEFAULT_FALSE_POSITIVE_PROBABILITY);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -176,20 +170,18 @@ public class BlacklistPasswordPolicyProviderTest {
|
||||
Path txtFile = tempFolder.newFile("denylist.txt").toPath();
|
||||
Files.writeString(txtFile, "mismatchpw\n");
|
||||
|
||||
// Build bloom with a DIFFERENT fpp than what the server will use
|
||||
double bloomFpp = 0.01; // coarser
|
||||
double serverFpp = BlacklistPasswordPolicyProviderFactory.DEFAULT_FALSE_POSITIVE_PROBABILITY; // 0.0001
|
||||
BlacklistPasswordPolicyProviderFactory.buildBloomFile(txtFile, txtFile.resolveSibling("denylist.txt.bloom"), bloomFpp);
|
||||
double bloomFpp = 0.01;
|
||||
double serverFpp = DenylistPasswordPolicyProviderFactory.DEFAULT_FALSE_POSITIVE_PROBABILITY;
|
||||
DenylistPasswordPolicyProviderFactory.buildBloomFile(txtFile, txtFile.resolveSibling("denylist.txt.bloom"), bloomFpp);
|
||||
|
||||
// Server should still load without throwing (only logs a warning)
|
||||
FileBasedPasswordBlacklist bl = new FileBasedPasswordBlacklist(
|
||||
FileBasedPasswordDenylist denylist = new FileBasedPasswordDenylist(
|
||||
tempFolder.getRoot().toPath(), "denylist.txt.bloom", serverFpp, 0);
|
||||
Assert.assertTrue("password must still be found despite fpp mismatch", bl.contains("mismatchpw"));
|
||||
Assert.assertTrue("password must still be found despite fpp mismatch", denylist.contains("mismatchpw"));
|
||||
}
|
||||
|
||||
private static void writeBloomFile(Path baseDir, String name, double fpp) throws IOException {
|
||||
Path input = baseDir.resolve(name);
|
||||
BlacklistPasswordPolicyProviderFactory.buildBloomFile(input, input.resolveSibling(name + ".bloom"), fpp);
|
||||
DenylistPasswordPolicyProviderFactory.buildBloomFile(input, input.resolveSibling(name + ".bloom"), fpp);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -25,8 +25,8 @@ import java.nio.file.Paths;
|
||||
import org.keycloak.models.ModelException;
|
||||
import org.keycloak.models.PasswordPolicy;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.policy.BlacklistPasswordPolicyProvider;
|
||||
import org.keycloak.policy.BlacklistPasswordPolicyProviderFactory;
|
||||
import org.keycloak.policy.DenylistPasswordPolicyProvider;
|
||||
import org.keycloak.policy.DenylistPasswordPolicyProviderFactory;
|
||||
import org.keycloak.policy.MaximumLengthPasswordPolicyProviderFactory;
|
||||
import org.keycloak.policy.PasswordPolicyManagerProvider;
|
||||
import org.keycloak.policy.PasswordPolicyProvider;
|
||||
@@ -187,7 +187,7 @@ public class PasswordPolicyTest {
|
||||
* KEYCLOAK-5244
|
||||
*/
|
||||
@Test
|
||||
public void testBlacklistPasswordPolicyWithTestBlacklist() {
|
||||
public void testDenylistPasswordPolicyWithTestDenylist() {
|
||||
runOnServer.run(session -> {
|
||||
|
||||
RealmModel realmModel = session.getContext().getRealm();
|
||||
@@ -195,22 +195,22 @@ public class PasswordPolicyTest {
|
||||
|
||||
realmModel.setPasswordPolicy(PasswordPolicy.parse(session, "passwordBlacklist(test-password-blacklist.txt)"));
|
||||
|
||||
Assertions.assertEquals(BlacklistPasswordPolicyProvider.ERROR_MESSAGE, policyManager.validate("jdoe", "blacklisted1").getMessage());
|
||||
Assertions.assertEquals(BlacklistPasswordPolicyProvider.ERROR_MESSAGE, policyManager.validate("jdoe", "blacklisted2").getMessage());
|
||||
Assertions.assertEquals(BlacklistPasswordPolicyProvider.ERROR_MESSAGE, policyManager.validate("jdoe", "bLaCkLiSteD2").getMessage());
|
||||
Assertions.assertEquals(DenylistPasswordPolicyProvider.ERROR_MESSAGE, policyManager.validate("jdoe", "blacklisted1").getMessage());
|
||||
Assertions.assertEquals(DenylistPasswordPolicyProvider.ERROR_MESSAGE, policyManager.validate("jdoe", "blacklisted2").getMessage());
|
||||
Assertions.assertEquals(DenylistPasswordPolicyProvider.ERROR_MESSAGE, policyManager.validate("jdoe", "bLaCkLiSteD2").getMessage());
|
||||
assertNull(policyManager.validate("jdoe", "notblacklisted"));
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBlacklistPasswordPolicyDefaultPath() {
|
||||
public void testDenylistPasswordPolicyDefaultPath() {
|
||||
final String SEPARATOR = File.separator;
|
||||
|
||||
runOnServer.run(session -> {
|
||||
ProviderFactory<PasswordPolicyProvider> passPolicyFact = session.getKeycloakSessionFactory().getProviderFactory(
|
||||
PasswordPolicyProvider.class, BlacklistPasswordPolicyProviderFactory.ID);
|
||||
assertThat(passPolicyFact, instanceOf(BlacklistPasswordPolicyProviderFactory.class));
|
||||
assertThat(((BlacklistPasswordPolicyProviderFactory) passPolicyFact).getDefaultBlacklistsBasePath(),
|
||||
PasswordPolicyProvider.class, DenylistPasswordPolicyProviderFactory.ID);
|
||||
assertThat(passPolicyFact, instanceOf(DenylistPasswordPolicyProviderFactory.class));
|
||||
assertThat(((DenylistPasswordPolicyProviderFactory) passPolicyFact).getDefaultDenylistsBasePath(),
|
||||
endsWith(SEPARATOR + "data" + SEPARATOR + "password-blacklists" + SEPARATOR));
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user