MapDeploymentStateProviderFactory.java

/*
 * Copyright 2021 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.models.map.deploymentState;

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import org.jboss.logging.Logger;
import org.keycloak.Config;
import org.keycloak.common.Profile;
import org.keycloak.common.Version;
import org.keycloak.common.util.Base64Url;
import org.keycloak.common.util.SecretGenerator;
import org.keycloak.migration.MigrationModel;
import org.keycloak.migration.ModelVersion;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.models.DeploymentStateProvider;
import org.keycloak.models.DeploymentStateProviderFactory;
import org.keycloak.models.DeploymentStateSpi;
import org.keycloak.provider.EnvironmentDependentProviderFactory;

public class MapDeploymentStateProviderFactory implements DeploymentStateProviderFactory, EnvironmentDependentProviderFactory {

    public static final String PROVIDER_ID = "map";

    private static final String RESOURCES_VERSION_SEED = "resourcesVersionSeed";

    @Override
    public DeploymentStateProvider create(KeycloakSession session) {
        return INSTANCE;
    }

    @Override
    public void init(Config.Scope config) {
        String seed = config.get(RESOURCES_VERSION_SEED);
        if (seed == null) {
            // hardcoded until https://github.com/keycloak/keycloak/issues/13828 has been implemented
            Logger.getLogger(DeploymentStateProviderFactory.class)
                    .warnf("Version seed for deployment state set with a random number. Caution: This can lead to unstable operations when serving resources from the cluster without a sticky loadbalancer or when restarting nodes. Set the 'storage-deployment-state-version-seed' option with a secret seed to ensure stable operations.", RESOURCES_VERSION_SEED, PROVIDER_ID, DeploymentStateSpi.NAME);
            //generate random string for this installation
            seed = SecretGenerator.getInstance().randomString(10);
        }
        try {
            Version.RESOURCES_VERSION = Base64Url.encode(MessageDigest.getInstance("SHA-256")
                    .digest((seed + Version.RESOURCES_VERSION).getBytes()))
                    .substring(0, 5);
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public void postInit(KeycloakSessionFactory factory) {
    }

    @Override
    public String getId() {
        return PROVIDER_ID;
    }

    @Override
    public void close() {
    }

    @Override
    public boolean isSupported() {
        return Profile.isFeatureEnabled(Profile.Feature.MAP_STORAGE);
    }

    private static final DeploymentStateProvider INSTANCE =  new DeploymentStateProvider() {

        private final MigrationModel INSTANCE = new MigrationModel() {
            @Override
                public String getStoredVersion() {
                    return null;
                }
                @Override
                public String getResourcesTag() {
                    throw new UnsupportedOperationException("Not supported.");
                }
                @Override
                public void setStoredVersion(String version) {
                    throw new UnsupportedOperationException("Not supported.");
                }
        };

        @Override
        public MigrationModel getMigrationModel() {
            return INSTANCE;
        }

        @Override
        public void close() {
        }

    };
}