Configuration Techniques

Our PropertyFileUserStorageProvider example is bit contrived. It is hardcoded to a property file that is embedded in the jar of the provider. Not very useful at all. We may want to make the location of this file configurable per instance of the provider. In other words, we may want to re-use this provider mulitple times in multiple different realms and point to completely different user property files. We’ll also want to do this configuration within the admin console UI.

The UserStorageProviderFactory has additional methods you can implement that deal with provider configuration. You describe the variables you want to configure per provider and the admin console will automatically render a generic input page to gather this configuration. There’s also callback methods to validate configuration before it is saved, when a provider is created for the first time, and when it is updated. UserStorageProviderFactory inherits these methods from the org.keycloak.component.ComponentFactory interface.

    List<ProviderConfigProperty> getConfigProperties();

    default
    void validateConfiguration(KeycloakSession session, RealmModel realm, ComponentModel model)
            throws ComponentValidationException
    {

    }

    default
    void onCreate(KeycloakSession session, RealmModel realm, ComponentModel model) {

    }

    default
    void onUpdate(KeycloakSession session, RealmModel realm, ComponentModel model) {

    }

The ComponentFactory.getConfigProperties() method returns a list of org.keycloak.provider.ProviderConfigProperty instances. These instances declare metadata that is needed to render and store each configuration variable of the provider.

Configuration Example

Let’s expand our PropertyFileUserStorageProviderFactory example to allow you to to point a provider instance to a specific file on disk.

PropertyFileUserStorageProviderFactory
public class PropertyFileUserStorageProviderFactory
                  implements UserStorageProviderFactory<PropertyFileUserStorageProvider> {

    protected static final List<ProviderConfigProperty> configMetadata;

    static {
        configMetadata = ProviderConfigurationBuilder.create()
                .property().name("path")
                .type(ProviderConfigProperty.STRING_TYPE)
                .label("Path")
                .defaultValue("${jboss.server.config.dir}/example-users.properties")
                .helpText("File path to properties file")
                .default
                .add().build();
    }

    @Override
    public List<ProviderConfigProperty> getConfigProperties() {
        return configMetadata;
    }

The ProviderConfigurationBuilder class is a great helper class to create a list of configuration properties. Here we specify a variable named path that is a string type. In the admin console config page for this provider, this config variable will be labed as Path and have a default value of ${jboss.server.config.dir}/example-users.properties. When you hover over the tooltip of this config option, it will display the help text File path to properties file.

The next thing we want to do is to verify that this file exists on disk. We don’t want to enable an instance of this provider in the realm unless it points to a valid user property file. To do this we implement the validateConfiguration() method.

    @Override
    public void validateConfiguration(KeycloakSession session, RealmModel realm, ComponentModel config)
                   throws ComponentValidationException {
        String fp = config.getConfig().getFirst("path");
        if (fp == null) throw new ComponentValidationException("user property file does not exist");
        fp = EnvUtil.replace(fp);
        File file = new File(fp);
        if (!file.exists()) {
            throw new ComponentValidationException("user property file does not exist");
        }
    }

In the validateConfiguration() method we get the config variable from the ComponentModel and we check to see if that file exists on disk. Notice that we use the org.keycloak.common.util.EnvUtil.replace()`method. With this method any string that has `${} within it will replace that with a system property value. The ${jboss.server.config.dir} string corresponds to the configuration/ directory of our server and is really useful for this example.

Next thing we have to do is remove the old init() method. We do this because user property files are going to be unique per provider instance. We move this logic to the create() method.

    @Override
    public PropertyFileUserStorageProvider create(KeycloakSession session, ComponentModel model) {
        String path = model.getConfig().getFirst("path");

        Properties props = new Properties();
        try {
            InputStream is = new FileInputStream(path);
            props.load(is);
            is.close();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }

        return new PropertyFileUserStorageProvider(session, model, props);
    }

This logic, is of course, is really inefficient as every different transaction will read the entire user property file from disk, but hopefully this illustrates, in a simple way, how to hook in configuration variables.

Configuring the Provider in Admin Console

Now that the configuration is enabled, you can set the path variable when you configure the provider in the admin console.

Configured Provider

storage-provider-with-config.png