KEYCLOAK-5244 Add BlacklistPasswordPolicyProvider (#4370)

* KEYCLOAK-5244 Add BlacklistPasswordPolicyProvider

This introduces a new PasswordPolicy which can refer to
a named predefined password-blacklist to avoid users
choosing too easy to guess passwords.

The BlacklistPasswordPolicyProvider supports built-in as
well as custom blacklists.
built-in blacklists use the form `default/filename`
and custom ones `custom/filename`, where filename
is the name of the found blacklist-filename.

I'd propose to use some of the freely available password blacklists
from the [SecLists](https://github.com/danielmiessler/SecLists/tree/master/Passwords) project.

For testing purposes one can download the password blacklist
```
wget -O 10_million_password_list_top_1000000.txt https://github.com/danielmiessler/SecLists/blob/master/Passwords/10_million_password_list_top_1000000.txt?raw=true
```
to /data/keycloak/blacklists/

Custom password policies can be configured with the SPI
configuration mechanism via jboss-cli:
```
/subsystem=keycloak-server/spi=password-policy:add()
/subsystem=keycloak-server/spi=password-policy/provider=passwordBlacklist:add(enabled=true)
/subsystem=keycloak-server/spi=password-policy/provider=passwordBlacklist:write-attribute(name=properties.blacklistsFolderUri, value=file:///data/keycloak/blacklists/)
```

Password blacklist is stored in a TreeSet.

* KEYCLOAK-5244 Encode PasswordBlacklist as a BloomFilter

We now use a dynamically sized BloomFilter with a
false positive probability of 1% as a backing store
for PasswordBlacklists.

BloomFilter implementation is provided by google-guava
which is available in wildfly.

Password blacklist files are now resolved against
the ${jboss.server.data.dir}/password-blacklists.

This can be overridden via system property, or SPI config.
See JavaDoc of BlacklistPasswordPolicyProviderFactory for details.

Revised implementation to be more extensible, e.g. it could be
possible to use other stores like databases etc.

Moved FileSystem specific methods to FileBasesPasswordBlacklistPolicy.

The PasswordBlacklistProvider uses the guava version 20.0
shipped with wildfly. Unfortunately the arquillian testsuite
transitively depends on guava 23.0 via the selenium-3.5.1
dependency. Hence we need to use version 23.0 for tests but 20.0
for the policy provider to avoid NoClassDefFoundErrors in the
server-dist.

Configure password blacklist folder for tests

* KEYCLOAK-5244 Configure jboss.server.data.dir for test servers

* KEYCLOAK-5244 Translate blacklisted message in base/login
This commit is contained in:
Thomas Darimont 2017-10-17 20:41:44 +02:00 committed by Stian Thorgersen
parent fe76b2428b
commit 3103e0fd0a
17 changed files with 515 additions and 9 deletions

View file

@ -38,5 +38,6 @@
<module name="javax.transaction.api"/> <module name="javax.transaction.api"/>
<module name="com.fasterxml.jackson.core.jackson-databind"/> <module name="com.fasterxml.jackson.core.jackson-databind"/>
<module name="com.fasterxml.jackson.core.jackson-core"/> <module name="com.fasterxml.jackson.core.jackson-core"/>
<module name="com.google.guava"/>
</dependencies> </dependencies>
</module> </module>

10
pom.xml
View file

@ -91,6 +91,10 @@
<apacheds.version>2.0.0-M21</apacheds.version> <apacheds.version>2.0.0-M21</apacheds.version>
<apacheds.codec.version>1.0.0-M33</apacheds.codec.version> <apacheds.codec.version>1.0.0-M33</apacheds.codec.version>
<google.zxing.version>3.2.1</google.zxing.version> <google.zxing.version>3.2.1</google.zxing.version>
<!-- Same version as ships with wildfly. -->
<google.guava.version>20.0</google.guava.version>
<freemarker.version>2.3.23</freemarker.version> <freemarker.version>2.3.23</freemarker.version>
<jetty9.version>9.1.0.v20131115</jetty9.version> <jetty9.version>9.1.0.v20131115</jetty9.version>
<liquibase.version>3.4.1</liquibase.version> <liquibase.version>3.4.1</liquibase.version>
@ -440,6 +444,12 @@
<version>${google.zxing.version}</version> <version>${google.zxing.version}</version>
</dependency> </dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>${google.guava.version}</version>
</dependency>
<!-- Email Test Servers --> <!-- Email Test Servers -->
<dependency> <dependency>
<groupId>com.icegreen</groupId> <groupId>com.icegreen</groupId>

View file

@ -76,6 +76,11 @@
<artifactId>httpclient</artifactId> <artifactId>httpclient</artifactId>
<scope>provided</scope> <scope>provided</scope>
</dependency> </dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<scope>provided</scope>
</dependency>
<dependency> <dependency>
<groupId>junit</groupId> <groupId>junit</groupId>
<artifactId>junit</artifactId> <artifactId>junit</artifactId>

View file

@ -0,0 +1,74 @@
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;
/**
* Checks a password against a configured password blacklist.
*
* @author <a href="mailto:thomas.darimont@gmail.com">Thomas Darimont</a>
*/
public class BlacklistPasswordPolicyProvider implements PasswordPolicyProvider {
public static final String ERROR_MESSAGE = "invalidPasswordBlacklistedMessage";
private final KeycloakContext context;
private final BlacklistPasswordPolicyProviderFactory factory;
public BlacklistPasswordPolicyProvider(KeycloakContext context, BlacklistPasswordPolicyProviderFactory factory) {
this.context = context;
this.factory = factory;
}
/**
* Checks whether the provided password is contained in the configured blacklist.
*
* @param username
* @param password
* @return {@literal null} if the password is not blacklisted otherwise a {@link PolicyError}
*/
@Override
public PolicyError validate(String username, String password) {
Object policyConfig = context.getRealm().getPasswordPolicy().getPolicyConfig(BlacklistPasswordPolicyProviderFactory.ID);
if (policyConfig == null) {
return null;
}
if (!(policyConfig instanceof PasswordBlacklist)) {
return null;
}
PasswordBlacklist blacklist = (FileBasedPasswordBlacklist) policyConfig;
if (!blacklist.contains(password)) {
return null;
}
return new PolicyError(ERROR_MESSAGE);
}
@Override
public PolicyError validate(RealmModel realm, UserModel user, String password) {
return validate(user.getUsername(), password);
}
@Override
public Object parseConfig(String blacklistName) {
if (blacklistName == null) {
return null;
}
return factory.resolvePasswordBlacklist(blacklistName);
}
@Override
public void close() {
//noop
}
}

View file

@ -0,0 +1,331 @@
/*
* Copyright 2017 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 com.google.common.hash.BloomFilter;
import com.google.common.hash.Funnels;
import org.jboss.logging.Logger;
import org.keycloak.Config;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import java.io.BufferedReader;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
/**
* Creates {@link BlacklistPasswordPolicyProvider} instances.
* <p>
* Password blacklists are simple text files where every line is a blacklisted password delimited by {@code \n}.
* Blacklist files are discovered and registered at startup.
* <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.
* <p>Users can provide custom blacklists by adding a blacklist password file to the configured blacklist folder.
* <p>
* <p>The location of the password-blacklists folder is derived as follows</p>
* <ol>
* <li>the value of the System property {@code keycloak.password.blacklists.path} if configured - fails if folder is missing</li>
* <li>the value of the SPI config property: {@code blacklistsPath} when explicitly configured - fails if folder is missing</li>
* <li>otherwise {@code ${jboss.server.data.dir}/password-blacklists/} if nothing else is configured - the folder is created automatically if not present</li>
* </ol>
* <p>Note that the preferred way for configuration is to copy the password file to the {@code ${jboss.server.data.dir}/password-blacklists/} folder</p>
* <p>To configure a password blacklist via the SPI configuration, run the following jboss-cli script:</p>
* <pre>{@code
* /subsystem=keycloak-server/spi=password-policy:add()
* /subsystem=keycloak-server/spi=password-policy/provider=passwordBlacklist:add(enabled=true)
* /subsystem=keycloak-server/spi=password-policy/provider=passwordBlacklist:write-attribute(name=properties.blacklistsPath, value=/data/keycloak/blacklists/)
* }</pre>
* <p>A password blacklist with the filename {@code 10_million_password_list_top_1000000-password-blacklist.txt}
* that is located beneath {@code /data/keycloak/blacklists/} can be referred to
* as {@code 10_million_password_list_top_1000000-password-blacklist.txt} in the <em>Authentication: Password Policy</em> configuration.
*
* @author <a href="mailto:thomas.darimont@gmail.com">Thomas Darimont</a>
*/
public class BlacklistPasswordPolicyProviderFactory implements PasswordPolicyProviderFactory {
private static final Logger LOG = Logger.getLogger(BlacklistPasswordPolicyProviderFactory.class);
public static final String ID = "passwordBlacklist";
public static final String SYSTEM_PROPERTY = "keycloak.password.blacklists.path";
public static final String BLACKLISTS_PATH_PROPERTY = "blacklistsPath";
public static final String JBOSS_SERVER_DATA_DIR = "jboss.server.data.dir";
public static final String PASSWORD_BLACKLISTS_FOLDER = "password-blacklists/";
private ConcurrentMap<String, FileBasedPasswordBlacklist> blacklistRegistry = new ConcurrentHashMap<>();
private Path blacklistsBasePath;
@Override
public PasswordPolicyProvider create(KeycloakSession session) {
return new BlacklistPasswordPolicyProvider(session.getContext(), this);
}
@Override
public void init(Config.Scope config) {
this.blacklistsBasePath = FileBasedPasswordBlacklist.detectBlacklistsBasePath(config);
}
@Override
public void postInit(KeycloakSessionFactory factory) {
}
@Override
public void close() {
}
@Override
public String getDisplayName() {
return "Password Blacklist";
}
@Override
public String getConfigType() {
return PasswordPolicyProvider.STRING_CONFIG_TYPE;
}
@Override
public String getDefaultConfigValue() {
return "";
}
@Override
public boolean isMultiplSupported() {
return false;
}
@Override
public String getId() {
return ID;
}
/**
* Resolves and potentially registers a {@link PasswordBlacklist} for the given {@code blacklistName}.
*
* @param blacklistName
* @return
*/
public PasswordBlacklist resolvePasswordBlacklist(String blacklistName) {
Objects.requireNonNull(blacklistName, "blacklistName");
String cleanedBlacklistName = blacklistName.trim();
if (cleanedBlacklistName.isEmpty()) {
throw new IllegalArgumentException("Password blacklist name must not be empty!");
}
return blacklistRegistry.computeIfAbsent(cleanedBlacklistName, (name) -> {
FileBasedPasswordBlacklist pbl = new FileBasedPasswordBlacklist(this.blacklistsBasePath, name);
pbl.lazyInit();
return pbl;
});
}
/**
* A {@link PasswordBlacklist} describes a list of too easy to guess
* or potentially leaked passwords that users should not be able to use.
*/
public interface PasswordBlacklist {
/**
* @return the logical name of the {@link PasswordBlacklist}
*/
String getName();
/**
* Checks whether a given {@code password} is contained in this {@link PasswordBlacklist}.
*
* @param password
* @return
*/
boolean contains(String password);
}
/**
* A {@link FileBasedPasswordBlacklist} uses password-blacklist files as
* to construct a {@link PasswordBlacklist}.
* <p>
* This implementation uses a dynamically sized {@link BloomFilter}
* to provide a false positive probability of 1%.
*
* @see BloomFilter
*/
public static class FileBasedPasswordBlacklist implements PasswordBlacklist {
private static final double FALSE_POSITIVE_PROBABILITY = 0.01;
private static final int BUFFER_SIZE_IN_BYTES = 512 * 1024;
/**
* The name of the blacklist filename.
*/
private final String name;
/**
* The concrete path to the password-blacklist file.
*/
private final Path path;
/**
* Initialized lazily via {@link #lazyInit()}
*/
private BloomFilter<String> blacklist;
public FileBasedPasswordBlacklist(Path blacklistBasePath, String name) {
this.name = name;
this.path = blacklistBasePath.resolve(name);
if (name.contains("/")) {
// disallow '/' to avoid accidental filesystem traversal
throw new IllegalArgumentException("" + name + " must not contain slashes!");
}
if (!Files.exists(this.path)) {
throw new IllegalArgumentException("Password blacklist " + name + " not found!");
}
}
public String getName() {
return name;
}
public boolean contains(String password) {
return blacklist != null && blacklist.mightContain(password);
}
void lazyInit() {
if (blacklist != null) {
return;
}
this.blacklist = load();
}
/**
* Loads the referenced blacklist into a {@link BloomFilter}.
*
* @return the {@link BloomFilter} backing a password blacklist
*/
private BloomFilter<String> load() {
try {
LOG.infof("Loading blacklist with name %s from %s - start", name, path);
long passwordCount = getPasswordCount();
BloomFilter<String> filter = BloomFilter.create(
Funnels.stringFunnel(StandardCharsets.UTF_8),
passwordCount,
FALSE_POSITIVE_PROBABILITY);
try (BufferedReader br = newReader(path)) {
br.lines().forEach(filter::put);
}
LOG.infof("Loading blacklist with name %s from %s - end", name, path);
return filter;
} catch (IOException e) {
throw new RuntimeException("Could not load password blacklist from path: " + path, e);
}
}
/**
* Determines password blacklist size to correctly size the {@link BloomFilter} backing this blacklist.
*
* @return
* @throws IOException
*/
private long getPasswordCount() throws IOException {
/*
* TODO find a more efficient way to determine the password count,
* e.g. require a header-line in the password-blacklist file
*/
try (BufferedReader br = newReader(path)) {
return br.lines().count();
}
}
private static BufferedReader newReader(Path path) throws IOException {
return new BufferedReader(Files.newBufferedReader(path), BUFFER_SIZE_IN_BYTES);
}
/**
* Discovers password blacklists location.
* <p>
* <ol>
* <li>
* system property {@code keycloak.password.blacklists.path} if present
* </li>
* <li>SPI config property {@code blacklistsPath}</li>
* </ol>
* and fallback to the {@code /data/password-blacklists} folder of the currently
* running wildfly instance.
*
* @param config
* @return the detected blacklist path
* @throws IllegalStateException if no blacklist folder could be detected
*/
private static Path detectBlacklistsBasePath(Config.Scope config) {
String pathFromSysProperty = System.getProperty(SYSTEM_PROPERTY);
if (pathFromSysProperty != null) {
return ensureExists(Paths.get(pathFromSysProperty));
}
String pathFromSpiConfig = config.get(BLACKLISTS_PATH_PROPERTY);
if (pathFromSpiConfig != null) {
return ensureExists(Paths.get(pathFromSpiConfig));
}
String pathFromJbossDataPath = System.getProperty(JBOSS_SERVER_DATA_DIR) + "/" + PASSWORD_BLACKLISTS_FOLDER;
if (!Files.exists(Paths.get(pathFromJbossDataPath))) {
if (!Paths.get(pathFromJbossDataPath).toFile().mkdirs()) {
LOG.errorf("Could not create folder for password blacklists: %s", pathFromJbossDataPath);
}
}
return ensureExists(Paths.get(pathFromJbossDataPath));
}
private static Path ensureExists(Path path) {
Objects.requireNonNull(path, "path");
if (Files.exists(path)) {
return path;
}
throw new IllegalStateException("Password blacklists location does not exist: " + path);
}
}
}

View file

@ -26,3 +26,4 @@ org.keycloak.policy.NotUsernamePasswordPolicyProviderFactory
org.keycloak.policy.RegexPatternsPasswordPolicyProviderFactory org.keycloak.policy.RegexPatternsPasswordPolicyProviderFactory
org.keycloak.policy.SpecialCharsPasswordPolicyProviderFactory org.keycloak.policy.SpecialCharsPasswordPolicyProviderFactory
org.keycloak.policy.UpperCasePasswordPolicyProviderFactory org.keycloak.policy.UpperCasePasswordPolicyProviderFactory
org.keycloak.policy.BlacklistPasswordPolicyProviderFactory

View file

@ -56,6 +56,7 @@
<migration.70.version>1.9.8.Final</migration.70.version> <migration.70.version>1.9.8.Final</migration.70.version>
<migration.70.authz.version>2.2.1.Final</migration.70.authz.version> <migration.70.authz.version>2.2.1.Final</migration.70.authz.version>
<migration.71.version>2.5.5.Final</migration.71.version> <migration.71.version>2.5.5.Final</migration.71.version>
<google.guava.version>23.0</google.guava.version>
<maven.compiler.target>1.8</maven.compiler.target> <maven.compiler.target>1.8</maven.compiler.target>
<maven.compiler.source>1.8</maven.compiler.source> <maven.compiler.source>1.8</maven.compiler.source>

View file

@ -29,6 +29,12 @@
<name>Auth Server - Undertow</name> <name>Auth Server - Undertow</name>
<dependencies> <dependencies>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-testsuite-utils</artifactId>
</dependency>
<dependency> <dependency>
<groupId>org.jboss.arquillian.junit</groupId> <groupId>org.jboss.arquillian.junit</groupId>
<artifactId>arquillian-junit-container</artifactId> <artifactId>arquillian-junit-container</artifactId>

View file

@ -41,23 +41,21 @@ import org.jboss.shrinkwrap.api.spec.WebArchive;
import org.jboss.shrinkwrap.descriptor.api.Descriptor; import org.jboss.shrinkwrap.descriptor.api.Descriptor;
import org.jboss.shrinkwrap.undertow.api.UndertowWebArchive; import org.jboss.shrinkwrap.undertow.api.UndertowWebArchive;
import org.keycloak.common.util.reflections.Reflections; import org.keycloak.common.util.reflections.Reflections;
import org.keycloak.connections.infinispan.InfinispanConnectionProvider;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory; import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.services.filters.KeycloakSessionServletFilter; import org.keycloak.services.filters.KeycloakSessionServletFilter;
import org.keycloak.services.managers.ApplianceBootstrap; import org.keycloak.services.managers.ApplianceBootstrap;
import org.keycloak.services.resources.KeycloakApplication; import org.keycloak.services.resources.KeycloakApplication;
import org.keycloak.testsuite.KeycloakServer;
import org.keycloak.util.JsonSerialization; import org.keycloak.util.JsonSerialization;
import java.io.IOException;
import javax.servlet.DispatcherType; import javax.servlet.DispatcherType;
import javax.servlet.ServletException; import javax.servlet.ServletException;
import java.io.IOException;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.util.Collection; import java.util.Collection;
import java.util.Map; import java.util.Map;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Level;
public class KeycloakOnUndertow implements DeployableContainer<KeycloakOnUndertowConfiguration> { public class KeycloakOnUndertow implements DeployableContainer<KeycloakOnUndertowConfiguration> {
@ -171,6 +169,8 @@ public class KeycloakOnUndertow implements DeployableContainer<KeycloakOnUnderto
return; return;
} }
KeycloakServer.configureDataDirectory();
log.infof("Starting auth server on embedded Undertow on: http://%s:%d", configuration.getBindAddress(), configuration.getBindHttpPort()); log.infof("Starting auth server on embedded Undertow on: http://%s:%d", configuration.getBindAddress(), configuration.getBindHttpPort());
long start = System.currentTimeMillis(); long start = System.currentTimeMillis();

View file

@ -20,11 +20,11 @@ package org.keycloak.testsuite.policy;
import org.jboss.arquillian.container.test.api.Deployment; import org.jboss.arquillian.container.test.api.Deployment;
import org.jboss.shrinkwrap.api.spec.WebArchive; import org.jboss.shrinkwrap.api.spec.WebArchive;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.keycloak.models.ModelException; import org.keycloak.models.ModelException;
import org.keycloak.models.PasswordPolicy; import org.keycloak.models.PasswordPolicy;
import org.keycloak.models.RealmModel; import org.keycloak.models.RealmModel;
import org.keycloak.policy.BlacklistPasswordPolicyProvider;
import org.keycloak.policy.PasswordPolicyManagerProvider; import org.keycloak.policy.PasswordPolicyManagerProvider;
import org.keycloak.representations.idm.RealmRepresentation; import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.testsuite.AbstractKeycloakTest; import org.keycloak.testsuite.AbstractKeycloakTest;
@ -32,7 +32,6 @@ import org.keycloak.testsuite.runonserver.RunOnServerDeployment;
import org.keycloak.testsuite.util.RealmBuilder; import org.keycloak.testsuite.util.RealmBuilder;
import java.util.List; import java.util.List;
import java.util.regex.PatternSyntaxException;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertFalse;
@ -142,6 +141,25 @@ public class PasswordPolicyTest extends AbstractKeycloakTest {
}); });
} }
/**
* KEYCLOAK-5244
*/
@Test
public void testBlacklistPasswordPolicyWithTestBlacklist() throws Exception {
testingClient.server("passwordPolicy").run(session -> {
RealmModel realmModel = session.getContext().getRealm();
PasswordPolicyManagerProvider policyManager = session.getProvider(PasswordPolicyManagerProvider.class);
realmModel.setPasswordPolicy(PasswordPolicy.parse(session, "passwordBlacklist(test-password-blacklist.txt)"));
Assert.assertEquals(BlacklistPasswordPolicyProvider.ERROR_MESSAGE, policyManager.validate("jdoe", "blacklisted1").getMessage());
Assert.assertEquals(BlacklistPasswordPolicyProvider.ERROR_MESSAGE, policyManager.validate("jdoe", "blacklisted2").getMessage());
assertNull(policyManager.validate("jdoe", "notblacklisted"));
});
}
@Test @Test
public void testNotUsername() { public void testNotUsername() {
testingClient.server("passwordPolicy").run(session -> { testingClient.server("passwordPolicy").run(session -> {

View file

@ -314,6 +314,9 @@
<keycloak.testsuite.logging.pattern>${keycloak.testsuite.logging.pattern}</keycloak.testsuite.logging.pattern> <keycloak.testsuite.logging.pattern>${keycloak.testsuite.logging.pattern}</keycloak.testsuite.logging.pattern>
<keycloak.connectionsJpa.url.crossdc>${keycloak.connectionsJpa.url.crossdc}</keycloak.connectionsJpa.url.crossdc> <keycloak.connectionsJpa.url.crossdc>${keycloak.connectionsJpa.url.crossdc}</keycloak.connectionsJpa.url.crossdc>
<!-- used by PasswordPolicyTest.testBlacklistPasswordPolicyWithTestBlacklist, see KEYCLOAK-5244 -->
<keycloak.password.blacklists.path>${project.build.directory}/test-classes/password-blacklists</keycloak.password.blacklists.path>
</systemPropertyVariables> </systemPropertyVariables>
<properties> <properties>
<property> <property>

View file

@ -40,17 +40,18 @@ import org.keycloak.services.resources.KeycloakApplication;
import org.keycloak.testsuite.util.cli.TestsuiteCLI; import org.keycloak.testsuite.util.cli.TestsuiteCLI;
import org.keycloak.util.JsonSerialization; import org.keycloak.util.JsonSerialization;
import javax.net.ssl.SSLContext;
import javax.servlet.DispatcherType; import javax.servlet.DispatcherType;
import java.io.File; import java.io.File;
import java.io.FileInputStream; import java.io.FileInputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.nio.file.Files;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
import java.util.Date; import java.util.Date;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.Properties; import java.util.Properties;
import javax.net.ssl.SSLContext;
/** /**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a> * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
@ -58,6 +59,7 @@ import javax.net.ssl.SSLContext;
public class KeycloakServer { public class KeycloakServer {
private static final Logger log = Logger.getLogger(KeycloakServer.class); private static final Logger log = Logger.getLogger(KeycloakServer.class);
public static final String JBOSS_SERVER_DATA_DIR = "jboss.server.data.dir";
private boolean sysout = false; private boolean sysout = false;
@ -211,6 +213,8 @@ public class KeycloakServer {
config.setWorkerThreads(undertowWorkerThreads); config.setWorkerThreads(undertowWorkerThreads);
} }
configureDataDirectory();
detectNodeName(config); detectNodeName(config);
final KeycloakServer keycloak = new KeycloakServer(config); final KeycloakServer keycloak = new KeycloakServer(config);
@ -241,6 +245,52 @@ public class KeycloakServer {
return keycloak; return keycloak;
} }
public static void configureDataDirectory() {
String dataPath = detectDataDirectory();
System.setProperty(JBOSS_SERVER_DATA_DIR, dataPath);
log.infof("Using %s %s", JBOSS_SERVER_DATA_DIR, dataPath);
}
/**
* Detects the {@code jboss.server.data.dir} to use.
* If the System property {@code jboss.server.data.dir} is already set then the property value is used,
* otherwise a temporary data dir is created that will be deleted on JVM exit.
*
* @return
*/
public static String detectDataDirectory() {
String dataPath = System.getProperty(JBOSS_SERVER_DATA_DIR);
if (dataPath != null){
// we assume jboss.server.data.dir is managed externally so just use it as is.
File dataDir = new File(dataPath);
if (!dataDir.exists() || !dataDir.isDirectory()) {
throw new RuntimeException("Invalid " + JBOSS_SERVER_DATA_DIR + " resources directory: " + dataPath);
}
return dataPath;
}
// we generate a dynamic jboss.server.data.dir and remove it at the end.
try {
File tempKeycloakFolder = Files.createTempDirectory("keycloak-server-").toFile();
File tmpDataDir = new File(tempKeycloakFolder, "/data");
if (tmpDataDir.mkdirs()) {
tmpDataDir.deleteOnExit();
} else {
throw new IOException("Could not create directory " + tmpDataDir);
}
dataPath = tmpDataDir.getAbsolutePath();
} catch (IOException ioe){
throw new RuntimeException("Could not create temporary " + JBOSS_SERVER_DATA_DIR, ioe);
}
return dataPath;
}
private KeycloakServerConfig config; private KeycloakServerConfig config;
private KeycloakSessionFactory sessionFactory; private KeycloakSessionFactory sessionFactory;

View file

@ -110,6 +110,7 @@ invalidPasswordExistingMessage=Das aktuelle Passwort is ung\u00FCltig.
invalidPasswordConfirmMessage=Die Passwortbest\u00E4tigung ist nicht identisch. invalidPasswordConfirmMessage=Die Passwortbest\u00E4tigung ist nicht identisch.
invalidTotpMessage=Ung\u00FCltiger One-time Code. invalidTotpMessage=Ung\u00FCltiger One-time Code.
invalidEmailMessage=Ung\u00FCltige E-Mail Adresse. invalidEmailMessage=Ung\u00FCltige E-Mail Adresse.
invalidPasswordBlacklistedMessage=Passwort ist nicht erlaubt.
usernameExistsMessage=Der Benutzername existiert bereits. usernameExistsMessage=Der Benutzername existiert bereits.
emailExistsMessage=Die E-Mail-Adresse existiert bereits. emailExistsMessage=Die E-Mail-Adresse existiert bereits.

View file

@ -152,6 +152,7 @@ invalidPasswordMinSpecialCharsMessage=Invalid password: must contain at least {0
invalidPasswordNotUsernameMessage=Invalid password: must not be equal to the username. invalidPasswordNotUsernameMessage=Invalid password: must not be equal to the username.
invalidPasswordRegexPatternMessage=Invalid password: fails to match regex pattern(s). invalidPasswordRegexPatternMessage=Invalid password: fails to match regex pattern(s).
invalidPasswordHistoryMessage=Invalid password: must not be equal to any of last {0} passwords. invalidPasswordHistoryMessage=Invalid password: must not be equal to any of last {0} passwords.
invalidPasswordBlacklistedMessage=Invalid password: password is blacklisted.
invalidPasswordGenericMessage=Invalid password: new password doesn''t match password policies. invalidPasswordGenericMessage=Invalid password: new password doesn''t match password policies.
locale_ca=Catal\u00E0 locale_ca=Catal\u00E0

View file

@ -6,6 +6,7 @@ invalidPasswordMinSpecialCharsMessage=Invalid password: must contain at least {0
invalidPasswordNotUsernameMessage=Invalid password: must not be equal to the username. invalidPasswordNotUsernameMessage=Invalid password: must not be equal to the username.
invalidPasswordRegexPatternMessage=Invalid password: fails to match regex pattern(s). invalidPasswordRegexPatternMessage=Invalid password: fails to match regex pattern(s).
invalidPasswordHistoryMessage=Invalid password: must not be equal to any of last {0} passwords. invalidPasswordHistoryMessage=Invalid password: must not be equal to any of last {0} passwords.
invalidPasswordBlacklistedMessage=Invalid password: password is blacklisted.
invalidPasswordGenericMessage=Invalid password: new password doesn''t match password policies. invalidPasswordGenericMessage=Invalid password: new password doesn''t match password policies.
ldapErrorInvalidCustomFilter=Custom configured LDAP filter does not start with "(" or does not end with ")". ldapErrorInvalidCustomFilter=Custom configured LDAP filter does not start with "(" or does not end with ")".

View file

@ -142,6 +142,7 @@ missingTotpMessage=Please specify authenticator code.
notMatchPasswordMessage=Passwords don''t match. notMatchPasswordMessage=Passwords don''t match.
invalidPasswordExistingMessage=Invalid existing password. invalidPasswordExistingMessage=Invalid existing password.
invalidPasswordBlacklistedMessage=Invalid password: password is blacklisted.
invalidPasswordConfirmMessage=Password confirmation doesn''t match. invalidPasswordConfirmMessage=Password confirmation doesn''t match.
invalidTotpMessage=Invalid authenticator code. invalidTotpMessage=Invalid authenticator code.