keycloak-aplcache
Changes
server-spi-private/src/main/java/org/keycloak/policy/BlacklistPasswordPolicyProviderFactory.java 405(+207 -198)
Details
diff --git a/server-spi-private/src/main/java/org/keycloak/policy/BlacklistPasswordPolicyProviderFactory.java b/server-spi-private/src/main/java/org/keycloak/policy/BlacklistPasswordPolicyProviderFactory.java
index cb7af62..e3936eb 100644
--- a/server-spi-private/src/main/java/org/keycloak/policy/BlacklistPasswordPolicyProviderFactory.java
+++ b/server-spi-private/src/main/java/org/keycloak/policy/BlacklistPasswordPolicyProviderFactory.java
@@ -64,268 +64,277 @@ import java.util.concurrent.ConcurrentMap;
*/
public class BlacklistPasswordPolicyProviderFactory implements PasswordPolicyProviderFactory {
- private static final Logger LOG = Logger.getLogger(BlacklistPasswordPolicyProviderFactory.class);
+ private static final Logger LOG = Logger.getLogger(BlacklistPasswordPolicyProviderFactory.class);
- public static final String ID = "passwordBlacklist";
+ public static final String ID = "passwordBlacklist";
- public static final String SYSTEM_PROPERTY = "keycloak.password.blacklists.path";
+ public static final String SYSTEM_PROPERTY = "keycloak.password.blacklists.path";
- public static final String BLACKLISTS_PATH_PROPERTY = "blacklistsPath";
+ public static final String BLACKLISTS_PATH_PROPERTY = "blacklistsPath";
- public static final String JBOSS_SERVER_DATA_DIR = "jboss.server.data.dir";
+ public static final String JBOSS_SERVER_DATA_DIR = "jboss.server.data.dir";
- public static final String PASSWORD_BLACKLISTS_FOLDER = "password-blacklists/";
+ public static final String PASSWORD_BLACKLISTS_FOLDER = "password-blacklists/";
- private ConcurrentMap<String, FileBasedPasswordBlacklist> blacklistRegistry = new ConcurrentHashMap<>();
+ private ConcurrentMap<String, FileBasedPasswordBlacklist> blacklistRegistry = new ConcurrentHashMap<>();
- private Path blacklistsBasePath;
+ private volatile Path blacklistsBasePath;
- @Override
- public PasswordPolicyProvider create(KeycloakSession session) {
- return new BlacklistPasswordPolicyProvider(session.getContext(), this);
- }
+ private Config.Scope config;
- @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 PasswordPolicyProvider create(KeycloakSession session) {
+ if (this.blacklistsBasePath == null) {
+ synchronized (this) {
+ if (this.blacklistsBasePath == null) {
+ this.blacklistsBasePath = FileBasedPasswordBlacklist.detectBlacklistsBasePath(config);
+ }
+ }
+ }
+ return new BlacklistPasswordPolicyProvider(session.getContext(), this);
+ }
- @Override
- public boolean isMultiplSupported() {
- return false;
- }
+ @Override
+ public void init(Config.Scope config) {
+ this.config = config;
+ }
- @Override
- public String getId() {
- return ID;
- }
+ @Override
+ public void postInit(KeycloakSessionFactory factory) {
+ }
+ @Override
+ public void close() {
+ }
- /**
- * Resolves and potentially registers a {@link PasswordBlacklist} for the given {@code blacklistName}.
- *
- * @param blacklistName
- * @return
- */
- public PasswordBlacklist resolvePasswordBlacklist(String blacklistName) {
+ @Override
+ public String getDisplayName() {
+ return "Password Blacklist";
+ }
- Objects.requireNonNull(blacklistName, "blacklistName");
+ @Override
+ public String getConfigType() {
+ return PasswordPolicyProvider.STRING_CONFIG_TYPE;
+ }
- String cleanedBlacklistName = blacklistName.trim();
- if (cleanedBlacklistName.isEmpty()) {
- throw new IllegalArgumentException("Password blacklist name must not be empty!");
+ @Override
+ public String getDefaultConfigValue() {
+ return "";
}
- return blacklistRegistry.computeIfAbsent(cleanedBlacklistName, (name) -> {
- FileBasedPasswordBlacklist pbl = new FileBasedPasswordBlacklist(this.blacklistsBasePath, name);
- pbl.lazyInit();
- return pbl;
- });
- }
+ @Override
+ public boolean isMultiplSupported() {
+ return false;
+ }
- /**
- * 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 {
+ @Override
+ public String getId() {
+ return ID;
+ }
/**
- * @return the logical name of the {@link PasswordBlacklist}
- */
- String getName();
-
- /**
- * Checks whether a given {@code password} is contained in this {@link PasswordBlacklist}.
+ * Resolves and potentially registers a {@link PasswordBlacklist} for the given {@code blacklistName}.
*
- * @param password
+ * @param blacklistName
* @return
*/
- boolean contains(String password);
- }
+ public PasswordBlacklist resolvePasswordBlacklist(String blacklistName) {
- /**
- * 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 {
+ Objects.requireNonNull(blacklistName, "blacklistName");
- private static final double FALSE_POSITIVE_PROBABILITY = 0.01;
+ String cleanedBlacklistName = blacklistName.trim();
+ if (cleanedBlacklistName.isEmpty()) {
+ throw new IllegalArgumentException("Password blacklist name must not be empty!");
+ }
- private static final int BUFFER_SIZE_IN_BYTES = 512 * 1024;
+ return blacklistRegistry.computeIfAbsent(cleanedBlacklistName, (name) -> {
+ FileBasedPasswordBlacklist pbl = new FileBasedPasswordBlacklist(this.blacklistsBasePath, name);
+ pbl.lazyInit();
+ return pbl;
+ });
+ }
/**
- * The name of the blacklist filename.
+ * A {@link PasswordBlacklist} describes a list of too easy to guess
+ * or potentially leaked passwords that users should not be able to use.
*/
- private final String name;
+ public interface PasswordBlacklist {
- /**
- * The concrete path to the password-blacklist file.
- */
- private final Path path;
+
+ /**
+ * @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);
+ }
/**
- * Initialized lazily via {@link #lazyInit()}
+ * 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
*/
- private BloomFilter<String> blacklist;
+ public static class FileBasedPasswordBlacklist implements PasswordBlacklist {
- public FileBasedPasswordBlacklist(Path blacklistBasePath, String name) {
+ private static final double FALSE_POSITIVE_PROBABILITY = 0.01;
- this.name = name;
- this.path = blacklistBasePath.resolve(name);
+ private static final int BUFFER_SIZE_IN_BYTES = 512 * 1024;
+ /**
+ * The name of the blacklist filename.
+ */
+ private final String name;
- if (name.contains("/")) {
- // disallow '/' to avoid accidental filesystem traversal
- throw new IllegalArgumentException("" + name + " must not contain slashes!");
- }
+ /**
+ * The concrete path to the password-blacklist file.
+ */
+ private final Path path;
- if (!Files.exists(this.path)) {
- throw new IllegalArgumentException("Password blacklist " + name + " not found!");
- }
- }
+ /**
+ * Initialized lazily via {@link #lazyInit()}
+ */
+ private BloomFilter<String> blacklist;
- public String getName() {
- return name;
- }
+ public FileBasedPasswordBlacklist(Path blacklistBasePath, String name) {
- public boolean contains(String password) {
- return blacklist != null && blacklist.mightContain(password);
- }
+ this.name = name;
+ this.path = blacklistBasePath.resolve(name);
- void lazyInit() {
- if (blacklist != null) {
- return;
- }
+ if (name.contains("/")) {
+ // disallow '/' to avoid accidental filesystem traversal
+ throw new IllegalArgumentException("" + name + " must not contain slashes!");
+ }
- this.blacklist = load();
- }
+ if (!Files.exists(this.path)) {
+ throw new IllegalArgumentException("Password blacklist " + name + " not found!");
+ }
+ }
- /**
- * Loads the referenced blacklist into a {@link BloomFilter}.
- *
- * @return the {@link BloomFilter} backing a password blacklist
- */
- private BloomFilter<String> load() {
+ public String getName() {
+ return name;
+ }
- try {
- LOG.infof("Loading blacklist with name %s from %s - start", name, path);
+ public boolean contains(String password) {
+ return blacklist != null && blacklist.mightContain(password);
+ }
- long passwordCount = getPasswordCount();
+ void lazyInit() {
- BloomFilter<String> filter = BloomFilter.create(
- Funnels.stringFunnel(StandardCharsets.UTF_8),
- passwordCount,
- FALSE_POSITIVE_PROBABILITY);
+ if (blacklist != null) {
+ return;
+ }
- try (BufferedReader br = newReader(path)) {
- br.lines().forEach(filter::put);
+ this.blacklist = load();
}
- LOG.infof("Loading blacklist with name %s from %s - end", name, path);
+ /**
+ * Loads the referenced blacklist into a {@link BloomFilter}.
+ *
+ * @return the {@link BloomFilter} backing a password blacklist
+ */
+ private BloomFilter<String> load() {
- return filter;
- } catch (IOException e) {
- throw new RuntimeException("Could not load password blacklist from path: " + path, e);
- }
- }
+ try {
+ LOG.infof("Loading blacklist with name %s from %s - start", name, path);
- /**
- * Determines password blacklist size to correctly size the {@link BloomFilter} backing this blacklist.
- *
- * @return
- * @throws IOException
- */
- private long getPasswordCount() throws IOException {
+ 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();
- }
- }
+ 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);
- }
+ 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);
+ /**
+ * 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));
}
- }
- return ensureExists(Paths.get(pathFromJbossDataPath));
- }
- private static Path ensureExists(Path path) {
+ private static Path ensureExists(Path path) {
- Objects.requireNonNull(path, "path");
+ Objects.requireNonNull(path, "path");
- if (Files.exists(path)) {
- return path;
- }
+ if (Files.exists(path)) {
+ return path;
+ }
- throw new IllegalStateException("Password blacklists location does not exist: " + path);
+ throw new IllegalStateException("Password blacklists location does not exist: " + path);
+ }
}
- }
}
diff --git a/themes/src/main/resources/theme/base/admin/resources/templates/kc-tabs-client.html b/themes/src/main/resources/theme/base/admin/resources/templates/kc-tabs-client.html
index aa91fe8..40aab54 100755
--- a/themes/src/main/resources/theme/base/admin/resources/templates/kc-tabs-client.html
+++ b/themes/src/main/resources/theme/base/admin/resources/templates/kc-tabs-client.html
@@ -51,7 +51,7 @@
<a href="#/realms/{{realm.realm}}/clients/{{client.id}}/service-account-roles">{{:: 'service-account-roles' | translate}}</a>
<kc-tooltip>{{:: 'service-account-roles.tooltip' | translate}}</kc-tooltip>
</li>
- <li ng-class="{active: path[4] == 'permissions'}" data-ng-show="client.access.manage && access.manageAuthorization">
+ <li ng-class="{active: path[4] == 'permissions'}" data-ng-show="serverInfo.featureEnabled('AUTHORIZATION') && client.access.manage && access.manageAuthorization">
<a href="#/realms/{{realm.realm}}/clients/{{client.id}}/permissions">{{:: 'authz-permissions' | translate}}</a>
<kc-tooltip>{{:: 'manage-permissions-client.tooltip' | translate}}</kc-tooltip>
</li>
diff --git a/themes/src/main/resources/theme/base/admin/resources/templates/kc-tabs-group.html b/themes/src/main/resources/theme/base/admin/resources/templates/kc-tabs-group.html
index e73d998..b0abb95 100755
--- a/themes/src/main/resources/theme/base/admin/resources/templates/kc-tabs-group.html
+++ b/themes/src/main/resources/theme/base/admin/resources/templates/kc-tabs-group.html
@@ -9,7 +9,7 @@
<li ng-class="{active: path[4] == 'attributes'}"><a href="#/realms/{{realm.realm}}/groups/{{group.id}}/attributes">{{:: 'attributes' | translate}}</a></li>
<li ng-class="{active: path[4] == 'role-mappings'}" ><a href="#/realms/{{realm.realm}}/groups/{{group.id}}/role-mappings">{{:: 'role-mappings' | translate}}</a></li>
<li ng-class="{active: path[4] == 'members'}"><a href="#/realms/{{realm.realm}}/groups/{{group.id}}/members">{{:: 'members' | translate}}</a></li>
- <li ng-class="{active: path[4] == 'permissions'}" data-ng-show="group.access.manage && access.manageAuthorization">
+ <li ng-class="{active: path[4] == 'permissions'}" data-ng-show="serverInfo.featureEnabled('AUTHORIZATION') && group.access.manage && access.manageAuthorization">
<a href="#/realms/{{realm.realm}}/groups/{{group.id}}/permissions">{{:: 'authz-permissions' | translate}}</a>
<kc-tooltip>{{:: 'manage-permissions-group.tooltip' | translate}}</kc-tooltip>
</li>
diff --git a/themes/src/main/resources/theme/base/admin/resources/templates/kc-tabs-identity-provider.html b/themes/src/main/resources/theme/base/admin/resources/templates/kc-tabs-identity-provider.html
index 439bcfd..c8e69e3 100644
--- a/themes/src/main/resources/theme/base/admin/resources/templates/kc-tabs-identity-provider.html
+++ b/themes/src/main/resources/theme/base/admin/resources/templates/kc-tabs-identity-provider.html
@@ -12,6 +12,6 @@
<li ng-class="{active: !path[6] && path.length > 5}"><a href="#/realms/{{realm.realm}}/identity-provider-settings/provider/{{identityProvider.providerId}}/{{identityProvider.alias}}">{{:: 'settings' | translate}}</a></li>
<li ng-class="{active: path[4] == 'mappers'}"><a href="#/realms/{{realm.realm}}/identity-provider-mappers/{{identityProvider.alias}}/mappers">{{:: 'mappers' | translate}}</a></li>
<li ng-class="{active: path[6] == 'export'}"><a href="#/realms/{{realm.realm}}/identity-provider-settings/provider/{{identityProvider.providerId}}/{{identityProvider.alias}}/export" data-ng-show="!importFile && !newIdentityProvider && identityProvider.providerId == 'saml'">{{:: 'export' | translate}}</a></li>
- <li ng-class="{active: path[6] == 'permissions'}" data-ng-show="!newIdentityProvider && access.manageAuthorization"><a href="#/realms/{{realm.realm}}/identity-provider-settings/provider/{{identityProvider.providerId}}/{{identityProvider.alias}}/permissions">{{:: 'authz-permissions' | translate}}</a></li>
+ <li ng-class="{active: path[6] == 'permissions'}" data-ng-show="serverInfo.featureEnabled('AUTHORIZATION') && !newIdentityProvider && access.manageAuthorization"><a href="#/realms/{{realm.realm}}/identity-provider-settings/provider/{{identityProvider.providerId}}/{{identityProvider.alias}}/permissions">{{:: 'authz-permissions' | translate}}</a></li>
</ul>
</div>
diff --git a/themes/src/main/resources/theme/base/admin/resources/templates/kc-tabs-role.html b/themes/src/main/resources/theme/base/admin/resources/templates/kc-tabs-role.html
index 9c3c4a7..9128dfd 100755
--- a/themes/src/main/resources/theme/base/admin/resources/templates/kc-tabs-role.html
+++ b/themes/src/main/resources/theme/base/admin/resources/templates/kc-tabs-role.html
@@ -5,7 +5,7 @@
<ul class="nav nav-tabs" data-ng-show="!create">
<li ng-class="{active: !path[4]}"><a href="#/realms/{{realm.realm}}/roles/{{role.id}}">{{:: 'details' | translate}}</a></li>
- <li ng-class="{active: path[4] == 'permissions'}" data-ng-show="access.manageRealm && access.manageAuthorization">
+ <li ng-class="{active: path[4] == 'permissions'}" data-ng-show="serverInfo.featureEnabled('AUTHORIZATION') && access.manageRealm && access.manageAuthorization">
<a href="#/realms/{{realm.realm}}/roles/{{role.id}}/permissions">{{:: 'authz-permissions' | translate}}</a>
<kc-tooltip>{{:: 'manage-permissions-role.tooltip' | translate}}</kc-tooltip>
</li>
diff --git a/themes/src/main/resources/theme/base/admin/resources/templates/kc-tabs-users.html b/themes/src/main/resources/theme/base/admin/resources/templates/kc-tabs-users.html
index f641dc0..61fe890 100755
--- a/themes/src/main/resources/theme/base/admin/resources/templates/kc-tabs-users.html
+++ b/themes/src/main/resources/theme/base/admin/resources/templates/kc-tabs-users.html
@@ -3,7 +3,7 @@
<ul class="nav nav-tabs">
<li ng-class="{active: path[2] == 'users'}"><a href="#/realms/{{realm.realm}}/users">{{:: 'lookup' | translate}}</a></li>
- <li ng-class="{active: path[2] == 'users-permissions'}" data-ng-show="access.manageUsers && access.manageAuthorization">
+ <li ng-class="{active: path[2] == 'users-permissions'}" data-ng-show="serverInfo.featureEnabled('AUTHORIZATION') && access.manageUsers && access.manageAuthorization">
<a href="#/realms/{{realm.realm}}/users-permissions">{{:: 'authz-permissions' | translate}}</a>
<kc-tooltip>{{:: 'manage-permissions-users.tooltip' | translate}}</kc-tooltip>
</li>