diff --git a/connections/jpa-liquibase/pom.xml b/connections/jpa-liquibase/pom.xml new file mode 100755 index 0000000000..5196c428ad --- /dev/null +++ b/connections/jpa-liquibase/pom.xml @@ -0,0 +1,74 @@ + + + + keycloak-parent + org.keycloak + 1.1.0-Alpha1-SNAPSHOT + ../../pom.xml + + 4.0.0 + + keycloak-connections-jpa-liquibase + Keycloak Connections JPA Liquibase Updater + + + + + org.keycloak + keycloak-connections-jpa + ${project.version} + + + org.liquibase + liquibase-core + + + org.yaml + snakeyaml + + + + + org.jboss.logging + jboss-logging + + + + + + + org.liquibase + liquibase-maven-plugin + + 3.1.1 + + META-INF/jpa-changelog-master.xml + + ${url} + ${driver} + ${username} + ${password} + + ${referenceUrl} + ${driver} + ${username} + ${password} + + + + com.h2database + h2 + ${h2.version} + + + + + + + + sa + + org.h2.Driver + + diff --git a/connections/jpa-liquibase/src/main/java/org/keycloak/connections/jpa/updater/liquibase/LiquibaseJpaUpdaterProvider.java b/connections/jpa-liquibase/src/main/java/org/keycloak/connections/jpa/updater/liquibase/LiquibaseJpaUpdaterProvider.java new file mode 100644 index 0000000000..8d1f6fad4a --- /dev/null +++ b/connections/jpa-liquibase/src/main/java/org/keycloak/connections/jpa/updater/liquibase/LiquibaseJpaUpdaterProvider.java @@ -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 Stian Thorgersen + */ +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 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 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; + } + + } + +} diff --git a/connections/jpa-liquibase/src/main/java/org/keycloak/connections/jpa/updater/liquibase/LiquibaseJpaUpdaterProviderFactory.java b/connections/jpa-liquibase/src/main/java/org/keycloak/connections/jpa/updater/liquibase/LiquibaseJpaUpdaterProviderFactory.java new file mode 100644 index 0000000000..d88b787df1 --- /dev/null +++ b/connections/jpa-liquibase/src/main/java/org/keycloak/connections/jpa/updater/liquibase/LiquibaseJpaUpdaterProviderFactory.java @@ -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 Stian Thorgersen + */ +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"; + } + +} diff --git a/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.0.0.Final.xml b/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.0.0.Final.xml new file mode 100644 index 0000000000..5b5e921e26 --- /dev/null +++ b/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.0.0.Final.xml @@ -0,0 +1,376 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.1.0.Beta1.xml b/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.1.0.Beta1.xml new file mode 100644 index 0000000000..0b9f0f527f --- /dev/null +++ b/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.1.0.Beta1.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-master.xml b/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-master.xml new file mode 100644 index 0000000000..010b121b68 --- /dev/null +++ b/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-master.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/connections/jpa-liquibase/src/main/resources/META-INF/services/org.keycloak.connections.jpa.updater.JpaUpdaterProviderFactory b/connections/jpa-liquibase/src/main/resources/META-INF/services/org.keycloak.connections.jpa.updater.JpaUpdaterProviderFactory new file mode 100644 index 0000000000..cb36ec330d --- /dev/null +++ b/connections/jpa-liquibase/src/main/resources/META-INF/services/org.keycloak.connections.jpa.updater.JpaUpdaterProviderFactory @@ -0,0 +1 @@ +org.keycloak.connections.jpa.updater.liquibase.LiquibaseJpaUpdaterProviderFactory \ No newline at end of file diff --git a/connections/jpa/src/main/java/org/keycloak/connections/jpa/DefaultJpaConnectionProviderFactory.java b/connections/jpa/src/main/java/org/keycloak/connections/jpa/DefaultJpaConnectionProviderFactory.java index 4bd1d6ee09..5a00c7403e 100755 --- a/connections/jpa/src/main/java/org/keycloak/connections/jpa/DefaultJpaConnectionProviderFactory.java +++ b/connections/jpa/src/main/java/org/keycloak/connections/jpa/DefaultJpaConnectionProviderFactory.java @@ -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 properties = new HashMap(); // 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); + } + } + } diff --git a/connections/jpa/src/main/java/org/keycloak/connections/jpa/updater/JpaUpdaterProvider.java b/connections/jpa/src/main/java/org/keycloak/connections/jpa/updater/JpaUpdaterProvider.java new file mode 100644 index 0000000000..9e19f7f3a7 --- /dev/null +++ b/connections/jpa/src/main/java/org/keycloak/connections/jpa/updater/JpaUpdaterProvider.java @@ -0,0 +1,22 @@ +package org.keycloak.connections.jpa.updater; + +import org.keycloak.provider.Provider; + +import java.sql.Connection; + +/** + * @author Stian Thorgersen + */ +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); + +} diff --git a/connections/jpa/src/main/java/org/keycloak/connections/jpa/updater/JpaUpdaterProviderFactory.java b/connections/jpa/src/main/java/org/keycloak/connections/jpa/updater/JpaUpdaterProviderFactory.java new file mode 100644 index 0000000000..29a89f40e8 --- /dev/null +++ b/connections/jpa/src/main/java/org/keycloak/connections/jpa/updater/JpaUpdaterProviderFactory.java @@ -0,0 +1,9 @@ +package org.keycloak.connections.jpa.updater; + +import org.keycloak.provider.ProviderFactory; + +/** + * @author Stian Thorgersen + */ +public interface JpaUpdaterProviderFactory extends ProviderFactory { +} diff --git a/connections/jpa/src/main/java/org/keycloak/connections/jpa/updater/JpaUpdaterSpi.java b/connections/jpa/src/main/java/org/keycloak/connections/jpa/updater/JpaUpdaterSpi.java new file mode 100644 index 0000000000..e95242aac9 --- /dev/null +++ b/connections/jpa/src/main/java/org/keycloak/connections/jpa/updater/JpaUpdaterSpi.java @@ -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 Stian Thorgersen + */ +public class JpaUpdaterSpi implements Spi { + + @Override + public String getName() { + return "connectionsJpaUpdater"; + } + + @Override + public Class getProviderClass() { + return JpaUpdaterProvider.class; + } + + @Override + public Class getProviderFactoryClass() { + return JpaUpdaterProviderFactory.class; + } + +} diff --git a/connections/jpa/src/main/resources/META-INF/services/org.keycloak.provider.Spi b/connections/jpa/src/main/resources/META-INF/services/org.keycloak.provider.Spi index d7b47a8319..175bcfbf10 100644 --- a/connections/jpa/src/main/resources/META-INF/services/org.keycloak.provider.Spi +++ b/connections/jpa/src/main/resources/META-INF/services/org.keycloak.provider.Spi @@ -1 +1,2 @@ -org.keycloak.connections.jpa.JpaConnectionSpi \ No newline at end of file +org.keycloak.connections.jpa.JpaConnectionSpi +org.keycloak.connections.jpa.updater.JpaUpdaterSpi \ No newline at end of file diff --git a/connections/mongo/src/main/java/org/keycloak/connections/mongo/DefaultMongoConnectionFactoryProvider.java b/connections/mongo/src/main/java/org/keycloak/connections/mongo/DefaultMongoConnectionFactoryProvider.java index e644c7acaf..f800dfbed3 100644 --- a/connections/mongo/src/main/java/org/keycloak/connections/mongo/DefaultMongoConnectionFactoryProvider.java +++ b/connections/mongo/src/main/java/org/keycloak/connections/mongo/DefaultMongoConnectionFactoryProvider.java @@ -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); } diff --git a/connections/mongo/src/main/java/org/keycloak/connections/mongo/api/MongoIndex.java b/connections/mongo/src/main/java/org/keycloak/connections/mongo/api/MongoIndex.java deleted file mode 100644 index 7b8a0f5448..0000000000 --- a/connections/mongo/src/main/java/org/keycloak/connections/mongo/api/MongoIndex.java +++ /dev/null @@ -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 Stian Thorgersen - */ -@Target({TYPE}) -@Documented -@Retention(RUNTIME) -@Inherited -public @interface MongoIndex { - - String[] fields(); - - boolean unique() default false; - - boolean sparse() default false; - -} diff --git a/connections/mongo/src/main/java/org/keycloak/connections/mongo/api/MongoIndexes.java b/connections/mongo/src/main/java/org/keycloak/connections/mongo/api/MongoIndexes.java deleted file mode 100644 index 7f0a3f5f99..0000000000 --- a/connections/mongo/src/main/java/org/keycloak/connections/mongo/api/MongoIndexes.java +++ /dev/null @@ -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 Stian Thorgersen - */ -@Target({TYPE}) -@Documented -@Retention(RUNTIME) -@Inherited -public @interface MongoIndexes { - - MongoIndex[] value(); - -} diff --git a/connections/mongo/src/main/java/org/keycloak/connections/mongo/impl/MongoStoreImpl.java b/connections/mongo/src/main/java/org/keycloak/connections/mongo/impl/MongoStoreImpl.java index 09104ea7a8..85bf25e3d6 100755 --- a/connections/mongo/src/main/java/org/keycloak/connections/mongo/impl/MongoStoreImpl.java +++ b/connections/mongo/src/main/java/org/keycloak/connections/mongo/impl/MongoStoreImpl.java @@ -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, 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 clazz = entity.getClass(); diff --git a/connections/mongo/src/main/java/org/keycloak/connections/mongo/updater/DefaultMongoUpdaterProvider.java b/connections/mongo/src/main/java/org/keycloak/connections/mongo/updater/DefaultMongoUpdaterProvider.java new file mode 100644 index 0000000000..1f3f6ae198 --- /dev/null +++ b/connections/mongo/src/main/java/org/keycloak/connections/mongo/updater/DefaultMongoUpdaterProvider.java @@ -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 Stian Thorgersen + */ +public class DefaultMongoUpdaterProvider implements MongoUpdaterProvider { + + public static final Logger log = Logger.getLogger(DefaultMongoUpdaterProvider.class); + + public static final String CHANGE_LOG_COLLECTION = "databaseChangeLog"; + + private Class[] 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 executed = new LinkedList(); + 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 updatesToRun = new LinkedList(); + for (Class 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() { + } + +} diff --git a/connections/mongo/src/main/java/org/keycloak/connections/mongo/updater/DefaultMongoUpdaterProviderFactory.java b/connections/mongo/src/main/java/org/keycloak/connections/mongo/updater/DefaultMongoUpdaterProviderFactory.java new file mode 100644 index 0000000000..80698505b0 --- /dev/null +++ b/connections/mongo/src/main/java/org/keycloak/connections/mongo/updater/DefaultMongoUpdaterProviderFactory.java @@ -0,0 +1,29 @@ +package org.keycloak.connections.mongo.updater; + +import org.keycloak.Config; +import org.keycloak.models.KeycloakSession; + +/** + * @author Stian Thorgersen + */ +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"; + } + +} diff --git a/connections/mongo/src/main/java/org/keycloak/connections/mongo/updater/MongoUpdaterProvider.java b/connections/mongo/src/main/java/org/keycloak/connections/mongo/updater/MongoUpdaterProvider.java new file mode 100644 index 0000000000..7192f1d10d --- /dev/null +++ b/connections/mongo/src/main/java/org/keycloak/connections/mongo/updater/MongoUpdaterProvider.java @@ -0,0 +1,13 @@ +package org.keycloak.connections.mongo.updater; + +import com.mongodb.DB; +import org.keycloak.provider.Provider; + +/** + * @author Stian Thorgersen + */ +public interface MongoUpdaterProvider extends Provider { + + public void update(DB db); + +} diff --git a/connections/mongo/src/main/java/org/keycloak/connections/mongo/updater/MongoUpdaterProviderFactory.java b/connections/mongo/src/main/java/org/keycloak/connections/mongo/updater/MongoUpdaterProviderFactory.java new file mode 100644 index 0000000000..981f6d0ad9 --- /dev/null +++ b/connections/mongo/src/main/java/org/keycloak/connections/mongo/updater/MongoUpdaterProviderFactory.java @@ -0,0 +1,9 @@ +package org.keycloak.connections.mongo.updater; + +import org.keycloak.provider.ProviderFactory; + +/** + * @author Stian Thorgersen + */ +public interface MongoUpdaterProviderFactory extends ProviderFactory { +} diff --git a/connections/mongo/src/main/java/org/keycloak/connections/mongo/updater/MongoUpdaterSpi.java b/connections/mongo/src/main/java/org/keycloak/connections/mongo/updater/MongoUpdaterSpi.java new file mode 100644 index 0000000000..da89b4d8f8 --- /dev/null +++ b/connections/mongo/src/main/java/org/keycloak/connections/mongo/updater/MongoUpdaterSpi.java @@ -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 Stian Thorgersen + */ +public class MongoUpdaterSpi implements Spi { + + @Override + public String getName() { + return "connectionsMongoUpdater"; + } + + @Override + public Class getProviderClass() { + return MongoUpdaterProvider.class; + } + + @Override + public Class getProviderFactoryClass() { + return MongoUpdaterProviderFactory.class; + } + +} diff --git a/connections/mongo/src/main/java/org/keycloak/connections/mongo/updater/updates/Update.java b/connections/mongo/src/main/java/org/keycloak/connections/mongo/updater/updates/Update.java new file mode 100644 index 0000000000..e8058f02f4 --- /dev/null +++ b/connections/mongo/src/main/java/org/keycloak/connections/mongo/updater/updates/Update.java @@ -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 Stian Thorgersen + */ +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; + } + +} diff --git a/connections/mongo/src/main/java/org/keycloak/connections/mongo/updater/updates/Update1_0_0_Final.java b/connections/mongo/src/main/java/org/keycloak/connections/mongo/updater/updates/Update1_0_0_Final.java new file mode 100644 index 0000000000..ded25dc74e --- /dev/null +++ b/connections/mongo/src/main/java/org/keycloak/connections/mongo/updater/updates/Update1_0_0_Final.java @@ -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 Stian Thorgersen + */ +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"); + } + +} diff --git a/connections/mongo/src/main/java/org/keycloak/connections/mongo/updater/updates/Update1_1_0_Beta1.java b/connections/mongo/src/main/java/org/keycloak/connections/mongo/updater/updates/Update1_1_0_Beta1.java new file mode 100644 index 0000000000..89d372f4be --- /dev/null +++ b/connections/mongo/src/main/java/org/keycloak/connections/mongo/updater/updates/Update1_1_0_Beta1.java @@ -0,0 +1,19 @@ +package org.keycloak.connections.mongo.updater.updates; + +/** + * @author Stian Thorgersen + */ +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"); + } + +} diff --git a/connections/pom.xml b/connections/pom.xml index a5b3bb2d6d..2d284bb5f2 100755 --- a/connections/pom.xml +++ b/connections/pom.xml @@ -14,6 +14,7 @@ jpa + jpa-liquibase infinispan mongo diff --git a/dependencies/server-all/pom.xml b/dependencies/server-all/pom.xml index a812b08f12..9c20bd459d 100755 --- a/dependencies/server-all/pom.xml +++ b/dependencies/server-all/pom.xml @@ -26,6 +26,11 @@ keycloak-connections-jpa ${project.version} + + org.keycloak + keycloak-connections-jpa-liquibase + ${project.version} + org.keycloak keycloak-connections-infinispan @@ -180,6 +185,17 @@ de.idyl winzipaes + + + org.liquibase + liquibase-core + + + org.yaml + snakeyaml + + + \ No newline at end of file diff --git a/misc/UpdatingDatabaseSchema.md b/misc/UpdatingDatabaseSchema.md new file mode 100644 index 0000000000..1dcee578df --- /dev/null +++ b/misc/UpdatingDatabaseSchema.md @@ -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-.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` 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. \ No newline at end of file diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/MongoApplicationEntity.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/MongoApplicationEntity.java index 9000d37ca4..7534e66fb0 100755 --- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/MongoApplicationEntity.java +++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/MongoApplicationEntity.java @@ -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 Marek Posolda */ @MongoCollection(collectionName = "applications") -@MongoIndex(fields = { "realmId", "name" }, unique = true) public class MongoApplicationEntity extends ApplicationEntity implements MongoIdentifiableEntity { @Override diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/MongoOAuthClientEntity.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/MongoOAuthClientEntity.java index bf07f91a20..ab01359cd5 100755 --- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/MongoOAuthClientEntity.java +++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/MongoOAuthClientEntity.java @@ -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 Marek Posolda */ @MongoCollection(collectionName = "oauthClients") -@MongoIndex(fields = { "realmId", "name" }, unique = true) public class MongoOAuthClientEntity extends OAuthClientEntity implements MongoIdentifiableEntity { @Override diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/MongoRealmEntity.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/MongoRealmEntity.java index c1a4267337..278435200c 100755 --- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/MongoRealmEntity.java +++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/MongoRealmEntity.java @@ -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 Marek Posolda */ @MongoCollection(collectionName = "realms") -@MongoIndex(fields = { "name" }, unique = true) public class MongoRealmEntity extends RealmEntity implements MongoIdentifiableEntity { @Override diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/MongoRoleEntity.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/MongoRoleEntity.java index be034da440..cf0cfed248 100755 --- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/MongoRoleEntity.java +++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/MongoRoleEntity.java @@ -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 Marek Posolda */ @MongoCollection(collectionName = "roles") -@MongoIndex(fields = "nameIndex", unique = true) public class MongoRoleEntity extends RoleEntity implements MongoIdentifiableEntity { private static final Logger logger = Logger.getLogger(MongoRoleEntity.class); diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/MongoUserEntity.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/MongoUserEntity.java index a0c16bf9e7..c9f317e50d 100755 --- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/MongoUserEntity.java +++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/MongoUserEntity.java @@ -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 Marek Posolda */ @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; } diff --git a/model/sessions-mem/src/main/java/org/keycloak/models/sessions/mem/MemUserSessionProvider.java b/model/sessions-mem/src/main/java/org/keycloak/models/sessions/mem/MemUserSessionProvider.java index b43f827607..53cf7f34e9 100755 --- a/model/sessions-mem/src/main/java/org/keycloak/models/sessions/mem/MemUserSessionProvider.java +++ b/model/sessions-mem/src/main/java/org/keycloak/models/sessions/mem/MemUserSessionProvider.java @@ -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); } diff --git a/pom.xml b/pom.xml index 73523c09df..763bdcb731 100755 --- a/pom.xml +++ b/pom.xml @@ -44,6 +44,7 @@ 2.35.0 1.4.5 6.0.2.Final + 3.2.2 1.6 @@ -449,6 +450,11 @@ infinispan-core ${infinispan.version} + + org.liquibase + liquibase-core + ${liquibase.version} + diff --git a/testsuite/integration/src/main/resources/META-INF/keycloak-server.json b/testsuite/integration/src/main/resources/META-INF/keycloak-server.json index 6e90fa12df..728631d7a9 100755 --- a/testsuite/integration/src/main/resources/META-INF/keycloak-server.json +++ b/testsuite/integration/src/main/resources/META-INF/keycloak-server.json @@ -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}" } } } \ No newline at end of file diff --git a/testsuite/integration/src/main/resources/log4j.properties b/testsuite/integration/src/main/resources/log4j.properties index 315b7ca2f6..778d39ac55 100755 --- a/testsuite/integration/src/main/resources/log4j.properties +++ b/testsuite/integration/src/main/resources/log4j.properties @@ -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 \ No newline at end of file diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/account/AccountTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/account/AccountTest.java index 5aa52c3c86..4cfaaa3586 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/account/AccountTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/account/AccountTest.java @@ -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() { diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/composites/CompositeImportRoleTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/composites/CompositeImportRoleTest.java index 32ba03cfcd..47d1ff5204 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/composites/CompositeImportRoleTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/composites/CompositeImportRoleTest.java @@ -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")); } - - - } diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/composites/CompositeRoleTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/composites/CompositeRoleTest.java index 6aaf0682af..b661d5a08b 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/composites/CompositeRoleTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/composites/CompositeRoleTest.java @@ -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")); diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/exportimport/ExportImportTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/exportimport/ExportImportTest.java index c4bf603c8b..5261da63e4 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/exportimport/ExportImportTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/exportimport/ExportImportTest.java @@ -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); } diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/rule/AbstractKeycloakRule.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/rule/AbstractKeycloakRule.java index 7b616f332d..06e793f92a 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/rule/AbstractKeycloakRule.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/rule/AbstractKeycloakRule.java @@ -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 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"}; + } + } diff --git a/testsuite/integration/src/test/resources/testcomposite.json b/testsuite/integration/src/test/resources/testcomposite.json index 8f3f76fd95..5870eb0b29 100755 --- a/testsuite/integration/src/test/resources/testcomposite.json +++ b/testsuite/integration/src/test/resources/testcomposite.json @@ -1,6 +1,6 @@ { - "id": "Test", - "realm": "Test", + "id": "test", + "realm": "test", "enabled": true, "accessTokenLifespan": 600, "accessCodeLifespan": 600,