[#10608] Password blacklists folder

This commit is contained in:
rmartinc 2022-03-08 09:11:54 +01:00 committed by Pedro Igor
parent 8454dc5a5d
commit 48565832d4
5 changed files with 94 additions and 4 deletions

View file

@ -102,6 +102,7 @@ import org.keycloak.quarkus.runtime.configuration.MicroProfileConfigProvider;
import org.keycloak.connections.jpa.DefaultJpaConnectionProviderFactory;
import org.keycloak.connections.jpa.updater.liquibase.LiquibaseJpaUpdaterProviderFactory;
import org.keycloak.connections.jpa.updater.liquibase.conn.DefaultLiquibaseConnectionProvider;
import org.keycloak.policy.BlacklistPasswordPolicyProviderFactory;
import org.keycloak.protocol.ProtocolMapperSpi;
import org.keycloak.protocol.oidc.mappers.DeployedScriptOIDCProtocolMapper;
import org.keycloak.provider.EnvironmentDependentProviderFactory;
@ -153,7 +154,8 @@ class KeycloakProcessor {
DefaultHostnameProviderFactory.class,
FixedHostnameProviderFactory.class,
RequestHostnameProviderFactory.class,
FilesPlainTextVaultProviderFactory.class);
FilesPlainTextVaultProviderFactory.class,
BlacklistPasswordPolicyProviderFactory.class);
static {
DEPLOYEABLE_SCRIPT_PROVIDERS.put(AUTHENTICATORS, KeycloakProcessor::registerScriptAuthenticator);

View file

@ -0,0 +1,36 @@
/*
* Copyright 2022 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.quarkus.runtime.policy;
import org.keycloak.policy.BlacklistPasswordPolicyProviderFactory;
import org.keycloak.quarkus.runtime.Environment;
/**
* <p>Quarkus implementation of the BlacklistPasswordPolicyProviderFactory. 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 {
@Override
public String getDefaultBlacklistsBasePath() {
return Environment.getDataDir() + "/" + PASSWORD_BLACKLISTS_FOLDER;
}
}

View file

@ -0,0 +1,18 @@
#
# Copyright 2022 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.quarkus.runtime.policy.QuarkusBlacklistPasswordPolicyProviderFactory

View file

@ -33,6 +33,7 @@ import java.nio.file.Paths;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.function.Supplier;
/**
* Creates {@link BlacklistPasswordPolicyProvider} instances.
@ -87,7 +88,7 @@ public class BlacklistPasswordPolicyProviderFactory implements PasswordPolicyPro
if (this.blacklistsBasePath == null) {
synchronized (this) {
if (this.blacklistsBasePath == null) {
this.blacklistsBasePath = FileBasedPasswordBlacklist.detectBlacklistsBasePath(config);
this.blacklistsBasePath = FileBasedPasswordBlacklist.detectBlacklistsBasePath(config, this::getDefaultBlacklistsBasePath);
}
}
}
@ -132,6 +133,17 @@ public class BlacklistPasswordPolicyProviderFactory implements PasswordPolicyPro
return ID;
}
/**
* Method to obtain the default location for the list folder. The method
* will return the <em>data</em> directory of the installation concatenated
* with <em>/password-blacklists/</em>.
*
* @return The default path used by the provider to lookup the lists
* when no other configuration is in place.
*/
public String getDefaultBlacklistsBasePath() {
return System.getProperty(JBOSS_SERVER_DATA_DIR) + "/" + PASSWORD_BLACKLISTS_FOLDER;
}
/**
* Resolves and potentially registers a {@link PasswordBlacklist} for the given {@code blacklistName}.
@ -302,10 +314,11 @@ public class BlacklistPasswordPolicyProviderFactory implements PasswordPolicyPro
* running wildfly instance.
*
* @param 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
*/
private static Path detectBlacklistsBasePath(Config.Scope config) {
private static Path detectBlacklistsBasePath(Config.Scope config, Supplier<String> defaultPathSupplier) {
String pathFromSysProperty = System.getProperty(SYSTEM_PROPERTY);
if (pathFromSysProperty != null) {
@ -317,7 +330,11 @@ public class BlacklistPasswordPolicyProviderFactory implements PasswordPolicyPro
return ensureExists(Paths.get(pathFromSpiConfig));
}
String pathFromJbossDataPath = System.getProperty(JBOSS_SERVER_DATA_DIR) + "/" + PASSWORD_BLACKLISTS_FOLDER;
String pathFromJbossDataPath = defaultPathSupplier.get();
if (pathFromJbossDataPath == null) {
throw new IllegalStateException("Default path for the blacklist folder was null");
}
if (!Files.exists(Paths.get(pathFromJbossDataPath))) {
if (!Paths.get(pathFromJbossDataPath).toFile().mkdirs()) {
LOG.errorf("Could not create folder for password blacklists: %s", pathFromJbossDataPath);

View file

@ -23,8 +23,11 @@ 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.MaximumLengthPasswordPolicyProviderFactory;
import org.keycloak.policy.PasswordPolicyManagerProvider;
import org.keycloak.policy.PasswordPolicyProvider;
import org.keycloak.provider.ProviderFactory;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.testsuite.AbstractKeycloakTest;
import org.keycloak.testsuite.arquillian.annotation.AuthServerContainerExclude;
@ -34,6 +37,9 @@ import org.keycloak.testsuite.util.RealmBuilder;
import java.util.List;
import static org.hamcrest.CoreMatchers.instanceOf;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.endsWith;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
@ -184,6 +190,17 @@ public class PasswordPolicyTest extends AbstractKeycloakTest {
});
}
@Test
public void testBlacklistPasswordPolicyDefaultPath() throws Exception {
testingClient.server("passwordPolicy").run(session -> {
ProviderFactory<PasswordPolicyProvider> passPolicyFact = session.getKeycloakSessionFactory().getProviderFactory(
PasswordPolicyProvider.class, BlacklistPasswordPolicyProviderFactory.ID);
assertThat(passPolicyFact, instanceOf(BlacklistPasswordPolicyProviderFactory.class));
assertThat(((BlacklistPasswordPolicyProviderFactory)passPolicyFact).getDefaultBlacklistsBasePath(),
endsWith("/data/password-blacklists/"));
});
}
@Test
public void testNotUsername() {
testingClient.server("passwordPolicy").run(session -> {