KEYCLOAK-736 Database migration support"
This commit is contained in:
parent
101c7697fd
commit
94de88ef3b
42 changed files with 1371 additions and 193 deletions
74
connections/jpa-liquibase/pom.xml
Executable file
74
connections/jpa-liquibase/pom.xml
Executable file
|
@ -0,0 +1,74 @@
|
|||
<?xml version="1.0"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
<parent>
|
||||
<artifactId>keycloak-parent</artifactId>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<version>1.1.0-Alpha1-SNAPSHOT</version>
|
||||
<relativePath>../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>keycloak-connections-jpa-liquibase</artifactId>
|
||||
<name>Keycloak Connections JPA Liquibase Updater</name>
|
||||
<description/>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-connections-jpa</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.liquibase</groupId>
|
||||
<artifactId>liquibase-core</artifactId>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.yaml</groupId>
|
||||
<artifactId>snakeyaml</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jboss.logging</groupId>
|
||||
<artifactId>jboss-logging</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.liquibase</groupId>
|
||||
<artifactId>liquibase-maven-plugin</artifactId>
|
||||
<!-- set to ${liquibase.version} once Liquibase 3.2.3 is released (https://liquibase.jira.com/browse/CORE-1987) -->
|
||||
<version>3.1.1</version>
|
||||
<configuration>
|
||||
<changeLogFile>META-INF/jpa-changelog-master.xml</changeLogFile>
|
||||
|
||||
<url>${url}</url>
|
||||
<driver>${driver}</driver>
|
||||
<username>${username}</username>
|
||||
<password>${password}</password>
|
||||
|
||||
<referenceUrl>${referenceUrl}</referenceUrl>
|
||||
<referenceDriver>${driver}</referenceDriver>
|
||||
<referenceUsername>${username}</referenceUsername>
|
||||
<referencePassword>${password}</referencePassword>
|
||||
</configuration>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.h2database</groupId>
|
||||
<artifactId>h2</artifactId>
|
||||
<version>${h2.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
<properties>
|
||||
<username>sa</username>
|
||||
<password></password>
|
||||
<driver>org.h2.Driver</driver>
|
||||
</properties>
|
||||
</project>
|
|
@ -0,0 +1,208 @@
|
|||
package org.keycloak.connections.jpa.updater.liquibase;
|
||||
|
||||
import liquibase.Contexts;
|
||||
import liquibase.Liquibase;
|
||||
import liquibase.changelog.ChangeSet;
|
||||
import liquibase.changelog.DatabaseChangeLog;
|
||||
import liquibase.changelog.RanChangeSet;
|
||||
import liquibase.database.Database;
|
||||
import liquibase.database.DatabaseFactory;
|
||||
import liquibase.database.core.DB2Database;
|
||||
import liquibase.database.core.DerbyDatabase;
|
||||
import liquibase.database.core.FirebirdDatabase;
|
||||
import liquibase.database.core.H2Database;
|
||||
import liquibase.database.core.HsqlDatabase;
|
||||
import liquibase.database.core.InformixDatabase;
|
||||
import liquibase.database.core.MSSQLDatabase;
|
||||
import liquibase.database.core.MySQLDatabase;
|
||||
import liquibase.database.core.OracleDatabase;
|
||||
import liquibase.database.core.PostgresDatabase;
|
||||
import liquibase.database.core.SQLiteDatabase;
|
||||
import liquibase.database.core.SybaseASADatabase;
|
||||
import liquibase.database.core.SybaseDatabase;
|
||||
import liquibase.database.jvm.JdbcConnection;
|
||||
import liquibase.exception.LiquibaseException;
|
||||
import liquibase.logging.LogFactory;
|
||||
import liquibase.logging.LogLevel;
|
||||
import liquibase.resource.ClassLoaderResourceAccessor;
|
||||
import liquibase.servicelocator.ServiceLocator;
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.connections.jpa.updater.JpaUpdaterProvider;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Statement;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public class LiquibaseJpaUpdaterProvider implements JpaUpdaterProvider {
|
||||
|
||||
private static final Logger logger = Logger.getLogger(LiquibaseJpaUpdaterProvider.class);
|
||||
|
||||
private static final String CHANGELOG = "META-INF/jpa-changelog-master.xml";
|
||||
|
||||
@Override
|
||||
public String getCurrentVersionSql() {
|
||||
return "SELECT ID from DATABASECHANGELOG ORDER BY DATEEXECUTED DESC LIMIT 1";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(Connection connection) {
|
||||
logger.debug("Starting database update");
|
||||
|
||||
try {
|
||||
Liquibase liquibase = getLiquibase(connection);
|
||||
|
||||
List<ChangeSet> changeSets = liquibase.listUnrunChangeSets((Contexts) null);
|
||||
if (!changeSets.isEmpty()) {
|
||||
if (changeSets.get(0).getId().equals(FIRST_VERSION)) {
|
||||
Statement statement = connection.createStatement();
|
||||
try {
|
||||
statement.executeQuery("SELECT id FROM REALM");
|
||||
|
||||
logger.infov("Updating database from {0} to {1}", FIRST_VERSION, changeSets.get(changeSets.size() - 1).getId());
|
||||
liquibase.markNextChangeSetRan((Contexts) null);
|
||||
} catch (SQLException e) {
|
||||
logger.info("Initializing database schema");
|
||||
}
|
||||
} else {
|
||||
if (logger.isDebugEnabled()) {
|
||||
List<RanChangeSet> ranChangeSets = liquibase.getDatabase().getRanChangeSetList();
|
||||
logger.debugv("Updating database from {0} to {1}", ranChangeSets.get(ranChangeSets.size() - 1).getId(), changeSets.get(changeSets.size() - 1).getId());
|
||||
} else {
|
||||
logger.infov("Updating database");
|
||||
}
|
||||
}
|
||||
|
||||
liquibase.update((Contexts) null);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Failed to update database", e);
|
||||
}
|
||||
logger.debug("Completed database update");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validate(Connection connection) {
|
||||
try {
|
||||
Liquibase liquibase = getLiquibase(connection);
|
||||
|
||||
liquibase.validate();
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Failed to validate database", e);
|
||||
}
|
||||
}
|
||||
|
||||
private Liquibase getLiquibase(Connection connection) throws Exception {
|
||||
if (!System.getProperties().containsKey("liquibase.scan.packages")) {
|
||||
System.setProperty("liquibase.scan.packages", "liquibase.change,liquibase.changelog,liquibase.database,liquibase.parser,liquibase.precondition,liquibase.datatype,liquibase.serializer,liquibase.sqlgenerator,liquibase.executor,liquibase.snapshot,liquibase.logging,liquibase.diff,liquibase.structure,liquibase.structurecompare,liquibase.lockservice");
|
||||
}
|
||||
LogFactory.setInstance(new LogWrapper());
|
||||
Database database = DatabaseFactory.getInstance().findCorrectDatabaseImplementation(new JdbcConnection(connection));
|
||||
return new Liquibase(CHANGELOG, new ClassLoaderResourceAccessor(getClass().getClassLoader()), database);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
}
|
||||
|
||||
private static class LogWrapper extends LogFactory {
|
||||
|
||||
private liquibase.logging.Logger logger = new liquibase.logging.Logger() {
|
||||
@Override
|
||||
public void setName(String name) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setLogLevel(String level) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setLogLevel(LogLevel level) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setLogLevel(String logLevel, String logFile) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void severe(String message) {
|
||||
LiquibaseJpaUpdaterProvider.logger.error(message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void severe(String message, Throwable e) {
|
||||
LiquibaseJpaUpdaterProvider.logger.error(message, e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void warning(String message) {
|
||||
LiquibaseJpaUpdaterProvider.logger.warn(message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void warning(String message, Throwable e) {
|
||||
LiquibaseJpaUpdaterProvider.logger.warn(message, e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void info(String message) {
|
||||
LiquibaseJpaUpdaterProvider.logger.debug(message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void info(String message, Throwable e) {
|
||||
LiquibaseJpaUpdaterProvider.logger.debug(message, e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void debug(String message) {
|
||||
LiquibaseJpaUpdaterProvider.logger.trace(message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public LogLevel getLogLevel() {
|
||||
if (LiquibaseJpaUpdaterProvider.logger.isTraceEnabled()) {
|
||||
return LogLevel.DEBUG;
|
||||
} else if (LiquibaseJpaUpdaterProvider.logger.isDebugEnabled()) {
|
||||
return LogLevel.INFO;
|
||||
} else {
|
||||
return LogLevel.WARNING;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void debug(String message, Throwable e) {
|
||||
LiquibaseJpaUpdaterProvider.logger.trace(message, e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setChangeLog(DatabaseChangeLog databaseChangeLog) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setChangeSet(ChangeSet changeSet) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPriority() {
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public liquibase.logging.Logger getLog(String name) {
|
||||
return logger;
|
||||
}
|
||||
|
||||
@Override
|
||||
public liquibase.logging.Logger getLog() {
|
||||
return logger;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
package org.keycloak.connections.jpa.updater.liquibase;
|
||||
|
||||
import org.keycloak.Config;
|
||||
import org.keycloak.connections.jpa.updater.JpaUpdaterProvider;
|
||||
import org.keycloak.connections.jpa.updater.JpaUpdaterProviderFactory;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public class LiquibaseJpaUpdaterProviderFactory implements JpaUpdaterProviderFactory {
|
||||
|
||||
@Override
|
||||
public JpaUpdaterProvider create(KeycloakSession session) {
|
||||
return new LiquibaseJpaUpdaterProvider();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(Config.Scope config) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return "liquibase";
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,376 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.2.xsd">
|
||||
<changeSet author="sthorger@redhat.com" id="1.0.0.Final">
|
||||
<createTable tableName="APPLICATION_DEFAULT_ROLES">
|
||||
<column name="APPLICATION_ID" type="VARCHAR(36)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="ROLE_ID" type="VARCHAR(36)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
</createTable>
|
||||
<createTable tableName="CLIENT">
|
||||
<column name="DTYPE" type="VARCHAR(31)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="ID" type="VARCHAR(36)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="ALLOWED_CLAIMS_MASK" type="BIGINT(19)"/>
|
||||
<column name="ENABLED" type="BOOLEAN(1)"/>
|
||||
<column name="FULL_SCOPE_ALLOWED" type="BOOLEAN(1)"/>
|
||||
<column name="NAME" type="VARCHAR(255)"/>
|
||||
<column name="NOT_BEFORE" type="INT(10)"/>
|
||||
<column name="PUBLIC_CLIENT" type="BOOLEAN(1)"/>
|
||||
<column name="SECRET" type="VARCHAR(255)"/>
|
||||
<column name="BASE_URL" type="VARCHAR(255)"/>
|
||||
<column name="BEARER_ONLY" type="BOOLEAN(1)"/>
|
||||
<column name="MANAGEMENT_URL" type="VARCHAR(255)"/>
|
||||
<column name="SURROGATE_AUTH_REQUIRED" type="BOOLEAN(1)"/>
|
||||
<column name="DIRECT_GRANTS_ONLY" type="BOOLEAN(1)"/>
|
||||
<column name="REALM_ID" type="VARCHAR(36)"/>
|
||||
</createTable>
|
||||
<createTable tableName="CLIENT_SESSION">
|
||||
<column name="ID" type="VARCHAR(36)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="ACTION" type="INT(10)"/>
|
||||
<column name="CLIENT_ID" type="VARCHAR(36)"/>
|
||||
<column name="REDIRECT_URI" type="VARCHAR(255)"/>
|
||||
<column name="STATE" type="VARCHAR(255)"/>
|
||||
<column name="TIMESTAMP" type="INT(10)"/>
|
||||
<column name="SESSION_ID" type="VARCHAR(36)"/>
|
||||
</createTable>
|
||||
<createTable tableName="CLIENT_SESSION_ROLE">
|
||||
<column name="ROLE_ID" type="VARCHAR(255)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="CLIENT_SESSION" type="VARCHAR(36)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
</createTable>
|
||||
<createTable tableName="COMPOSITE_ROLE">
|
||||
<column name="COMPOSITE" type="VARCHAR(36)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="CHILD_ROLE" type="VARCHAR(36)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
</createTable>
|
||||
<createTable tableName="CREDENTIAL">
|
||||
<column name="ID" type="VARCHAR(36)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="DEVICE" type="VARCHAR(255)"/>
|
||||
<column name="HASH_ITERATIONS" type="INT(10)"/>
|
||||
<column name="SALT" type="BINARY(255)"/>
|
||||
<column name="TYPE" type="VARCHAR(255)"/>
|
||||
<column name="VALUE" type="VARCHAR(255)"/>
|
||||
<column name="USER_ID" type="VARCHAR(36)"/>
|
||||
</createTable>
|
||||
<createTable tableName="EVENT_ENTITY">
|
||||
<column name="ID" type="VARCHAR(36)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="CLIENT_ID" type="VARCHAR(255)"/>
|
||||
<column name="DETAILS_JSON" type="VARCHAR(2550)"/>
|
||||
<column name="ERROR" type="VARCHAR(255)"/>
|
||||
<column name="IP_ADDRESS" type="VARCHAR(255)"/>
|
||||
<column name="REALM_ID" type="VARCHAR(255)"/>
|
||||
<column name="SESSION_ID" type="VARCHAR(255)"/>
|
||||
<column name="TIME" type="BIGINT(19)"/>
|
||||
<column name="TYPE" type="VARCHAR(255)"/>
|
||||
<column name="USER_ID" type="VARCHAR(255)"/>
|
||||
</createTable>
|
||||
<createTable tableName="FED_PROVIDERS">
|
||||
<column name="REALM_ID" type="VARCHAR(36)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="USERFEDERATIONPROVIDERS_ID" type="VARCHAR(36)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
</createTable>
|
||||
<createTable tableName="KEYCLOAK_ROLE">
|
||||
<column name="ID" type="VARCHAR(36)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="APP_REALM_CONSTRAINT" type="VARCHAR(36)"/>
|
||||
<column name="APPLICATION_ROLE" type="BOOLEAN(1)"/>
|
||||
<column name="DESCRIPTION" type="VARCHAR(255)"/>
|
||||
<column name="NAME" type="VARCHAR(255)"/>
|
||||
<column name="REALM_ID" type="VARCHAR(255)"/>
|
||||
<column name="APPLICATION" type="VARCHAR(36)"/>
|
||||
<column name="REALM" type="VARCHAR(36)"/>
|
||||
</createTable>
|
||||
<createTable tableName="REALM">
|
||||
<column name="ID" type="VARCHAR(36)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="ACCESS_CODE_LIFESPAN" type="INT(10)"/>
|
||||
<column name="USER_ACTION_LIFESPAN" type="INT(10)"/>
|
||||
<column name="ACCESS_TOKEN_LIFESPAN" type="INT(10)"/>
|
||||
<column name="ACCOUNT_THEME" type="VARCHAR(255)"/>
|
||||
<column name="ADMIN_THEME" type="VARCHAR(255)"/>
|
||||
<column name="EMAIL_THEME" type="VARCHAR(255)"/>
|
||||
<column name="ENABLED" type="BOOLEAN(1)"/>
|
||||
<column name="EVENTS_ENABLED" type="BOOLEAN(1)"/>
|
||||
<column name="EVENTS_EXPIRATION" type="BIGINT(19)"/>
|
||||
<column name="LOGIN_THEME" type="VARCHAR(255)"/>
|
||||
<column name="NAME" type="VARCHAR(255)"/>
|
||||
<column name="NOT_BEFORE" type="INT(10)"/>
|
||||
<column name="PASSWORD_CRED_GRANT_ALLOWED" type="BOOLEAN(1)"/>
|
||||
<column name="PASSWORD_POLICY" type="VARCHAR(255)"/>
|
||||
<column name="PRIVATE_KEY" type="VARCHAR(2048)"/>
|
||||
<column name="PUBLIC_KEY" type="VARCHAR(2048)"/>
|
||||
<column name="REGISTRATION_ALLOWED" type="BOOLEAN(1)"/>
|
||||
<column name="REMEMBER_ME" type="BOOLEAN(1)"/>
|
||||
<column name="RESET_PASSWORD_ALLOWED" type="BOOLEAN(1)"/>
|
||||
<column name="SOCIAL" type="BOOLEAN(1)"/>
|
||||
<column name="SSL_REQUIRED" type="VARCHAR(255)"/>
|
||||
<column name="SSO_IDLE_TIMEOUT" type="INT(10)"/>
|
||||
<column name="SSO_MAX_LIFESPAN" type="INT(10)"/>
|
||||
<column name="UPDATE_PROFILE_ON_SOC_LOGIN" type="BOOLEAN(1)"/>
|
||||
<column name="VERIFY_EMAIL" type="BOOLEAN(1)"/>
|
||||
<column name="MASTER_ADMIN_APP" type="VARCHAR(36)"/>
|
||||
</createTable>
|
||||
<createTable tableName="REALM_APPLICATION">
|
||||
<column name="APPLICATION_ID" type="VARCHAR(36)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="REALM_ID" type="VARCHAR(36)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
</createTable>
|
||||
<createTable tableName="REALM_ATTRIBUTE">
|
||||
<column name="NAME" type="VARCHAR(255)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="VALUE" type="VARCHAR(255)"/>
|
||||
<column name="REALM_ID" type="VARCHAR(36)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
</createTable>
|
||||
<createTable tableName="REALM_DEFAULT_ROLES">
|
||||
<column name="REALM_ID" type="VARCHAR(36)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="ROLE_ID" type="VARCHAR(36)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
</createTable>
|
||||
<createTable tableName="REALM_EVENTS_LISTENERS">
|
||||
<column name="REALM_ID" type="VARCHAR(36)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="VALUE" type="VARCHAR(255)"/>
|
||||
</createTable>
|
||||
<createTable tableName="REALM_REQUIRED_CREDENTIAL">
|
||||
<column name="TYPE" type="VARCHAR(255)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="FORM_LABEL" type="VARCHAR(255)"/>
|
||||
<column name="INPUT" type="BOOLEAN(1)"/>
|
||||
<column name="SECRET" type="BOOLEAN(1)"/>
|
||||
<column name="REALM_ID" type="VARCHAR(36)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
</createTable>
|
||||
<createTable tableName="REALM_SMTP_CONFIG">
|
||||
<column name="REALM_ID" type="VARCHAR(36)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="VALUE" type="VARCHAR(255)"/>
|
||||
<column name="NAME" type="VARCHAR(255)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
</createTable>
|
||||
<createTable tableName="REALM_SOCIAL_CONFIG">
|
||||
<column name="REALM_ID" type="VARCHAR(36)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="VALUE" type="VARCHAR(255)"/>
|
||||
<column name="NAME" type="VARCHAR(255)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
</createTable>
|
||||
<createTable tableName="REDIRECT_URIS">
|
||||
<column name="CLIENT_ID" type="VARCHAR(36)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="VALUE" type="VARCHAR(255)"/>
|
||||
</createTable>
|
||||
<createTable tableName="SCOPE_MAPPING">
|
||||
<column name="CLIENT_ID" type="VARCHAR(36)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="ROLE_ID" type="VARCHAR(36)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
</createTable>
|
||||
<createTable tableName="USERNAME_LOGIN_FAILURE">
|
||||
<column name="REALM_ID" type="VARCHAR(36)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="USERNAME" type="VARCHAR(200)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="FAILED_LOGIN_NOT_BEFORE" type="INT(10)"/>
|
||||
<column name="LAST_FAILURE" type="BIGINT(19)"/>
|
||||
<column name="LAST_IP_FAILURE" type="VARCHAR(255)"/>
|
||||
<column name="NUM_FAILURES" type="INT(10)"/>
|
||||
</createTable>
|
||||
<createTable tableName="USER_ATTRIBUTE">
|
||||
<column name="NAME" type="VARCHAR(255)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="VALUE" type="VARCHAR(255)"/>
|
||||
<column name="USER_ID" type="VARCHAR(36)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
</createTable>
|
||||
<createTable tableName="USER_ENTITY">
|
||||
<column name="ID" type="VARCHAR(36)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="EMAIL" type="VARCHAR(255)"/>
|
||||
<column name="EMAIL_CONSTRAINT" type="VARCHAR(255)"/>
|
||||
<column name="EMAIL_VERIFIED" type="BOOLEAN(1)"/>
|
||||
<column name="ENABLED" type="BOOLEAN(1)"/>
|
||||
<column name="FEDERATION_LINK" type="VARCHAR(255)"/>
|
||||
<column name="FIRST_NAME" type="VARCHAR(255)"/>
|
||||
<column name="LAST_NAME" type="VARCHAR(255)"/>
|
||||
<column name="REALM_ID" type="VARCHAR(255)"/>
|
||||
<column name="TOTP" type="BOOLEAN(1)"/>
|
||||
<column name="USERNAME" type="VARCHAR(255)"/>
|
||||
</createTable>
|
||||
<createTable tableName="USER_FEDERATION_CONFIG">
|
||||
<column name="USER_FEDERATION_PROVIDER_ID" type="VARCHAR(36)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="VALUE" type="VARCHAR(255)"/>
|
||||
<column name="NAME" type="VARCHAR(255)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
</createTable>
|
||||
<createTable tableName="USER_FEDERATION_PROVIDER">
|
||||
<column name="ID" type="VARCHAR(36)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="CHANGED_SYNC_PERIOD" type="INT(10)"/>
|
||||
<column name="DISPLAY_NAME" type="VARCHAR(255)"/>
|
||||
<column name="FULL_SYNC_PERIOD" type="INT(10)"/>
|
||||
<column name="LAST_SYNC" type="INT(10)"/>
|
||||
<column name="PRIORITY" type="INT(10)"/>
|
||||
<column name="PROVIDER_NAME" type="VARCHAR(255)"/>
|
||||
<column name="REALM_ID" type="VARCHAR(36)"/>
|
||||
</createTable>
|
||||
<createTable tableName="USER_REQUIRED_ACTION">
|
||||
<column name="ACTION" type="INT(10)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="USER_ID" type="VARCHAR(36)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
</createTable>
|
||||
<createTable tableName="USER_ROLE_MAPPING">
|
||||
<column name="ROLE_ID" type="VARCHAR(255)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="USER_ID" type="VARCHAR(36)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
</createTable>
|
||||
<createTable tableName="USER_SESSION">
|
||||
<column name="ID" type="VARCHAR(36)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="AUTH_METHOD" type="VARCHAR(255)"/>
|
||||
<column name="IP_ADDRESS" type="VARCHAR(255)"/>
|
||||
<column name="LAST_SESSION_REFRESH" type="INT(10)"/>
|
||||
<column name="LOGIN_USERNAME" type="VARCHAR(255)"/>
|
||||
<column name="REALM_ID" type="VARCHAR(255)"/>
|
||||
<column name="REMEMBER_ME" type="BOOLEAN(1)"/>
|
||||
<column name="STARTED" type="INT(10)"/>
|
||||
<column name="USER_ID" type="VARCHAR(255)"/>
|
||||
</createTable>
|
||||
<createTable tableName="USER_SOCIAL_LINK">
|
||||
<column name="SOCIAL_PROVIDER" type="VARCHAR(255)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="REALM_ID" type="VARCHAR(255)"/>
|
||||
<column name="SOCIAL_USER_ID" type="VARCHAR(255)"/>
|
||||
<column name="SOCIAL_USERNAME" type="VARCHAR(255)"/>
|
||||
<column name="USER_ID" type="VARCHAR(36)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
</createTable>
|
||||
<createTable tableName="WEB_ORIGINS">
|
||||
<column name="CLIENT_ID" type="VARCHAR(36)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="VALUE" type="VARCHAR(255)"/>
|
||||
</createTable>
|
||||
<addPrimaryKey columnNames="REALM_ID, NAME" constraintName="CONSTRAINT_1" tableName="REALM_SOCIAL_CONFIG"/>
|
||||
<addPrimaryKey columnNames="REALM_ID, USERNAME" constraintName="CONSTRAINT_17" tableName="USERNAME_LOGIN_FAILURE"/>
|
||||
<addPrimaryKey columnNames="ACTION, USER_ID" constraintName="CONSTRAINT_2" tableName="USER_REQUIRED_ACTION"/>
|
||||
<addPrimaryKey columnNames="SOCIAL_PROVIDER, USER_ID" constraintName="CONSTRAINT_3" tableName="USER_SOCIAL_LINK"/>
|
||||
<addPrimaryKey columnNames="ID" constraintName="CONSTRAINT_4" tableName="EVENT_ENTITY"/>
|
||||
<addPrimaryKey columnNames="ID" constraintName="CONSTRAINT_4A" tableName="REALM"/>
|
||||
<addPrimaryKey columnNames="CLIENT_SESSION, ROLE_ID" constraintName="CONSTRAINT_5" tableName="CLIENT_SESSION_ROLE"/>
|
||||
<addPrimaryKey columnNames="ID" constraintName="CONSTRAINT_57" tableName="USER_SESSION"/>
|
||||
<addPrimaryKey columnNames="ID" constraintName="CONSTRAINT_5C" tableName="USER_FEDERATION_PROVIDER"/>
|
||||
<addPrimaryKey columnNames="NAME, USER_ID" constraintName="CONSTRAINT_6" tableName="USER_ATTRIBUTE"/>
|
||||
<addPrimaryKey columnNames="ID" constraintName="CONSTRAINT_7" tableName="CLIENT"/>
|
||||
<addPrimaryKey columnNames="ID" constraintName="CONSTRAINT_8" tableName="CLIENT_SESSION"/>
|
||||
<addPrimaryKey columnNames="CLIENT_ID, ROLE_ID" constraintName="CONSTRAINT_81" tableName="SCOPE_MAPPING"/>
|
||||
<addPrimaryKey columnNames="NAME, REALM_ID" constraintName="CONSTRAINT_9" tableName="REALM_ATTRIBUTE"/>
|
||||
<addPrimaryKey columnNames="REALM_ID, TYPE" constraintName="CONSTRAINT_92" tableName="REALM_REQUIRED_CREDENTIAL"/>
|
||||
<addPrimaryKey columnNames="ID" constraintName="CONSTRAINT_A" tableName="KEYCLOAK_ROLE"/>
|
||||
<addPrimaryKey columnNames="ROLE_ID, USER_ID" constraintName="CONSTRAINT_C" tableName="USER_ROLE_MAPPING"/>
|
||||
<addPrimaryKey columnNames="REALM_ID, NAME" constraintName="CONSTRAINT_E" tableName="REALM_SMTP_CONFIG"/>
|
||||
<addPrimaryKey columnNames="ID" constraintName="CONSTRAINT_F" tableName="CREDENTIAL"/>
|
||||
<addPrimaryKey columnNames="USER_FEDERATION_PROVIDER_ID, NAME" constraintName="CONSTRAINT_F9" tableName="USER_FEDERATION_CONFIG"/>
|
||||
<addPrimaryKey columnNames="ID" constraintName="CONSTRAINT_FB" tableName="USER_ENTITY"/>
|
||||
<addUniqueConstraint columnNames="ROLE_ID" constraintName="UK_8AELWNIBJI49AVXSRTUF6XJOW" deferrable="false" disabled="false" initiallyDeferred="false" tableName="APPLICATION_DEFAULT_ROLES"/>
|
||||
<addUniqueConstraint columnNames="REALM_ID,NAME" constraintName="UK_B71CJLBENV945RB6GCON438AT" deferrable="false" disabled="false" initiallyDeferred="false" tableName="CLIENT"/>
|
||||
<addUniqueConstraint columnNames="USERFEDERATIONPROVIDERS_ID" constraintName="UK_DCCIRJLIPU1478VQC89DID88C" deferrable="false" disabled="false" initiallyDeferred="false" tableName="FED_PROVIDERS"/>
|
||||
<addUniqueConstraint columnNames="REALM_ID,EMAIL_CONSTRAINT" constraintName="UK_DYKN684SL8UP1CRFEI6ECKHD7" deferrable="false" disabled="false" initiallyDeferred="false" tableName="USER_ENTITY"/>
|
||||
<addUniqueConstraint columnNames="ROLE_ID" constraintName="UK_H4WPD7W4HSOOLNI3H0SW7BTJE" deferrable="false" disabled="false" initiallyDeferred="false" tableName="REALM_DEFAULT_ROLES"/>
|
||||
<addUniqueConstraint columnNames="NAME,APP_REALM_CONSTRAINT" constraintName="UK_J3RWUVD56ONTGSUHOGM184WW2" deferrable="false" disabled="false" initiallyDeferred="false" tableName="KEYCLOAK_ROLE"/>
|
||||
<addUniqueConstraint columnNames="REALM_ID" constraintName="UK_L5QGA3RFME47335JY8JXYXH3I" deferrable="false" disabled="false" initiallyDeferred="false" tableName="REALM_APPLICATION"/>
|
||||
<addUniqueConstraint columnNames="NAME" constraintName="UK_ORVSDMLA56612EAEFIQ6WL5OI" deferrable="false" disabled="false" initiallyDeferred="false" tableName="REALM"/>
|
||||
<addUniqueConstraint columnNames="REALM_ID,USERNAME" constraintName="UK_RU8TT6T700S9V50BU18WS5HA6" deferrable="false" disabled="false" initiallyDeferred="false" tableName="USER_ENTITY"/>
|
||||
<addForeignKeyConstraint baseColumnNames="CLIENT_SESSION" baseTableName="CLIENT_SESSION_ROLE" constraintName="FK_11B7SGQW18I532811V7O2DV76" deferrable="false" initiallyDeferred="false" onDelete="RESTRICT" onUpdate="RESTRICT" referencedColumnNames="ID" referencedTableName="CLIENT_SESSION"/>
|
||||
<addForeignKeyConstraint baseColumnNames="CLIENT_ID" baseTableName="REDIRECT_URIS" constraintName="FK_1BURS8PB4OUJ97H5WUPPAHV9F" deferrable="false" initiallyDeferred="false" onDelete="RESTRICT" onUpdate="RESTRICT" referencedColumnNames="ID" referencedTableName="CLIENT"/>
|
||||
<addForeignKeyConstraint baseColumnNames="REALM_ID" baseTableName="USER_FEDERATION_PROVIDER" constraintName="FK_1FJ32F6PTOLW2QY60CD8N01E8" deferrable="false" initiallyDeferred="false" onDelete="RESTRICT" onUpdate="RESTRICT" referencedColumnNames="ID" referencedTableName="REALM"/>
|
||||
<addForeignKeyConstraint baseColumnNames="REALM_ID" baseTableName="FED_PROVIDERS" constraintName="FK_213LYQ09FKXQ8K8NY8DY3737T" deferrable="false" initiallyDeferred="false" onDelete="RESTRICT" onUpdate="RESTRICT" referencedColumnNames="ID" referencedTableName="REALM"/>
|
||||
<addForeignKeyConstraint baseColumnNames="REALM_ID" baseTableName="REALM_REQUIRED_CREDENTIAL" constraintName="FK_5HG65LYBEVAVKQFKI3KPONH9V" deferrable="false" initiallyDeferred="false" onDelete="RESTRICT" onUpdate="RESTRICT" referencedColumnNames="ID" referencedTableName="REALM"/>
|
||||
<addForeignKeyConstraint baseColumnNames="USER_ID" baseTableName="USER_ATTRIBUTE" constraintName="FK_5HRM2VLF9QL5FU043KQEPOVBR" deferrable="false" initiallyDeferred="false" onDelete="RESTRICT" onUpdate="RESTRICT" referencedColumnNames="ID" referencedTableName="USER_ENTITY"/>
|
||||
<addForeignKeyConstraint baseColumnNames="USER_ID" baseTableName="USER_SOCIAL_LINK" constraintName="FK_68CJYS5UWM55UY823Y75XG4OM" deferrable="false" initiallyDeferred="false" onDelete="RESTRICT" onUpdate="RESTRICT" referencedColumnNames="ID" referencedTableName="USER_ENTITY"/>
|
||||
<addForeignKeyConstraint baseColumnNames="USER_ID" baseTableName="USER_REQUIRED_ACTION" constraintName="FK_6QJ3W1JW9CVAFHE19BWSIUVMD" deferrable="false" initiallyDeferred="false" onDelete="RESTRICT" onUpdate="RESTRICT" referencedColumnNames="ID" referencedTableName="USER_ENTITY"/>
|
||||
<addForeignKeyConstraint baseColumnNames="REALM" baseTableName="KEYCLOAK_ROLE" constraintName="FK_6VYQFE4CN4WLQ8R6KT5VDSJ5C" deferrable="false" initiallyDeferred="false" onDelete="RESTRICT" onUpdate="RESTRICT" referencedColumnNames="ID" referencedTableName="REALM"/>
|
||||
<addForeignKeyConstraint baseColumnNames="REALM_ID" baseTableName="REALM_SMTP_CONFIG" constraintName="FK_70EJ8XDXGXD0B9HH6180IRR0O" deferrable="false" initiallyDeferred="false" onDelete="RESTRICT" onUpdate="RESTRICT" referencedColumnNames="ID" referencedTableName="REALM"/>
|
||||
<addForeignKeyConstraint baseColumnNames="APPLICATION_ID" baseTableName="REALM_APPLICATION" constraintName="FK_71S3P0DIUXAWWQQSA528UBY2Q" deferrable="false" initiallyDeferred="false" onDelete="RESTRICT" onUpdate="RESTRICT" referencedColumnNames="ID" referencedTableName="REALM"/>
|
||||
<addForeignKeyConstraint baseColumnNames="ROLE_ID" baseTableName="APPLICATION_DEFAULT_ROLES" constraintName="FK_8AELWNIBJI49AVXSRTUF6XJOW" deferrable="false" initiallyDeferred="false" onDelete="RESTRICT" onUpdate="RESTRICT" referencedColumnNames="ID" referencedTableName="KEYCLOAK_ROLE"/>
|
||||
<addForeignKeyConstraint baseColumnNames="REALM_ID" baseTableName="REALM_ATTRIBUTE" constraintName="FK_8SHXD6L3E9ATQUKACXGPFFPTW" deferrable="false" initiallyDeferred="false" onDelete="RESTRICT" onUpdate="RESTRICT" referencedColumnNames="ID" referencedTableName="REALM"/>
|
||||
<addForeignKeyConstraint baseColumnNames="COMPOSITE" baseTableName="COMPOSITE_ROLE" constraintName="FK_A63WVEKFTU8JO1PNJ81E7MCE2" deferrable="false" initiallyDeferred="false" onDelete="RESTRICT" onUpdate="RESTRICT" referencedColumnNames="ID" referencedTableName="KEYCLOAK_ROLE"/>
|
||||
<addForeignKeyConstraint baseColumnNames="SESSION_ID" baseTableName="CLIENT_SESSION" constraintName="FK_B4AO2VCVAT6UKAU74WBWTFQO1" deferrable="false" initiallyDeferred="false" onDelete="RESTRICT" onUpdate="RESTRICT" referencedColumnNames="ID" referencedTableName="USER_SESSION"/>
|
||||
<addForeignKeyConstraint baseColumnNames="USER_ID" baseTableName="USER_ROLE_MAPPING" constraintName="FK_C4FQV34P1MBYLLOXANG7B1Q3L" deferrable="false" initiallyDeferred="false" onDelete="RESTRICT" onUpdate="RESTRICT" referencedColumnNames="ID" referencedTableName="USER_ENTITY"/>
|
||||
<addForeignKeyConstraint baseColumnNames="USERFEDERATIONPROVIDERS_ID" baseTableName="FED_PROVIDERS" constraintName="FK_DCCIRJLIPU1478VQC89DID88C" deferrable="false" initiallyDeferred="false" onDelete="RESTRICT" onUpdate="RESTRICT" referencedColumnNames="ID" referencedTableName="USER_FEDERATION_PROVIDER"/>
|
||||
<addForeignKeyConstraint baseColumnNames="REALM_ID" baseTableName="REALM_DEFAULT_ROLES" constraintName="FK_EVUDB1PPW84OXFAX2DRS03ICC" deferrable="false" initiallyDeferred="false" onDelete="RESTRICT" onUpdate="RESTRICT" referencedColumnNames="ID" referencedTableName="REALM"/>
|
||||
<addForeignKeyConstraint baseColumnNames="CHILD_ROLE" baseTableName="COMPOSITE_ROLE" constraintName="FK_GR7THLLB9LU8Q4VQA4524JJY8" deferrable="false" initiallyDeferred="false" onDelete="RESTRICT" onUpdate="RESTRICT" referencedColumnNames="ID" referencedTableName="KEYCLOAK_ROLE"/>
|
||||
<addForeignKeyConstraint baseColumnNames="ROLE_ID" baseTableName="REALM_DEFAULT_ROLES" constraintName="FK_H4WPD7W4HSOOLNI3H0SW7BTJE" deferrable="false" initiallyDeferred="false" onDelete="RESTRICT" onUpdate="RESTRICT" referencedColumnNames="ID" referencedTableName="KEYCLOAK_ROLE"/>
|
||||
<addForeignKeyConstraint baseColumnNames="REALM_ID" baseTableName="REALM_EVENTS_LISTENERS" constraintName="FK_H846O4H0W8EPX5NXEV9F5Y69J" deferrable="false" initiallyDeferred="false" onDelete="RESTRICT" onUpdate="RESTRICT" referencedColumnNames="ID" referencedTableName="REALM"/>
|
||||
<addForeignKeyConstraint baseColumnNames="REALM_ID" baseTableName="REALM_APPLICATION" constraintName="FK_L5QGA3RFME47335JY8JXYXH3I" deferrable="false" initiallyDeferred="false" onDelete="RESTRICT" onUpdate="RESTRICT" referencedColumnNames="ID" referencedTableName="CLIENT"/>
|
||||
<addForeignKeyConstraint baseColumnNames="CLIENT_ID" baseTableName="WEB_ORIGINS" constraintName="FK_LOJPHO213XCX4WNKOG82SSRFY" deferrable="false" initiallyDeferred="false" onDelete="RESTRICT" onUpdate="RESTRICT" referencedColumnNames="ID" referencedTableName="CLIENT"/>
|
||||
<addForeignKeyConstraint baseColumnNames="APPLICATION_ID" baseTableName="APPLICATION_DEFAULT_ROLES" constraintName="FK_MAYLTS7KLWQW2H8M2B5JOYTKY" deferrable="false" initiallyDeferred="false" onDelete="RESTRICT" onUpdate="RESTRICT" referencedColumnNames="ID" referencedTableName="CLIENT"/>
|
||||
<addForeignKeyConstraint baseColumnNames="CLIENT_ID" baseTableName="SCOPE_MAPPING" constraintName="FK_OUSE064PLMLR732LXJCN1Q5F1" deferrable="false" initiallyDeferred="false" onDelete="RESTRICT" onUpdate="RESTRICT" referencedColumnNames="ID" referencedTableName="CLIENT"/>
|
||||
<addForeignKeyConstraint baseColumnNames="ROLE_ID" baseTableName="SCOPE_MAPPING" constraintName="FK_P3RH9GRKU11KQFRS4FLTT7RNQ" deferrable="false" initiallyDeferred="false" onDelete="RESTRICT" onUpdate="RESTRICT" referencedColumnNames="ID" referencedTableName="KEYCLOAK_ROLE"/>
|
||||
<addForeignKeyConstraint baseColumnNames="REALM_ID" baseTableName="CLIENT" constraintName="FK_P56CTINXXB9GSK57FO49F9TAC" deferrable="false" initiallyDeferred="false" onDelete="RESTRICT" onUpdate="RESTRICT" referencedColumnNames="ID" referencedTableName="REALM"/>
|
||||
<addForeignKeyConstraint baseColumnNames="USER_ID" baseTableName="CREDENTIAL" constraintName="FK_PFYR0GLASQYL0DEI3KL69R6V0" deferrable="false" initiallyDeferred="false" onDelete="RESTRICT" onUpdate="RESTRICT" referencedColumnNames="ID" referencedTableName="USER_ENTITY"/>
|
||||
<addForeignKeyConstraint baseColumnNames="APPLICATION" baseTableName="KEYCLOAK_ROLE" constraintName="FK_PIMO5LE2C0RAL09FL8CM9WFW9" deferrable="false" initiallyDeferred="false" onDelete="RESTRICT" onUpdate="RESTRICT" referencedColumnNames="ID" referencedTableName="CLIENT"/>
|
||||
<addForeignKeyConstraint baseColumnNames="MASTER_ADMIN_APP" baseTableName="REALM" constraintName="FK_RSAF444KK6QRKMS7N56AIWQ5Y" deferrable="false" initiallyDeferred="false" onDelete="RESTRICT" onUpdate="RESTRICT" referencedColumnNames="ID" referencedTableName="CLIENT"/>
|
||||
<addForeignKeyConstraint baseColumnNames="REALM_ID" baseTableName="REALM_SOCIAL_CONFIG" constraintName="FK_SV5I3C2TI7G0G922FGE683SOV" deferrable="false" initiallyDeferred="false" onDelete="RESTRICT" onUpdate="RESTRICT" referencedColumnNames="ID" referencedTableName="REALM"/>
|
||||
<addForeignKeyConstraint baseColumnNames="USER_FEDERATION_PROVIDER_ID" baseTableName="USER_FEDERATION_CONFIG" constraintName="FK_T13HPU1J94R2EBPEKR39X5EU5" deferrable="false" initiallyDeferred="false" onDelete="RESTRICT" onUpdate="RESTRICT" referencedColumnNames="ID" referencedTableName="USER_FEDERATION_PROVIDER"/>
|
||||
</changeSet>
|
||||
</databaseChangeLog>
|
|
@ -0,0 +1,40 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.2.xsd">
|
||||
<changeSet author="sthorger@redhat.com" id="1.1.0.Beta1">
|
||||
<delete tableName="CLIENT_SESSION_ROLE"/>
|
||||
<delete tableName="CLIENT_SESSION"/>
|
||||
<delete tableName="USER_SESSION"/>
|
||||
|
||||
<createTable tableName="CLIENT_ATTRIBUTES">
|
||||
<column name="CLIENT_ID" type="VARCHAR(36)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="VALUE" type="VARCHAR(2048)"/>
|
||||
<column name="NAME" type="VARCHAR(255)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
</createTable>
|
||||
<createTable tableName="CLIENT_SESSION_NOTE">
|
||||
<column name="NAME" type="VARCHAR(255)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="VALUE" type="VARCHAR(255)"/>
|
||||
<column name="CLIENT_SESSION" type="VARCHAR(36)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
</createTable>
|
||||
<addColumn tableName="CLIENT_SESSION">
|
||||
<column name="AUTH_METHOD" type="VARCHAR(255)"/>
|
||||
</addColumn>
|
||||
<addColumn tableName="CLIENT">
|
||||
<column name="PROTOCOL" type="VARCHAR(255)"/>
|
||||
</addColumn>
|
||||
<addColumn tableName="CLIENT_SESSION">
|
||||
<column name="REALM_ID" type="VARCHAR(255)"/>
|
||||
</addColumn>
|
||||
<addPrimaryKey columnNames="CLIENT_ID, NAME" constraintName="CONSTRAINT_3C" tableName="CLIENT_ATTRIBUTES"/>
|
||||
<addPrimaryKey columnNames="CLIENT_SESSION, NAME" constraintName="CONSTRAINT_5E" tableName="CLIENT_SESSION_NOTE"/>
|
||||
<addForeignKeyConstraint baseColumnNames="CLIENT_ID" baseTableName="CLIENT_ATTRIBUTES" constraintName="FK3C47C64BEACCA966" deferrable="false" initiallyDeferred="false" onDelete="RESTRICT" onUpdate="RESTRICT" referencedColumnNames="ID" referencedTableName="CLIENT"/>
|
||||
<addForeignKeyConstraint baseColumnNames="CLIENT_SESSION" baseTableName="CLIENT_SESSION_NOTE" constraintName="FK5EDFB00FF51C2736" deferrable="false" initiallyDeferred="false" onDelete="RESTRICT" onUpdate="RESTRICT" referencedColumnNames="ID" referencedTableName="CLIENT_SESSION"/>
|
||||
</changeSet>
|
||||
</databaseChangeLog>
|
|
@ -0,0 +1,5 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.2.xsd">
|
||||
<include file="META-INF/jpa-changelog-1.0.0.Final.xml"/>
|
||||
<include file="META-INF/jpa-changelog-1.1.0.Beta1.xml"/>
|
||||
</databaseChangeLog>
|
|
@ -0,0 +1 @@
|
|||
org.keycloak.connections.jpa.updater.liquibase.LiquibaseJpaUpdaterProviderFactory
|
|
@ -1,12 +1,20 @@
|
|||
package org.keycloak.connections.jpa;
|
||||
|
||||
import org.hibernate.ejb.AvailableSettings;
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.Config;
|
||||
import org.keycloak.connections.jpa.updater.JpaUpdaterProvider;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
|
||||
import javax.naming.InitialContext;
|
||||
import javax.persistence.EntityManager;
|
||||
import javax.persistence.EntityManagerFactory;
|
||||
import javax.persistence.Persistence;
|
||||
import javax.sql.DataSource;
|
||||
import java.sql.Connection;
|
||||
import java.sql.DriverManager;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
|
@ -15,13 +23,15 @@ import java.util.Map;
|
|||
*/
|
||||
public class DefaultJpaConnectionProviderFactory implements JpaConnectionProviderFactory {
|
||||
|
||||
private static final Logger logger = Logger.getLogger(DefaultJpaConnectionProviderFactory.class);
|
||||
|
||||
private volatile EntityManagerFactory emf;
|
||||
|
||||
private Config.Scope config;
|
||||
|
||||
@Override
|
||||
public JpaConnectionProvider create(KeycloakSession session) {
|
||||
lazyInit();
|
||||
lazyInit(session);
|
||||
|
||||
EntityManager em = emf.createEntityManager();
|
||||
em = PersistenceExceptionConverter.create(em);
|
||||
|
@ -46,11 +56,17 @@ public class DefaultJpaConnectionProviderFactory implements JpaConnectionProvide
|
|||
this.config = config;
|
||||
}
|
||||
|
||||
private void lazyInit() {
|
||||
private void lazyInit(KeycloakSession session) {
|
||||
if (emf == null) {
|
||||
synchronized (this) {
|
||||
if (emf == null) {
|
||||
logger.debug("Initializing JPA connections");
|
||||
|
||||
Connection connection = null;
|
||||
|
||||
String unitName = config.get("unitName");
|
||||
String databaseSchema = config.get("databaseSchema");
|
||||
|
||||
Map<String, Object> properties = new HashMap<String, Object>();
|
||||
|
||||
// Only load config from keycloak-server.json if unitName is not specified
|
||||
|
@ -83,19 +99,80 @@ public class DefaultJpaConnectionProviderFactory implements JpaConnectionProvide
|
|||
properties.put("hibernate.dialect", driverDialect);
|
||||
}
|
||||
|
||||
String databaseSchema = config.get("databaseSchema", "validate");
|
||||
if (databaseSchema != null) {
|
||||
properties.put("hibernate.hbm2ddl.auto", databaseSchema);
|
||||
if (databaseSchema.equals("development-update")) {
|
||||
properties.put("hibernate.hbm2ddl.auto", "update");
|
||||
databaseSchema = null;
|
||||
} else if (databaseSchema.equals("development-validate")) {
|
||||
properties.put("hibernate.hbm2ddl.auto", "validate");
|
||||
databaseSchema = null;
|
||||
}
|
||||
}
|
||||
|
||||
properties.put("hibernate.show_sql", config.getBoolean("showSql", false));
|
||||
properties.put("hibernate.format_sql", config.getBoolean("formatSql", true));
|
||||
}
|
||||
|
||||
if (databaseSchema != null) {
|
||||
logger.trace("Updating database");
|
||||
|
||||
JpaUpdaterProvider updater = session.getProvider(JpaUpdaterProvider.class);
|
||||
connection = getConnection();
|
||||
|
||||
if (databaseSchema.equals("update")) {
|
||||
String currentVersion = null;
|
||||
try {
|
||||
ResultSet resultSet = connection.createStatement().executeQuery(updater.getCurrentVersionSql());
|
||||
if (resultSet.next()) {
|
||||
currentVersion = resultSet.getString(1);
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
}
|
||||
|
||||
if (currentVersion == null || !JpaUpdaterProvider.LAST_VERSION.equals(currentVersion)) {
|
||||
updater.update(connection);
|
||||
} else {
|
||||
logger.debug("Database is up to date");
|
||||
}
|
||||
} else if (databaseSchema.equals("validate")) {
|
||||
updater.validate(connection);
|
||||
} else {
|
||||
throw new RuntimeException("Invalid value for databaseSchema: " + databaseSchema);
|
||||
}
|
||||
|
||||
logger.trace("Database update completed");
|
||||
}
|
||||
|
||||
logger.trace("Creating EntityManagerFactory");
|
||||
emf = Persistence.createEntityManagerFactory(unitName, properties);
|
||||
logger.trace("EntityManagerFactory created");
|
||||
|
||||
// Close after creating EntityManagerFactory to prevent in-mem databases from closing
|
||||
if (connection != null) {
|
||||
try {
|
||||
connection.close();
|
||||
} catch (SQLException e) {
|
||||
logger.warn(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Connection getConnection() {
|
||||
try {
|
||||
String dataSourceLookup = config.get("dataSource");
|
||||
if (dataSourceLookup != null) {
|
||||
DataSource dataSource = (DataSource) new InitialContext().lookup(dataSourceLookup);
|
||||
return dataSource.getConnection();
|
||||
} else {
|
||||
Class.forName(config.get("driver"));
|
||||
return DriverManager.getConnection(config.get("url"), config.get("user"), config.get("password"));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Failed to connect to database", e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
package org.keycloak.connections.jpa.updater;
|
||||
|
||||
import org.keycloak.provider.Provider;
|
||||
|
||||
import java.sql.Connection;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public interface JpaUpdaterProvider extends Provider {
|
||||
|
||||
public String FIRST_VERSION = "1.0.0.Final";
|
||||
|
||||
public String LAST_VERSION = "1.1.0.Beta1";
|
||||
|
||||
public String getCurrentVersionSql();
|
||||
|
||||
public void update(Connection connection);
|
||||
|
||||
public void validate(Connection connection);
|
||||
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
package org.keycloak.connections.jpa.updater;
|
||||
|
||||
import org.keycloak.provider.ProviderFactory;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public interface JpaUpdaterProviderFactory extends ProviderFactory<JpaUpdaterProvider> {
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
package org.keycloak.connections.jpa.updater;
|
||||
|
||||
import org.keycloak.provider.Provider;
|
||||
import org.keycloak.provider.ProviderFactory;
|
||||
import org.keycloak.provider.Spi;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public class JpaUpdaterSpi implements Spi {
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "connectionsJpaUpdater";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<? extends Provider> getProviderClass() {
|
||||
return JpaUpdaterProvider.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<? extends ProviderFactory> getProviderFactoryClass() {
|
||||
return JpaUpdaterProviderFactory.class;
|
||||
}
|
||||
|
||||
}
|
|
@ -1 +1,2 @@
|
|||
org.keycloak.connections.jpa.JpaConnectionSpi
|
||||
org.keycloak.connections.jpa.JpaConnectionSpi
|
||||
org.keycloak.connections.jpa.updater.JpaUpdaterSpi
|
|
@ -9,6 +9,7 @@ import org.keycloak.Config;
|
|||
import org.keycloak.connections.mongo.api.MongoStore;
|
||||
import org.keycloak.connections.mongo.impl.MongoStoreImpl;
|
||||
import org.keycloak.connections.mongo.impl.context.TransactionMongoStoreInvocationContext;
|
||||
import org.keycloak.connections.mongo.updater.DefaultMongoUpdaterProvider;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
|
||||
import java.util.Collections;
|
||||
|
@ -64,7 +65,6 @@ public class DefaultMongoConnectionFactoryProvider implements MongoConnectionPro
|
|||
String host = config.get("host", ServerAddress.defaultHost());
|
||||
int port = config.getInt("port", ServerAddress.defaultPort());
|
||||
String dbName = config.get("db", "keycloak");
|
||||
boolean clearOnStartup = config.getBoolean("clearOnStartup", false);
|
||||
|
||||
String user = config.get("user");
|
||||
String password = config.get("password");
|
||||
|
@ -77,9 +77,18 @@ public class DefaultMongoConnectionFactoryProvider implements MongoConnectionPro
|
|||
|
||||
this.db = client.getDB(dbName);
|
||||
|
||||
this.mongoStore = new MongoStoreImpl(db, clearOnStartup, getManagedEntities());
|
||||
String databaseSchema = config.get("databaseSchema");
|
||||
if (databaseSchema != null) {
|
||||
if (databaseSchema.equals("update")) {
|
||||
new DefaultMongoUpdaterProvider().update(db);
|
||||
} else {
|
||||
throw new RuntimeException("Invalid value for databaseSchema: " + databaseSchema);
|
||||
}
|
||||
}
|
||||
|
||||
logger.infof("Initialized mongo model. host: %s, port: %d, db: %s, clearOnStartup: %b", host, port, dbName, clearOnStartup);
|
||||
this.mongoStore = new MongoStoreImpl(db, getManagedEntities());
|
||||
|
||||
logger.debugv("Initialized mongo model. host: %s, port: %d, db: %s", host, port, dbName);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
|
|
@ -1,26 +0,0 @@
|
|||
package org.keycloak.connections.mongo.api;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.Inherited;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import static java.lang.annotation.ElementType.TYPE;
|
||||
import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
@Target({TYPE})
|
||||
@Documented
|
||||
@Retention(RUNTIME)
|
||||
@Inherited
|
||||
public @interface MongoIndex {
|
||||
|
||||
String[] fields();
|
||||
|
||||
boolean unique() default false;
|
||||
|
||||
boolean sparse() default false;
|
||||
|
||||
}
|
|
@ -1,22 +0,0 @@
|
|||
package org.keycloak.connections.mongo.api;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.Inherited;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import static java.lang.annotation.ElementType.TYPE;
|
||||
import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
@Target({TYPE})
|
||||
@Documented
|
||||
@Retention(RUNTIME)
|
||||
@Inherited
|
||||
public @interface MongoIndexes {
|
||||
|
||||
MongoIndex[] value();
|
||||
|
||||
}
|
|
@ -11,8 +11,6 @@ import org.jboss.logging.Logger;
|
|||
import org.keycloak.connections.mongo.api.MongoCollection;
|
||||
import org.keycloak.connections.mongo.api.MongoEntity;
|
||||
import org.keycloak.connections.mongo.api.MongoIdentifiableEntity;
|
||||
import org.keycloak.connections.mongo.api.MongoIndex;
|
||||
import org.keycloak.connections.mongo.api.MongoIndexes;
|
||||
import org.keycloak.connections.mongo.api.MongoStore;
|
||||
import org.keycloak.connections.mongo.api.context.MongoStoreInvocationContext;
|
||||
import org.keycloak.connections.mongo.api.context.MongoTask;
|
||||
|
@ -57,7 +55,7 @@ public class MongoStoreImpl implements MongoStore {
|
|||
new ConcurrentHashMap<Class<?>, EntityInfo>();
|
||||
|
||||
|
||||
public MongoStoreImpl(DB database, boolean clearCollectionsOnStartup, Class<?>[] managedEntityTypes) {
|
||||
public MongoStoreImpl(DB database, Class<?>[] managedEntityTypes) {
|
||||
this.database = database;
|
||||
|
||||
mapperRegistry = new MapperRegistry();
|
||||
|
@ -86,13 +84,6 @@ public class MongoStoreImpl implements MongoStore {
|
|||
mapperRegistry.addAppObjectMapper(new MongoEntityMapper(this, mapperRegistry, type));
|
||||
mapperRegistry.addDBObjectMapper(new BasicDBObjectMapper(this, mapperRegistry, type));
|
||||
}
|
||||
|
||||
if (clearCollectionsOnStartup) {
|
||||
// dropDatabase();
|
||||
clearManagedCollections(managedEntityTypes);
|
||||
}
|
||||
|
||||
initManagedCollections(managedEntityTypes);
|
||||
}
|
||||
|
||||
protected void dropDatabase() {
|
||||
|
@ -100,63 +91,6 @@ public class MongoStoreImpl implements MongoStore {
|
|||
logger.info("Database " + this.database.getName() + " dropped in MongoDB");
|
||||
}
|
||||
|
||||
// Don't drop database, but just clear all data in managed collections (useful for export/import or during development)
|
||||
protected void clearManagedCollections(Class<?>[] managedEntityTypes) {
|
||||
for (Class<?> clazz : managedEntityTypes) {
|
||||
DBCollection dbCollection = getDBCollectionForType(clazz);
|
||||
if (dbCollection != null) {
|
||||
dbCollection.remove(new BasicDBObject());
|
||||
logger.debug("Collection " + dbCollection.getName() + " cleared from " + this.database.getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void initManagedCollections(Class<?>[] managedEntityTypes) {
|
||||
for (Class<?> clazz : managedEntityTypes) {
|
||||
EntityInfo entityInfo = getEntityInfo(clazz);
|
||||
String dbCollectionName = entityInfo.getDbCollectionName();
|
||||
if (dbCollectionName != null && !database.collectionExists(dbCollectionName)) {
|
||||
DBCollection dbCollection = database.getCollection(dbCollectionName);
|
||||
|
||||
logger.debug("Created collection " + dbCollection.getName() + " in " + this.database.getName());
|
||||
|
||||
MongoIndex index = clazz.getAnnotation(MongoIndex.class);
|
||||
if (index != null) {
|
||||
createIndex(dbCollection, index);
|
||||
}
|
||||
|
||||
MongoIndexes indexes = clazz.getAnnotation(MongoIndexes.class);
|
||||
if (indexes != null) {
|
||||
for (MongoIndex i : indexes.value()) {
|
||||
createIndex(dbCollection, i);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void createIndex(DBCollection dbCollection, MongoIndex index) {
|
||||
BasicDBObject fields = new BasicDBObject();
|
||||
for (String f : index.fields()) {
|
||||
fields.put(f, 1);
|
||||
}
|
||||
|
||||
boolean unique = index.unique();
|
||||
boolean sparse = index.sparse();
|
||||
|
||||
BasicDBObject options = new BasicDBObject();
|
||||
if (unique) {
|
||||
options.put("unique", unique);
|
||||
}
|
||||
if (sparse) {
|
||||
options.put("sparse", sparse);
|
||||
}
|
||||
|
||||
dbCollection.ensureIndex(fields, options);
|
||||
|
||||
logger.debug("Created index " + fields + "(options: " + options + ") on " + dbCollection.getName() + " in " + this.database.getName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void insertEntity(MongoIdentifiableEntity entity, MongoStoreInvocationContext context) {
|
||||
Class<? extends MongoEntity> clazz = entity.getClass();
|
||||
|
|
|
@ -0,0 +1,103 @@
|
|||
package org.keycloak.connections.mongo.updater;
|
||||
|
||||
import com.mongodb.BasicDBObject;
|
||||
import com.mongodb.DB;
|
||||
import com.mongodb.DBCollection;
|
||||
import com.mongodb.DBCursor;
|
||||
import com.mongodb.DBObject;
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.connections.mongo.updater.updates.Update;
|
||||
import org.keycloak.connections.mongo.updater.updates.Update1_0_0_Final;
|
||||
import org.keycloak.connections.mongo.updater.updates.Update1_1_0_Beta1;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public class DefaultMongoUpdaterProvider implements MongoUpdaterProvider {
|
||||
|
||||
public static final Logger log = Logger.getLogger(DefaultMongoUpdaterProvider.class);
|
||||
|
||||
public static final String CHANGE_LOG_COLLECTION = "databaseChangeLog";
|
||||
|
||||
private Class<? extends Update>[] updates = new Class[]{
|
||||
Update1_0_0_Final.class,
|
||||
Update1_1_0_Beta1.class
|
||||
};
|
||||
|
||||
@Override
|
||||
public void update(DB db) {
|
||||
log.debug("Starting database update");
|
||||
try {
|
||||
boolean changeLogExists = db.collectionExists(CHANGE_LOG_COLLECTION);
|
||||
boolean realmExists = db.collectionExists("realms");
|
||||
|
||||
DBCollection changeLog = db.getCollection(CHANGE_LOG_COLLECTION);
|
||||
|
||||
List<String> executed = new LinkedList<String>();
|
||||
if (!changeLogExists && realmExists) {
|
||||
Update1_0_0_Final u = new Update1_0_0_Final();
|
||||
executed.add(u.getId());
|
||||
createLog(changeLog, u, 1);
|
||||
} else if (changeLogExists) {
|
||||
DBCursor cursor = changeLog.find().sort(new BasicDBObject("orderExecuted", 1));
|
||||
while (cursor.hasNext()) {
|
||||
executed.add((String) cursor.next().get("_id"));
|
||||
}
|
||||
}
|
||||
|
||||
List<Update> updatesToRun = new LinkedList<Update>();
|
||||
for (Class<? extends Update> updateClass : updates) {
|
||||
Update u = updateClass.newInstance();
|
||||
if (!executed.contains(u.getId())) {
|
||||
updatesToRun.add(u);
|
||||
}
|
||||
}
|
||||
|
||||
if (!updatesToRun.isEmpty()) {
|
||||
if (executed.isEmpty()) {
|
||||
log.info("Initializing database schema");
|
||||
} else {
|
||||
if (log.isDebugEnabled()) {
|
||||
log.infov("Updating database from {0} to {1}", executed.get(executed.size() - 1), updatesToRun.get(updatesToRun.size() - 1).getId());
|
||||
} else {
|
||||
log.debugv("Updating database");
|
||||
}
|
||||
}
|
||||
|
||||
int order = executed.size();
|
||||
for (Update u : updatesToRun) {
|
||||
log.debugv("Executing updates for {0}", u.getId());
|
||||
|
||||
u.setLog(log);
|
||||
u.setDb(db);
|
||||
u.update();
|
||||
|
||||
createLog(changeLog, u, ++order);
|
||||
|
||||
log.debugv("Completed updates for {0}", u.getId());
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Failed to update database", e);
|
||||
}
|
||||
log.debug("Completed database update");
|
||||
}
|
||||
|
||||
private void createLog(DBCollection changeLog, Update update, int orderExecuted) {
|
||||
changeLog.insert(new BasicDBObject("_id", update.getId()).append("dateExecuted", new Date()).append("orderExecuted", orderExecuted));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
package org.keycloak.connections.mongo.updater;
|
||||
|
||||
import org.keycloak.Config;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public class DefaultMongoUpdaterProviderFactory implements MongoUpdaterProviderFactory {
|
||||
|
||||
@Override
|
||||
public MongoUpdaterProvider create(KeycloakSession session) {
|
||||
return new DefaultMongoUpdaterProvider();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(Config.Scope config) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return "default";
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
package org.keycloak.connections.mongo.updater;
|
||||
|
||||
import com.mongodb.DB;
|
||||
import org.keycloak.provider.Provider;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public interface MongoUpdaterProvider extends Provider {
|
||||
|
||||
public void update(DB db);
|
||||
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
package org.keycloak.connections.mongo.updater;
|
||||
|
||||
import org.keycloak.provider.ProviderFactory;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public interface MongoUpdaterProviderFactory extends ProviderFactory<MongoUpdaterProvider> {
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
package org.keycloak.connections.mongo.updater;
|
||||
|
||||
import org.keycloak.provider.Provider;
|
||||
import org.keycloak.provider.ProviderFactory;
|
||||
import org.keycloak.provider.Spi;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public class MongoUpdaterSpi implements Spi {
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "connectionsMongoUpdater";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<? extends Provider> getProviderClass() {
|
||||
return MongoUpdaterProvider.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<? extends ProviderFactory> getProviderFactoryClass() {
|
||||
return MongoUpdaterProviderFactory.class;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,62 @@
|
|||
package org.keycloak.connections.mongo.updater.updates;
|
||||
|
||||
import com.mongodb.BasicDBObject;
|
||||
import com.mongodb.DB;
|
||||
import com.mongodb.DBCollection;
|
||||
import org.jboss.logging.Logger;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public abstract class Update {
|
||||
|
||||
protected DB db;
|
||||
|
||||
protected Logger log;
|
||||
|
||||
public abstract String getId();
|
||||
|
||||
public abstract void update() throws ClassNotFoundException;
|
||||
|
||||
protected DBCollection createCollection(String name) {
|
||||
if (db.collectionExists(name)) {
|
||||
throw new RuntimeException("Failed to create collection {0}: collection already exists");
|
||||
}
|
||||
|
||||
DBCollection col = db.getCollection(name);
|
||||
log.debugv("Created collection {0}", name);
|
||||
return col;
|
||||
}
|
||||
|
||||
protected void ensureIndex(String name, String field, boolean unique, boolean sparse) {
|
||||
ensureIndex(name, new String[]{field}, unique, sparse);
|
||||
}
|
||||
|
||||
protected void ensureIndex(String name, String[] fields, boolean unique, boolean sparse) {
|
||||
DBCollection col = db.getCollection(name);
|
||||
|
||||
BasicDBObject o = new BasicDBObject();
|
||||
for (String f : fields) {
|
||||
o.append(f, 1);
|
||||
}
|
||||
|
||||
col.ensureIndex(o, new BasicDBObject("unique", unique).append("sparse", sparse));
|
||||
log.debugv("Created index {0}, fields={1}, unique={2}, sparse={3}", name, Arrays.toString(fields), unique, sparse);
|
||||
}
|
||||
|
||||
protected void deleteEntries(String collection) {
|
||||
db.getCollection(collection).remove(new BasicDBObject());
|
||||
log.debugv("Deleted entries from {0}", collection);
|
||||
}
|
||||
|
||||
public void setLog(Logger log) {
|
||||
this.log = log;
|
||||
}
|
||||
|
||||
public void setDb(DB db) {
|
||||
this.db = db;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
package org.keycloak.connections.mongo.updater.updates;
|
||||
|
||||
import com.mongodb.BasicDBObject;
|
||||
import com.mongodb.DBCollection;
|
||||
import org.keycloak.connections.mongo.updater.DefaultMongoUpdaterProvider;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public class Update1_0_0_Final extends Update {
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return "1.0.0.Final";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update() throws ClassNotFoundException {
|
||||
DBCollection realmsCollection = db.getCollection("realms");
|
||||
realmsCollection.ensureIndex(new BasicDBObject("name", 1), new BasicDBObject("unique", true));
|
||||
|
||||
DefaultMongoUpdaterProvider.log.debugv("Created collection {0}", "realms");
|
||||
|
||||
createCollection("users");
|
||||
ensureIndex("users", new String[] { "realmId", "username"}, true, false);
|
||||
ensureIndex("users", "emailIndex", true, true);
|
||||
|
||||
createCollection("roles");
|
||||
ensureIndex("roles", "nameIndex", true, false);
|
||||
|
||||
createCollection("applications");
|
||||
ensureIndex("applications", new String[]{"realmId", "name"}, true, false);
|
||||
|
||||
createCollection("oauthClients");
|
||||
ensureIndex("oauthClients", new String[] { "realmId", "name"}, true, false);
|
||||
|
||||
createCollection("userFailures");
|
||||
|
||||
createCollection("sessions");
|
||||
|
||||
createCollection("clientSessions");
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
package org.keycloak.connections.mongo.updater.updates;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public class Update1_1_0_Beta1 extends Update {
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return "1.1.0.Beta1";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update() {
|
||||
deleteEntries("clientSessions");
|
||||
deleteEntries("sessions");
|
||||
}
|
||||
|
||||
}
|
|
@ -14,6 +14,7 @@
|
|||
|
||||
<modules>
|
||||
<module>jpa</module>
|
||||
<module>jpa-liquibase</module>
|
||||
<module>infinispan</module>
|
||||
<module>mongo</module>
|
||||
</modules>
|
||||
|
|
16
dependencies/server-all/pom.xml
vendored
16
dependencies/server-all/pom.xml
vendored
|
@ -26,6 +26,11 @@
|
|||
<artifactId>keycloak-connections-jpa</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-connections-jpa-liquibase</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-connections-infinispan</artifactId>
|
||||
|
@ -180,6 +185,17 @@
|
|||
<groupId>de.idyl</groupId>
|
||||
<artifactId>winzipaes</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.liquibase</groupId>
|
||||
<artifactId>liquibase-core</artifactId>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.yaml</groupId>
|
||||
<artifactId>snakeyaml</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
79
misc/UpdatingDatabaseSchema.md
Normal file
79
misc/UpdatingDatabaseSchema.md
Normal file
|
@ -0,0 +1,79 @@
|
|||
Updating Database Schema
|
||||
========================
|
||||
|
||||
Keycloak supports automatically migrating the database to a new version. This is done by applying one or more change-sets
|
||||
to the existing database. This means if you need to do any changes to database schemas for JPA or Mongo you need to create
|
||||
a change-set that can transform the schema as well as any existing data.
|
||||
|
||||
This includes changes to:
|
||||
|
||||
* Realm entities
|
||||
* User entities
|
||||
* User session entities
|
||||
* Event entities
|
||||
|
||||
|
||||
Creating a JPA change-set
|
||||
-------------------------
|
||||
|
||||
We use Liquibase to support updating the database. The change-sets are located in
|
||||
`connections/jpa-liquibase/src/main/resources/META-INF`. There's a separate file for each release that requires database
|
||||
changes.
|
||||
|
||||
To manually create a change-set add a new file in the above location with the name `jpa-changelog-<version>.xml`. This file
|
||||
should contain a single `change-set` with `id` equal to the next version to be released and `author` set to your email
|
||||
address. Then look at Liquibase documentation on how to write this file. Add a reference to this file in `jpa-changelog-master.xml`.
|
||||
The file should have a single change-set and the id of the change-set should be the next version to be released.
|
||||
|
||||
You also need to update `org.keycloak.connections.jpa.updater.JpaUpdaterProvider#LAST_VERSION`. This
|
||||
is used by Keycloak to quickly determine if the database is up to date or not.
|
||||
|
||||
You can also have Liquibase and Hibernate create one for you. To do this follow these steps:
|
||||
|
||||
1. Delete existing databases
|
||||
`rm keycloak*h2.db`
|
||||
2. Create a database of the old format:
|
||||
`mvn -f connections/jpa-liquibase/pom.xml liquibase:update -Durl=jdbc:h2:keycloak`
|
||||
3. Make a copy of the database:
|
||||
`cp keycloak.h2.db keycloak-old.h2.db`
|
||||
3. Run KeycloakServer to make Hibernate update the schema:
|
||||
`mvn -f testsuite/integration exec:java -Pkeycloak-server -Dkeycloak.connectionsJpa.url='jdbc:h2:keycloak' -Dkeycloak.connectionsJpa.databaseSchema='development-update'`
|
||||
4. Wait until server is completely started, then stop it
|
||||
5. View the difference:
|
||||
`mvn -f connections/jpa-liquibase/pom.xml liquibase:diff -Durl=jdbc:h2:keycloak-old -DreferenceUrl=jdbc:h2:keycloak`
|
||||
6. Create a change-set file:
|
||||
`mvn -f connections/jpa-liquibase/pom.xml liquibase:diff -Durl=jdbc:h2:keycloak-old -DreferenceUrl=jdbc:h2:keycloak -Dliquibase.diffChangeLogFile=changelog.xml`
|
||||
|
||||
This will generate the file `changelog.xml`. Once it's generated edit the file and combine all `change-sets` into
|
||||
a single `change-set` and change the `id` to the next version to be released and `author` to your email address. Then
|
||||
follow the steps above to copy it to the correct location and update `jpa-changelog-master.xml`. You have to manually
|
||||
add entries to the `change-set` to update existing data if required.
|
||||
|
||||
When you have update the change-set Hibernate can validate the schema for you. First run:
|
||||
|
||||
rm -rf keycloak*h2.db
|
||||
mvn -f testsuite/integration exec:java -Pkeycloak-server -Dkeycloak.connectionsJpa.url='jdbc:h2:keycloak' -Dkeycloak.connectionsJpa.databaseSchema='update'
|
||||
|
||||
Once the server has started fully, stop it and run:
|
||||
|
||||
mvn -f testsuite/integration exec:java -Pkeycloak-server -Dkeycloak.connectionsJpa.url='jdbc:h2:keycloak' -Dkeycloak.connectionsJpa.databaseSchema='development-validate'
|
||||
|
||||
|
||||
Creating a Mongo change-set
|
||||
---------------------------
|
||||
|
||||
As Mongo is schema-less it's significantly easier to create a change-set. You only need to create/delete collections as
|
||||
needed, as well as update any indexes. You will also need to update existing data if required.
|
||||
|
||||
Mongo change-sets are written in Java and are located in the `connections/mongo` module, to add a new change-set create
|
||||
a new class that implements `org.keycloak.connections.mongo.updater.updates.Update` the name of the class should be
|
||||
`Update<version>` with `.` replaced with `_`.
|
||||
|
||||
You also need to add a reference to this file in `org.keycloak.connections.mongo.updater.DefaultMongoUpdaterProvider`.
|
||||
It should be added last to the `DefaultMongoUpdaterProvider#updates` array.
|
||||
|
||||
|
||||
Testing database migration
|
||||
--------------------------
|
||||
|
||||
Get the database from an old version of Keycloak that includes the demo applications. Start the server with this and test it.
|
|
@ -4,7 +4,6 @@ import com.mongodb.DBObject;
|
|||
import com.mongodb.QueryBuilder;
|
||||
import org.keycloak.connections.mongo.api.MongoCollection;
|
||||
import org.keycloak.connections.mongo.api.MongoIdentifiableEntity;
|
||||
import org.keycloak.connections.mongo.api.MongoIndex;
|
||||
import org.keycloak.connections.mongo.api.context.MongoStoreInvocationContext;
|
||||
import org.keycloak.models.entities.ApplicationEntity;
|
||||
|
||||
|
@ -12,7 +11,6 @@ import org.keycloak.models.entities.ApplicationEntity;
|
|||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
@MongoCollection(collectionName = "applications")
|
||||
@MongoIndex(fields = { "realmId", "name" }, unique = true)
|
||||
public class MongoApplicationEntity extends ApplicationEntity implements MongoIdentifiableEntity {
|
||||
|
||||
@Override
|
||||
|
|
|
@ -4,7 +4,6 @@ import com.mongodb.DBObject;
|
|||
import com.mongodb.QueryBuilder;
|
||||
import org.keycloak.connections.mongo.api.MongoCollection;
|
||||
import org.keycloak.connections.mongo.api.MongoIdentifiableEntity;
|
||||
import org.keycloak.connections.mongo.api.MongoIndex;
|
||||
import org.keycloak.connections.mongo.api.context.MongoStoreInvocationContext;
|
||||
import org.keycloak.models.entities.OAuthClientEntity;
|
||||
|
||||
|
@ -12,7 +11,6 @@ import org.keycloak.models.entities.OAuthClientEntity;
|
|||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
@MongoCollection(collectionName = "oauthClients")
|
||||
@MongoIndex(fields = { "realmId", "name" }, unique = true)
|
||||
public class MongoOAuthClientEntity extends OAuthClientEntity implements MongoIdentifiableEntity {
|
||||
|
||||
@Override
|
||||
|
|
|
@ -4,7 +4,6 @@ import com.mongodb.DBObject;
|
|||
import com.mongodb.QueryBuilder;
|
||||
import org.keycloak.connections.mongo.api.MongoCollection;
|
||||
import org.keycloak.connections.mongo.api.MongoIdentifiableEntity;
|
||||
import org.keycloak.connections.mongo.api.MongoIndex;
|
||||
import org.keycloak.connections.mongo.api.context.MongoStoreInvocationContext;
|
||||
import org.keycloak.models.entities.RealmEntity;
|
||||
|
||||
|
@ -12,7 +11,6 @@ import org.keycloak.models.entities.RealmEntity;
|
|||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
@MongoCollection(collectionName = "realms")
|
||||
@MongoIndex(fields = { "name" }, unique = true)
|
||||
public class MongoRealmEntity extends RealmEntity implements MongoIdentifiableEntity {
|
||||
|
||||
@Override
|
||||
|
|
|
@ -6,7 +6,6 @@ import org.jboss.logging.Logger;
|
|||
import org.keycloak.connections.mongo.api.MongoCollection;
|
||||
import org.keycloak.connections.mongo.api.MongoField;
|
||||
import org.keycloak.connections.mongo.api.MongoIdentifiableEntity;
|
||||
import org.keycloak.connections.mongo.api.MongoIndex;
|
||||
import org.keycloak.connections.mongo.api.MongoStore;
|
||||
import org.keycloak.connections.mongo.api.context.MongoStoreInvocationContext;
|
||||
import org.keycloak.models.entities.RoleEntity;
|
||||
|
@ -17,7 +16,6 @@ import java.util.List;
|
|||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
@MongoCollection(collectionName = "roles")
|
||||
@MongoIndex(fields = "nameIndex", unique = true)
|
||||
public class MongoRoleEntity extends RoleEntity implements MongoIdentifiableEntity {
|
||||
|
||||
private static final Logger logger = Logger.getLogger(MongoRoleEntity.class);
|
||||
|
|
|
@ -2,8 +2,6 @@ package org.keycloak.models.mongo.keycloak.entities;
|
|||
|
||||
import org.keycloak.connections.mongo.api.MongoCollection;
|
||||
import org.keycloak.connections.mongo.api.MongoIdentifiableEntity;
|
||||
import org.keycloak.connections.mongo.api.MongoIndex;
|
||||
import org.keycloak.connections.mongo.api.MongoIndexes;
|
||||
import org.keycloak.connections.mongo.api.context.MongoStoreInvocationContext;
|
||||
import org.keycloak.models.entities.UserEntity;
|
||||
|
||||
|
@ -11,13 +9,8 @@ import org.keycloak.models.entities.UserEntity;
|
|||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
@MongoCollection(collectionName = "users")
|
||||
@MongoIndexes({
|
||||
@MongoIndex(fields = { "realmId", "username" }, unique = true),
|
||||
@MongoIndex(fields = { "emailIndex" }, unique = true, sparse = true),
|
||||
})
|
||||
public class MongoUserEntity extends UserEntity implements MongoIdentifiableEntity {
|
||||
|
||||
|
||||
public String getEmailIndex() {
|
||||
return getEmail() != null ? getRealmId() + "//" + getEmail() : null;
|
||||
}
|
||||
|
|
|
@ -253,7 +253,7 @@ public class MemUserSessionProvider implements UserSessionProvider {
|
|||
@Override
|
||||
public void onClientRemoved(RealmModel realm, ClientModel client) {
|
||||
for (ClientSessionEntity e : clientSessions.values()) {
|
||||
if (e.getSession().getRealm().equals(realm.getId()) && e.getClientId().equals(client.getId())) {
|
||||
if (e.getRealmId().equals(realm.getId()) && e.getClientId().equals(client.getId())) {
|
||||
clientSessions.remove(e.getId());
|
||||
e.getSession().removeClientSession(e);
|
||||
}
|
||||
|
|
6
pom.xml
6
pom.xml
|
@ -44,6 +44,7 @@
|
|||
<selenium.version>2.35.0</selenium.version>
|
||||
<javax.mail.version>1.4.5</javax.mail.version>
|
||||
<infinispan.version>6.0.2.Final</infinispan.version>
|
||||
<liquibase.version>3.2.2</liquibase.version>
|
||||
|
||||
<!-- maven-compiler-plugin -->
|
||||
<maven.compiler.target>1.6</maven.compiler.target>
|
||||
|
@ -449,6 +450,11 @@
|
|||
<artifactId>infinispan-core</artifactId>
|
||||
<version>${infinispan.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.liquibase</groupId>
|
||||
<artifactId>liquibase-core</artifactId>
|
||||
<version>${liquibase.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
|
||||
|
|
|
@ -67,7 +67,7 @@
|
|||
"driverDialect": "${keycloak.connectionsJpa.driverDialect:}",
|
||||
"user": "${keycloak.connectionsJpa.user:sa}",
|
||||
"password": "${keycloak.connectionsJpa.password:}",
|
||||
"databaseSchema": "${keycloak.connectionsJpa.databaseSchema:create-drop}",
|
||||
"databaseSchema": "${keycloak.connectionsJpa.databaseSchema:update}",
|
||||
"showSql": "${keycloak.connectionsJpa.showSql:false}",
|
||||
"formatSql": "${keycloak.connectionsJpa.formatSql:true}"
|
||||
}
|
||||
|
@ -78,7 +78,7 @@
|
|||
"host": "${keycloak.connectionsMongo.host:127.0.0.1}",
|
||||
"port": "${keycloak.connectionsMongo.port:27017}",
|
||||
"db": "${keycloak.connectionsMongo.db:keycloak}",
|
||||
"clearOnStartup": "${keycloak.connectionsMongo.clearOnStartup:false}"
|
||||
"databaseSchema": "${keycloak.connectionsMongo.databaseSchema:update}"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -6,6 +6,15 @@ log4j.appender.stdout.layout.ConversionPattern=%d{HH:mm:ss,SSS} %-5p [%c] %m%n
|
|||
|
||||
log4j.logger.org.keycloak=info
|
||||
|
||||
# Enable to view loaded SPI and Providers
|
||||
# log4j.logger.org.keycloak.services.DefaultKeycloakSessionFactory=debug
|
||||
|
||||
# Enable to view database updates
|
||||
# log4j.logger.org.keycloak.connections.jpa.updater.liquibase.LiquibaseJpaUpdaterProvider=debug
|
||||
# log4j.logger.org.keycloak.connections.mongo.updater.DefaultMongoUpdaterProvider=debug
|
||||
|
||||
# log4j.logger.org.keycloak.connections.jpa.DefaultJpaConnectionProviderFactory=debug
|
||||
|
||||
log4j.logger.org.xnio=off
|
||||
log4j.logger.org.hibernate=off
|
||||
log4j.logger.org.jboss.resteasy=warn
|
|
@ -157,15 +157,11 @@ public class AccountTest {
|
|||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore
|
||||
public void runit() throws Exception {
|
||||
Thread.sleep(10000000);
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
// @Test
|
||||
// @Ignore
|
||||
// public void runit() throws Exception {
|
||||
// Thread.sleep(10000000);
|
||||
// }
|
||||
|
||||
@Test
|
||||
public void returnToAppFromQueryParam() {
|
||||
|
|
|
@ -54,7 +54,7 @@ public class CompositeImportRoleTest {
|
|||
@Override
|
||||
protected void configure(KeycloakSession session, RealmManager manager, RealmModel adminRealm) {
|
||||
RealmRepresentation representation = KeycloakServer.loadJson(getClass().getResourceAsStream("/testcomposite.json"), RealmRepresentation.class);
|
||||
representation.setId("Test");
|
||||
representation.setId("test");
|
||||
RealmModel realm = manager.importRealm(representation);
|
||||
|
||||
realmPublicKey = realm.getPublicKey();
|
||||
|
@ -78,7 +78,7 @@ public class CompositeImportRoleTest {
|
|||
|
||||
@Test
|
||||
public void testAppCompositeUser() throws Exception {
|
||||
oauth.realm("Test");
|
||||
oauth.realm("test");
|
||||
oauth.realmPublicKey(realmPublicKey);
|
||||
oauth.clientId("APP_COMPOSITE_APPLICATION");
|
||||
oauth.doLogin("APP_COMPOSITE_USER", "password");
|
||||
|
@ -92,7 +92,7 @@ public class CompositeImportRoleTest {
|
|||
|
||||
AccessToken token = oauth.verifyToken(response.getAccessToken());
|
||||
|
||||
Assert.assertEquals(keycloakRule.getUser("Test", "APP_COMPOSITE_USER").getId(), token.getSubject());
|
||||
Assert.assertEquals(keycloakRule.getUser("test", "APP_COMPOSITE_USER").getId(), token.getSubject());
|
||||
|
||||
Assert.assertEquals(1, token.getResourceAccess("APP_ROLE_APPLICATION").getRoles().size());
|
||||
Assert.assertEquals(1, token.getRealmAccess().getRoles().size());
|
||||
|
@ -103,7 +103,7 @@ public class CompositeImportRoleTest {
|
|||
|
||||
@Test
|
||||
public void testRealmAppCompositeUser() throws Exception {
|
||||
oauth.realm("Test");
|
||||
oauth.realm("test");
|
||||
oauth.realmPublicKey(realmPublicKey);
|
||||
oauth.clientId("APP_ROLE_APPLICATION");
|
||||
oauth.doLogin("REALM_APP_COMPOSITE_USER", "password");
|
||||
|
@ -117,7 +117,7 @@ public class CompositeImportRoleTest {
|
|||
|
||||
AccessToken token = oauth.verifyToken(response.getAccessToken());
|
||||
|
||||
Assert.assertEquals(keycloakRule.getUser("Test", "REALM_APP_COMPOSITE_USER").getId(), token.getSubject());
|
||||
Assert.assertEquals(keycloakRule.getUser("test", "REALM_APP_COMPOSITE_USER").getId(), token.getSubject());
|
||||
|
||||
Assert.assertEquals(1, token.getResourceAccess("APP_ROLE_APPLICATION").getRoles().size());
|
||||
Assert.assertTrue(token.getResourceAccess("APP_ROLE_APPLICATION").isUserInRole("APP_ROLE_1"));
|
||||
|
@ -127,7 +127,7 @@ public class CompositeImportRoleTest {
|
|||
|
||||
@Test
|
||||
public void testRealmOnlyWithUserCompositeAppComposite() throws Exception {
|
||||
oauth.realm("Test");
|
||||
oauth.realm("test");
|
||||
oauth.realmPublicKey(realmPublicKey);
|
||||
oauth.clientId("REALM_COMPOSITE_1_APPLICATION");
|
||||
oauth.doLogin("REALM_COMPOSITE_1_USER", "password");
|
||||
|
@ -141,7 +141,7 @@ public class CompositeImportRoleTest {
|
|||
|
||||
AccessToken token = oauth.verifyToken(response.getAccessToken());
|
||||
|
||||
Assert.assertEquals(keycloakRule.getUser("Test", "REALM_COMPOSITE_1_USER").getId(), token.getSubject());
|
||||
Assert.assertEquals(keycloakRule.getUser("test", "REALM_COMPOSITE_1_USER").getId(), token.getSubject());
|
||||
|
||||
Assert.assertEquals(2, token.getRealmAccess().getRoles().size());
|
||||
Assert.assertTrue(token.getRealmAccess().isUserInRole("REALM_COMPOSITE_1"));
|
||||
|
@ -150,7 +150,7 @@ public class CompositeImportRoleTest {
|
|||
|
||||
@Test
|
||||
public void testRealmOnlyWithUserCompositeAppRole() throws Exception {
|
||||
oauth.realm("Test");
|
||||
oauth.realm("test");
|
||||
oauth.realmPublicKey(realmPublicKey);
|
||||
oauth.clientId("REALM_ROLE_1_APPLICATION");
|
||||
oauth.doLogin("REALM_COMPOSITE_1_USER", "password");
|
||||
|
@ -164,7 +164,7 @@ public class CompositeImportRoleTest {
|
|||
|
||||
AccessToken token = oauth.verifyToken(response.getAccessToken());
|
||||
|
||||
Assert.assertEquals(keycloakRule.getUser("Test", "REALM_COMPOSITE_1_USER").getId(), token.getSubject());
|
||||
Assert.assertEquals(keycloakRule.getUser("test", "REALM_COMPOSITE_1_USER").getId(), token.getSubject());
|
||||
|
||||
Assert.assertEquals(1, token.getRealmAccess().getRoles().size());
|
||||
Assert.assertTrue(token.getRealmAccess().isUserInRole("REALM_ROLE_1"));
|
||||
|
@ -172,7 +172,7 @@ public class CompositeImportRoleTest {
|
|||
|
||||
@Test
|
||||
public void testRealmOnlyWithUserRoleAppComposite() throws Exception {
|
||||
oauth.realm("Test");
|
||||
oauth.realm("test");
|
||||
oauth.realmPublicKey(realmPublicKey);
|
||||
oauth.clientId("REALM_COMPOSITE_1_APPLICATION");
|
||||
oauth.doLogin("REALM_ROLE_1_USER", "password");
|
||||
|
@ -186,13 +186,10 @@ public class CompositeImportRoleTest {
|
|||
|
||||
AccessToken token = oauth.verifyToken(response.getAccessToken());
|
||||
|
||||
Assert.assertEquals(keycloakRule.getUser("Test", "REALM_ROLE_1_USER").getId(), token.getSubject());
|
||||
Assert.assertEquals(keycloakRule.getUser("test", "REALM_ROLE_1_USER").getId(), token.getSubject());
|
||||
|
||||
Assert.assertEquals(1, token.getRealmAccess().getRoles().size());
|
||||
Assert.assertTrue(token.getRealmAccess().isUserInRole("REALM_ROLE_1"));
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -58,7 +58,7 @@ public class CompositeRoleTest {
|
|||
public static AbstractKeycloakRule keycloakRule = new AbstractKeycloakRule(){
|
||||
@Override
|
||||
protected void configure(KeycloakSession session, RealmManager manager, RealmModel adminRealm) {
|
||||
RealmModel realm = manager.createRealm("Test");
|
||||
RealmModel realm = manager.createRealm("test");
|
||||
KeycloakModelUtils.generateRealmKeys(realm);
|
||||
realmPublicKey = realm.getPublicKey();
|
||||
realm.setSsoSessionIdleTimeout(3000);
|
||||
|
@ -166,7 +166,7 @@ public class CompositeRoleTest {
|
|||
|
||||
@Test
|
||||
public void testAppCompositeUser() throws Exception {
|
||||
oauth.realm("Test");
|
||||
oauth.realm("test");
|
||||
oauth.realmPublicKey(realmPublicKey);
|
||||
oauth.clientId("APP_COMPOSITE_APPLICATION");
|
||||
oauth.doLogin("APP_COMPOSITE_USER", "password");
|
||||
|
@ -180,7 +180,7 @@ public class CompositeRoleTest {
|
|||
|
||||
AccessToken token = oauth.verifyToken(response.getAccessToken());
|
||||
|
||||
Assert.assertEquals(keycloakRule.getUser("Test", "APP_COMPOSITE_USER").getId(), token.getSubject());
|
||||
Assert.assertEquals(keycloakRule.getUser("test", "APP_COMPOSITE_USER").getId(), token.getSubject());
|
||||
|
||||
Assert.assertEquals(1, token.getResourceAccess("APP_ROLE_APPLICATION").getRoles().size());
|
||||
Assert.assertEquals(1, token.getRealmAccess().getRoles().size());
|
||||
|
@ -194,7 +194,7 @@ public class CompositeRoleTest {
|
|||
|
||||
@Test
|
||||
public void testRealmAppCompositeUser() throws Exception {
|
||||
oauth.realm("Test");
|
||||
oauth.realm("test");
|
||||
oauth.realmPublicKey(realmPublicKey);
|
||||
oauth.clientId("APP_ROLE_APPLICATION");
|
||||
oauth.doLogin("REALM_APP_COMPOSITE_USER", "password");
|
||||
|
@ -208,7 +208,7 @@ public class CompositeRoleTest {
|
|||
|
||||
AccessToken token = oauth.verifyToken(response.getAccessToken());
|
||||
|
||||
Assert.assertEquals(keycloakRule.getUser("Test", "REALM_APP_COMPOSITE_USER").getId(), token.getSubject());
|
||||
Assert.assertEquals(keycloakRule.getUser("test", "REALM_APP_COMPOSITE_USER").getId(), token.getSubject());
|
||||
|
||||
Assert.assertEquals(1, token.getResourceAccess("APP_ROLE_APPLICATION").getRoles().size());
|
||||
Assert.assertTrue(token.getResourceAccess("APP_ROLE_APPLICATION").isUserInRole("APP_ROLE_1"));
|
||||
|
@ -219,7 +219,7 @@ public class CompositeRoleTest {
|
|||
|
||||
@Test
|
||||
public void testRealmOnlyWithUserCompositeAppComposite() throws Exception {
|
||||
oauth.realm("Test");
|
||||
oauth.realm("test");
|
||||
oauth.realmPublicKey(realmPublicKey);
|
||||
oauth.clientId("REALM_COMPOSITE_1_APPLICATION");
|
||||
oauth.doLogin("REALM_COMPOSITE_1_USER", "password");
|
||||
|
@ -233,7 +233,7 @@ public class CompositeRoleTest {
|
|||
|
||||
AccessToken token = oauth.verifyToken(response.getAccessToken());
|
||||
|
||||
Assert.assertEquals(keycloakRule.getUser("Test", "REALM_COMPOSITE_1_USER").getId(), token.getSubject());
|
||||
Assert.assertEquals(keycloakRule.getUser("test", "REALM_COMPOSITE_1_USER").getId(), token.getSubject());
|
||||
|
||||
Assert.assertEquals(2, token.getRealmAccess().getRoles().size());
|
||||
Assert.assertTrue(token.getRealmAccess().isUserInRole("REALM_COMPOSITE_1"));
|
||||
|
@ -245,7 +245,7 @@ public class CompositeRoleTest {
|
|||
|
||||
@Test
|
||||
public void testRealmOnlyWithUserCompositeAppRole() throws Exception {
|
||||
oauth.realm("Test");
|
||||
oauth.realm("test");
|
||||
oauth.realmPublicKey(realmPublicKey);
|
||||
oauth.clientId("REALM_ROLE_1_APPLICATION");
|
||||
oauth.doLogin("REALM_COMPOSITE_1_USER", "password");
|
||||
|
@ -259,7 +259,7 @@ public class CompositeRoleTest {
|
|||
|
||||
AccessToken token = oauth.verifyToken(response.getAccessToken());
|
||||
|
||||
Assert.assertEquals(keycloakRule.getUser("Test", "REALM_COMPOSITE_1_USER").getId(), token.getSubject());
|
||||
Assert.assertEquals(keycloakRule.getUser("test", "REALM_COMPOSITE_1_USER").getId(), token.getSubject());
|
||||
|
||||
Assert.assertEquals(1, token.getRealmAccess().getRoles().size());
|
||||
Assert.assertTrue(token.getRealmAccess().isUserInRole("REALM_ROLE_1"));
|
||||
|
@ -270,7 +270,7 @@ public class CompositeRoleTest {
|
|||
|
||||
@Test
|
||||
public void testRealmOnlyWithUserRoleAppComposite() throws Exception {
|
||||
oauth.realm("Test");
|
||||
oauth.realm("test");
|
||||
oauth.realmPublicKey(realmPublicKey);
|
||||
oauth.clientId("REALM_COMPOSITE_1_APPLICATION");
|
||||
oauth.doLogin("REALM_ROLE_1_USER", "password");
|
||||
|
@ -284,7 +284,7 @@ public class CompositeRoleTest {
|
|||
|
||||
AccessToken token = oauth.verifyToken(response.getAccessToken());
|
||||
|
||||
Assert.assertEquals(keycloakRule.getUser("Test", "REALM_ROLE_1_USER").getId(), token.getSubject());
|
||||
Assert.assertEquals(keycloakRule.getUser("test", "REALM_ROLE_1_USER").getId(), token.getSubject());
|
||||
|
||||
Assert.assertEquals(1, token.getRealmAccess().getRoles().size());
|
||||
Assert.assertTrue(token.getRealmAccess().isUserInRole("REALM_ROLE_1"));
|
||||
|
|
|
@ -64,7 +64,7 @@ public class ExportImportTest {
|
|||
propsHelper.pushProperty(JPA_CONNECTION_URL, "jdbc:h2:file:" + dbDir + ";DB_CLOSE_DELAY=-1");
|
||||
connectionURLSet = true;
|
||||
}
|
||||
propsHelper.pushProperty(JPA_DB_SCHEMA, "create");
|
||||
propsHelper.pushProperty(JPA_DB_SCHEMA, "update");
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -98,7 +98,6 @@ public class ExportImportTest {
|
|||
addUser(manager.getSession().users(), appRealm, "user1", "password");
|
||||
addUser(manager.getSession().users(), appRealm, "user2", "password");
|
||||
addUser(manager.getSession().users(), appRealm, "user3", "password");
|
||||
addUser(manager.getSession().users(), adminstrationRealm, "admin2", "admin2");
|
||||
|
||||
// Import "test-realm" realm
|
||||
try {
|
||||
|
@ -129,6 +128,10 @@ public class ExportImportTest {
|
|||
systemProps.remove(propToRemove);
|
||||
}
|
||||
}
|
||||
|
||||
protected String[] getTestRealms() {
|
||||
return new String[]{"test", "demo", "test-realm"};
|
||||
}
|
||||
};
|
||||
|
||||
@ClassRule
|
||||
|
@ -222,10 +225,6 @@ public class ExportImportTest {
|
|||
new RealmManager(session).removeRealm(realmProvider.getRealmByName("test"));
|
||||
Assert.assertEquals(2, realmProvider.getRealms().size());
|
||||
|
||||
RealmModel master = realmProvider.getRealmByName(Config.getAdminRealm());
|
||||
UserModel admin2 = session.users().getUserByUsername("admin2", master);
|
||||
session.users().removeUser(master, admin2);
|
||||
assertNotAuthenticated(userProvider, realmProvider, Config.getAdminRealm(), "admin2", "admin2");
|
||||
assertNotAuthenticated(userProvider, realmProvider, "test", "test-user@localhost", "password");
|
||||
assertNotAuthenticated(userProvider, realmProvider, "test", "user1", "password");
|
||||
assertNotAuthenticated(userProvider, realmProvider, "test", "user2", "password");
|
||||
|
@ -247,7 +246,6 @@ public class ExportImportTest {
|
|||
UserProvider userProvider = session.users();
|
||||
Assert.assertEquals(3, model.getRealms().size());
|
||||
|
||||
assertAuthenticated(userProvider, model, Config.getAdminRealm(), "admin2", "admin2");
|
||||
assertAuthenticated(userProvider, model, "test", "test-user@localhost", "password");
|
||||
assertAuthenticated(userProvider, model, "test", "user1", "password");
|
||||
assertAuthenticated(userProvider, model, "test", "user2", "password");
|
||||
|
@ -275,11 +273,6 @@ public class ExportImportTest {
|
|||
new RealmManager(session).removeRealm(realmProvider.getRealmByName("test"));
|
||||
Assert.assertEquals(2, realmProvider.getRealms().size());
|
||||
|
||||
RealmModel master = realmProvider.getRealmByName(Config.getAdminRealm());
|
||||
UserModel admin2 = session.users().getUserByUsername("admin2", master);
|
||||
session.users().removeUser(master, admin2);
|
||||
|
||||
assertNotAuthenticated(userProvider, realmProvider, Config.getAdminRealm(), "admin2", "admin2");
|
||||
assertNotAuthenticated(userProvider, realmProvider, "test", "test-user@localhost", "password");
|
||||
assertNotAuthenticated(userProvider, realmProvider, "test", "user1", "password");
|
||||
assertNotAuthenticated(userProvider, realmProvider, "test", "user2", "password");
|
||||
|
@ -301,13 +294,10 @@ public class ExportImportTest {
|
|||
UserProvider userProvider = session.users();
|
||||
Assert.assertEquals(3, realmProvider.getRealms().size());
|
||||
|
||||
assertNotAuthenticated(userProvider, realmProvider, Config.getAdminRealm(), "admin2", "admin2");
|
||||
assertAuthenticated(userProvider, realmProvider, "test", "test-user@localhost", "password");
|
||||
assertAuthenticated(userProvider, realmProvider, "test", "user1", "password");
|
||||
assertAuthenticated(userProvider, realmProvider, "test", "user2", "password");
|
||||
assertAuthenticated(userProvider, realmProvider, "test", "user3", "password");
|
||||
|
||||
addUser(userProvider, realmProvider.getRealmByName(Config.getAdminRealm()), "admin2", "admin2");
|
||||
} finally {
|
||||
keycloakRule.stopSession(session, true);
|
||||
}
|
||||
|
|
|
@ -30,12 +30,15 @@ import java.net.Socket;
|
|||
* @version $Revision: 1 $
|
||||
*/
|
||||
public abstract class AbstractKeycloakRule extends ExternalResource {
|
||||
|
||||
protected KeycloakServer server;
|
||||
|
||||
protected void before() throws Throwable {
|
||||
server = new KeycloakServer();
|
||||
server.start();
|
||||
|
||||
removeTestRealms();
|
||||
|
||||
setupKeycloak();
|
||||
}
|
||||
|
||||
|
@ -123,6 +126,7 @@ public abstract class AbstractKeycloakRule extends ExternalResource {
|
|||
deploymentInfo.addServlet(servlet);
|
||||
return deploymentInfo;
|
||||
}
|
||||
|
||||
public void deployApplication(String name, String contextPath, Class<? extends Servlet> servletClass, String adapterConfigPath, String role) {
|
||||
deployApplication(name, contextPath, servletClass, adapterConfigPath, role, true);
|
||||
|
||||
|
@ -147,9 +151,27 @@ public abstract class AbstractKeycloakRule extends ExternalResource {
|
|||
|
||||
@Override
|
||||
protected void after() {
|
||||
removeTestRealms();
|
||||
stopServer();
|
||||
}
|
||||
|
||||
protected void removeTestRealms() {
|
||||
KeycloakSession session = server.getSessionFactory().create();
|
||||
try {
|
||||
session.getTransaction().begin();
|
||||
RealmManager realmManager = new RealmManager(session);
|
||||
for (String realmName : getTestRealms()) {
|
||||
RealmModel realm = realmManager.getRealmByName(realmName);
|
||||
if (realm != null) {
|
||||
realmManager.removeRealm(realm);
|
||||
}
|
||||
}
|
||||
session.getTransaction().commit();
|
||||
} finally {
|
||||
session.close();
|
||||
}
|
||||
}
|
||||
|
||||
public RealmRepresentation loadJson(String path) throws IOException {
|
||||
InputStream is = Thread.currentThread().getContextClassLoader().getResourceAsStream(path);
|
||||
ByteArrayOutputStream os = new ByteArrayOutputStream();
|
||||
|
@ -169,7 +191,7 @@ public abstract class AbstractKeycloakRule extends ExternalResource {
|
|||
|
||||
public void stopSession(KeycloakSession session, boolean commit) {
|
||||
KeycloakTransaction transaction = session.getTransaction();
|
||||
if (commit) {
|
||||
if (commit && !transaction.getRollbackOnly()) {
|
||||
transaction.commit();
|
||||
} else {
|
||||
transaction.rollback();
|
||||
|
@ -208,4 +230,9 @@ public abstract class AbstractKeycloakRule extends ExternalResource {
|
|||
Thread.currentThread().interrupt();
|
||||
}
|
||||
}
|
||||
|
||||
protected String[] getTestRealms() {
|
||||
return new String[]{"test", "demo"};
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"id": "Test",
|
||||
"realm": "Test",
|
||||
"id": "test",
|
||||
"realm": "test",
|
||||
"enabled": true,
|
||||
"accessTokenLifespan": 600,
|
||||
"accessCodeLifespan": 600,
|
||||
|
|
Loading…
Reference in a new issue