keycloak-aplcache

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>