keycloak-memoizeit
Changes
examples/providers/domain-extension/src/main/java/org/keycloak/examples/domainextension/CompanyRepresentation.java 17(+12 -5)
examples/providers/domain-extension/src/main/java/org/keycloak/examples/domainextension/entities/Company.java 110(+0 -110)
examples/providers/domain-extension/src/main/java/org/keycloak/examples/domainextension/entities/UserAccount.java 73(+0 -73)
examples/providers/domain-extension/src/main/java/org/keycloak/examples/domainextension/entities/UserAccountRegionRole.java 79(+0 -79)
examples/providers/domain-extension/src/main/java/org/keycloak/examples/domainextension/jpa/Company.java 46(+25 -21)
examples/providers/domain-extension/src/main/java/org/keycloak/examples/domainextension/jpa/ExampleJpaEntityProvider.java 16(+8 -8)
examples/providers/domain-extension/src/main/java/org/keycloak/examples/domainextension/jpa/ExampleJpaEntityProviderFactory.java 4(+2 -2)
examples/providers/domain-extension/src/main/java/org/keycloak/examples/domainextension/rest/CompanyResource.java 41(+26 -15)
examples/providers/domain-extension/src/main/java/org/keycloak/examples/domainextension/rest/ExampleRealmResourceProvider.java 3(+1 -2)
examples/providers/domain-extension/src/main/java/org/keycloak/examples/domainextension/rest/ExampleRealmResourceProviderFactory.java 2(+1 -1)
examples/providers/domain-extension/src/main/java/org/keycloak/examples/domainextension/rest/ExampleRestResource.java 25(+24 -1)
examples/providers/domain-extension/src/main/java/org/keycloak/examples/domainextension/services/repository/AbstractRepository.java 51(+0 -51)
examples/providers/domain-extension/src/main/java/org/keycloak/examples/domainextension/spi/ExampleService.java 10(+5 -5)
examples/providers/domain-extension/src/main/java/org/keycloak/examples/domainextension/spi/ExampleServiceProviderFactory.java 3(+1 -2)
examples/providers/domain-extension/src/main/java/org/keycloak/examples/domainextension/spi/ExampleSpi.java 3(+1 -2)
examples/providers/domain-extension/src/main/java/org/keycloak/examples/domainextension/spi/impl/ExampleServiceImpl.java 41(+29 -12)
examples/providers/domain-extension/src/main/java/org/keycloak/examples/domainextension/spi/impl/ExampleServiceProviderFactoryImpl.java 8(+4 -4)
examples/providers/domain-extension/src/main/resources/META-INF/services/org.keycloak.connections.jpa.entityprovider.JpaEntityProviderFactory 2(+1 -1)
examples/providers/domain-extension/src/main/resources/META-INF/services/org.keycloak.examples.domainextension.spi.ExampleServiceProviderFactory 2(+1 -1)
examples/providers/domain-extension/src/main/resources/META-INF/services/org.keycloak.provider.Spi 2(+1 -1)
examples/providers/domain-extension/src/main/resources/META-INF/services/org.keycloak.services.resource.RealmResourceProviderFactory 2(+1 -1)
model/jpa/pom.xml 5(+5 -0)
model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/conn/DefaultLiquibaseConnectionProvider.java 79(+15 -64)
model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/conn/LiquibaseConnectionProvider.java 2(+2 -0)
model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/LiquibaseJpaUpdaterProvider.java 104(+75 -29)
testsuite/integration/pom.xml 4(+4 -0)
Details
diff --git a/examples/providers/domain-extension/invoke-authenticated.sh b/examples/providers/domain-extension/invoke-authenticated.sh
new file mode 100755
index 0000000..19b56b1
--- /dev/null
+++ b/examples/providers/domain-extension/invoke-authenticated.sh
@@ -0,0 +1,19 @@
+#!/bin/bash
+
+export DIRECT_GRANT_RESPONSE=$(curl -i --request POST http://localhost:8080/auth/realms/master/protocol/openid-connect/token --header "Accept: application/json" --header "Content-Type: application/x-www-form-urlencoded" --data "grant_type=password&username=admin&password=admin&client_id=admin-cli")
+
+echo -e "\n\nSENT RESOURCE-OWNER-PASSWORD-CREDENTIALS-REQUEST. OUTPUT IS:\n\n";
+echo $DIRECT_GRANT_RESPONSE;
+
+export ACCESS_TOKEN=$(echo $DIRECT_GRANT_RESPONSE | grep "access_token" | sed 's/.*\"access_token\":\"\([^\"]*\)\".*/\1/g');
+echo -e "\n\nACCESS TOKEN IS \"$ACCESS_TOKEN\"";
+
+echo -e "\n\nSENDING UN-AUTHENTICATED REQUEST. THIS SHOULD FAIL WITH 401: ";
+curl -i --request POST http://localhost:8080/auth/realms/master/example/companies-auth --data "{ \"name\": \"auth foo company\" }" --header "Content-type: application/json"
+
+echo -e "\n\nSENDING AUTHENTICATED REQUEST. THIS SHOULD SUCCESSFULY CREATE COMPANY AND SUCCESS WITH 201: ";
+curl -i --request POST http://localhost:8080/auth/realms/master/example/companies-auth --data "{ \"name\": \"auth foo company\" }" --header "Content-type: application/json" --header "Authorization: Bearer $ACCESS_TOKEN";
+
+echo -e "\n\nSEARCH COMPANIES: ";
+curl -i --request GET http://localhost:8080/auth/realms/master/example/companies-auth --header "Accept: application/json" --header "Authorization: Bearer $ACCESS_TOKEN";
+
diff --git a/examples/providers/domain-extension/README.md b/examples/providers/domain-extension/README.md
index c8163cd..e1aa2cd 100644
--- a/examples/providers/domain-extension/README.md
+++ b/examples/providers/domain-extension/README.md
@@ -3,7 +3,8 @@ Example Domain Extension
To run, deploy as a module by running:
- $KEYCLOAK_HOME/bin/jboss-cli.sh --command="module add --name=org.keycloak.examples.domain-extension-example --resources=target/domain-extension-example.jar --dependencies=org.keycloak.keycloak-core,org.keycloak.keycloak-services,org.keycloak.keycloak-model-jpa,org.keycloak.keycloak-server-spi,javax.ws.rs.api"
+ $KEYCLOAK_HOME/bin/jboss-cli.sh --command="module add --name=org.keycloak.examples.domain-extension-example --resources=target/domain-extension-example.jar --dependencies=org.keycloak.keycloak-core,org.keycloak.keycloak-services,org.keycloak.keycloak-model-jpa,org.keycloak.keycloak-server-spi,javax.ws.rs.api,javax.persistence.api,org.hibernate,org.javassist"
+
Then registering the provider by editing keycloak-server.json and adding the module to the providers field:
@@ -12,4 +13,32 @@ Then registering the provider by editing keycloak-server.json and adding the mod
"module:org.keycloak.examples.domain-extension-example"
],
-Then start (or restart) the server. Once started do xyz TODO.
\ No newline at end of file
+Then start (or restart) the server.
+
+Testing
+-------
+First you can create some example companies with these CURL requests.
+
+````
+curl -i --request POST http://localhost:8080/auth/realms/master/example/companies --data "{ \"name\": \"foo company\" }" --header "Content-type: application/json"
+curl -i --request POST http://localhost:8080/auth/realms/master/example/companies --data "{ \"name\": \"bar company\" }" --header "Content-type: application/json"
+````
+
+Then you can lookup all companies
+
+````
+curl -i --request GET http://localhost:8080/auth/realms/master/example/companies --header "Accept: application/json"
+````
+
+If you create realm `foo` in Keycloak admin console and then replace the realm name in the URI (for example like `http://localhost:8080/auth/realms/foo/example/companies` ) you will see
+that companies are scoped per-realm. So you will see different companies for realm `master` and for realm `foo` .
+
+
+Testing with authenticated access
+---------------------------------
+Example contains the endpoint, which is accessible just for authenticated users. REST request must be authenticated with bearer access token
+of authenticated user and the user must be in realm role `admin` in order to access the resource. You can run bash script from the current directory:
+````
+./invoke-authenticated.sh
+````
+The script assumes user `admin` with password `admin` exists in realm `master`. Also it assumes that you have `curl` installed.
\ No newline at end of file
diff --git a/examples/providers/domain-extension/src/main/java/org/keycloak/examples/domainextension/rest/CompanyResource.java b/examples/providers/domain-extension/src/main/java/org/keycloak/examples/domainextension/rest/CompanyResource.java
index b3dab2b..8236264 100644
--- a/examples/providers/domain-extension/src/main/java/org/keycloak/examples/domainextension/rest/CompanyResource.java
+++ b/examples/providers/domain-extension/src/main/java/org/keycloak/examples/domainextension/rest/CompanyResource.java
@@ -1,21 +1,24 @@
package org.keycloak.examples.domainextension.rest;
-import java.util.HashSet;
import java.util.List;
-import java.util.Set;
+import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
+import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
-import org.keycloak.examples.domainextension.entities.Company;
-import org.keycloak.examples.domainextension.rest.model.CompanyView;
-import org.keycloak.examples.domainextension.services.ExampleService;
+import org.jboss.resteasy.annotations.cache.NoCache;
+import org.keycloak.examples.domainextension.CompanyRepresentation;
+import org.keycloak.examples.domainextension.spi.ExampleService;
import org.keycloak.models.KeycloakSession;
public class CompanyResource {
- private KeycloakSession session;
+ private final KeycloakSession session;
public CompanyResource(KeycloakSession session) {
this.session = session;
@@ -23,19 +26,27 @@ public class CompanyResource {
@GET
@Path("")
- public Set<CompanyView> getMasterAccounts() {
- List<Company> companies = session.getProvider(ExampleService.class).listCompanies();
- Set<CompanyView> companyViews = new HashSet<>();
- for (Company company : companies) {
- companyViews.add(new CompanyView(company));
- }
- return companyViews;
+ @NoCache
+ @Produces(MediaType.APPLICATION_JSON)
+ public List<CompanyRepresentation> getCompanies() {
+ return session.getProvider(ExampleService.class).listCompanies();
+ }
+
+ @POST
+ @Path("")
+ @NoCache
+ @Consumes(MediaType.APPLICATION_JSON)
+ public Response createProviderInstance(CompanyRepresentation rep) {
+ session.getProvider(ExampleService.class).addCompany(rep);
+ return Response.created(session.getContext().getUri().getAbsolutePathBuilder().path(rep.getId()).build()).build();
}
@GET
+ @NoCache
@Path("{id}")
- public CompanyView getCompany(@PathParam("id") final String id) {
- return new CompanyView(session.getProvider(ExampleService.class).findCompany(id));
+ @Produces(MediaType.APPLICATION_JSON)
+ public CompanyRepresentation getCompany(@PathParam("id") final String id) {
+ return session.getProvider(ExampleService.class).findCompany(id);
}
}
\ No newline at end of file
diff --git a/examples/providers/domain-extension/src/main/java/org/keycloak/examples/domainextension/rest/ExampleRestResource.java b/examples/providers/domain-extension/src/main/java/org/keycloak/examples/domainextension/rest/ExampleRestResource.java
index 4309810..db774cf 100644
--- a/examples/providers/domain-extension/src/main/java/org/keycloak/examples/domainextension/rest/ExampleRestResource.java
+++ b/examples/providers/domain-extension/src/main/java/org/keycloak/examples/domainextension/rest/ExampleRestResource.java
@@ -1,15 +1,22 @@
package org.keycloak.examples.domainextension.rest;
+import javax.ws.rs.ForbiddenException;
+import javax.ws.rs.NotAuthorizedException;
import javax.ws.rs.Path;
+
import org.keycloak.models.KeycloakSession;
+import org.keycloak.services.managers.AppAuthManager;
+import org.keycloak.services.managers.AuthenticationManager;
public class ExampleRestResource {
- private KeycloakSession session;
+ private final KeycloakSession session;
+ private final AuthenticationManager.AuthResult auth;
public ExampleRestResource(KeycloakSession session) {
this.session = session;
+ this.auth = new AppAuthManager().authenticateBearerToken(session, session.getContext().getRealm());
}
@Path("companies")
@@ -17,4 +24,20 @@ public class ExampleRestResource {
return new CompanyResource(session);
}
+ // Same like "companies" endpoint, but REST endpoint is authenticated with Bearer token and user must be in realm role "admin"
+ // Just for illustration purposes
+ @Path("companies-auth")
+ public CompanyResource getCompanyResourceAuthenticated() {
+ checkRealmAdmin();
+ return new CompanyResource(session);
+ }
+
+ private void checkRealmAdmin() {
+ if (auth == null) {
+ throw new NotAuthorizedException("Bearer");
+ } else if (auth.getToken().getRealmAccess() == null || !auth.getToken().getRealmAccess().isUserInRole("admin")) {
+ throw new ForbiddenException("Does not have realm admin role");
+ }
+ }
+
}
diff --git a/examples/providers/domain-extension/src/main/resources/META-INF/example-changelog.xml b/examples/providers/domain-extension/src/main/resources/META-INF/example-changelog.xml
index 3d96bdb..ec4e5a1 100644
--- a/examples/providers/domain-extension/src/main/resources/META-INF/example-changelog.xml
+++ b/examples/providers/domain-extension/src/main/resources/META-INF/example-changelog.xml
@@ -16,7 +16,7 @@
<addPrimaryKey
constraintName="PK_COMPANY"
- tableName="DOCDATA_COMPANY"
+ tableName="EXAMPLE_COMPANY"
columnNames="ID"
/>
@@ -27,117 +27,7 @@
referencedTableName="REALM"
referencedColumnNames="ID"
/>
-
- <createTable tableName="EXAMPLE_REGION">
- <column name="ID" type="VARCHAR(36)">
- <constraints nullable="false"/>
- </column>
- <column name="NAME" type="VARCHAR(255)">
- <constraints nullable="false"/>
- </column>
- <column name="COMPANY_ID" type="VARCHAR(36)">
- <constraints nullable="false"/>
- </column>
- </createTable>
-
- <addPrimaryKey
- constraintName="PK_REGION"
- tableName="EXAMPLE_REGION"
- columnNames="ID"
- />
-
- <addForeignKeyConstraint
- constraintName="FK_REGION_COMPANY"
- baseTableName="EXAMPLE_REGION"
- baseColumnNames="COMPANY_ID"
- referencedTableName="EXAMPLE_COMPANY"
- referencedColumnNames="ID"
- />
-
- <createTable tableName="EXAMPLE_USER_ACCOUNT">
- <column name="ID" type="VARCHAR(36)">
- <constraints nullable="false"/>
- </column>
- <column name="USER_ID" type="VARCHAR(36)">
- <constraints nullable="false"/>
- </column>
- <column name="COMPANY_ID" type="VARCHAR(36)">
- <constraints nullable="false"/>
- </column>
- </createTable>
-
- <addPrimaryKey
- constraintName="PK_USER_ACCOUNT"
- tableName="EXAMPLE_USER_ACCOUNT"
- columnNames="ID"
- />
-
- <addUniqueConstraint
- constraintName="UC_USER_ACCOUNT_USER_ID"
- tableName="EXAMPLE_USER_ACCOUNT"
- columnNames="USER_ID"
- />
-
- <addForeignKeyConstraint
- constraintName="FK_USER_ACCOUNT_USER_ENTITY"
- baseTableName="EXAMPLE_USER_ACCOUNT"
- baseColumnNames="USER_ID"
- referencedTableName="USER_ENTITY"
- referencedColumnNames="ID"
- />
-
- <addForeignKeyConstraint
- constraintName="FK_USER_ACCOUNT_COMPANY"
- baseTableName="EXAMPLE_USER_ACCOUNT"
- baseColumnNames="COMPANY_ID"
- referencedTableName="EXAMPLE_COMPANY"
- referencedColumnNames="ID"
- />
-
- <createTable tableName="EXAMPLE_USER_ACCOUNT_REGION_ROLE">
- <column name="ID" type="VARCHAR(36)">
- <constraints nullable="false" />
- </column>
- <column name="USER_ACCOUNT_ID" type="VARCHAR(36)">
- <constraints nullable="false" />
- </column>
- <column name="REGION_ID" type="VARCHAR(36)">
- <constraints nullable="false" />
- </column>
- <column name="ROLE_ID" type="VARCHAR(36)">
- <constraints nullable="false" />
- </column>
- </createTable>
- <addPrimaryKey
- constraintName="PK_USER_ACCOUNT_REGION_ROLE"
- tableName="EXAMPLE_USER_ACCOUNT_REGION_ROLE"
- columnNames="ID"
- />
-
- <addForeignKeyConstraint
- constraintName="FK_USER_ACCOUNT_REGION_ROLE_USER_ACCOUNT"
- baseTableName="EXAMPLE_USER_ACCOUNT_REGION_ROLE"
- baseColumnNames="USER_ACCOUNT_ID"
- referencedTableName="EXAMPLE_USER_ACCOUNT"
- referencedColumnNames="ID"
- />
-
- <addForeignKeyConstraint
- constraintName="FK_USER_ACCOUNT_REGION_ROLE_REGION"
- baseTableName="EXAMPLE_USER_ACCOUNT_REGION_ROLE"
- baseColumnNames="REGION_ID"
- referencedTableName="EXAMPLE_REGION"
- referencedColumnNames="ID"
- />
-
- <addForeignKeyConstraint
- constraintName="FK_USER_ACCOUNT_REGION_ROLE_ROLE"
- baseTableName="EXAMPLE_USER_ACCOUNT_REGION_ROLE"
- baseColumnNames="ROLE_ID"
- referencedTableName="KEYCLOAK_ROLE"
- referencedColumnNames="ID"
- />
</changeSet>
</databaseChangeLog>
diff --git a/examples/providers/domain-extension/src/main/resources/META-INF/services/org.keycloak.connections.jpa.entityprovider.JpaEntityProviderFactory b/examples/providers/domain-extension/src/main/resources/META-INF/services/org.keycloak.connections.jpa.entityprovider.JpaEntityProviderFactory
index 19058b3..c7b88db 100644
--- a/examples/providers/domain-extension/src/main/resources/META-INF/services/org.keycloak.connections.jpa.entityprovider.JpaEntityProviderFactory
+++ b/examples/providers/domain-extension/src/main/resources/META-INF/services/org.keycloak.connections.jpa.entityprovider.JpaEntityProviderFactory
@@ -15,4 +15,4 @@
# limitations under the License.
#
-org.keycloak.examples.domainextension.DomainExtensionProviderFactory
+org.keycloak.examples.domainextension.jpa.ExampleJpaEntityProviderFactory
diff --git a/examples/providers/domain-extension/src/main/resources/META-INF/services/org.keycloak.provider.Spi b/examples/providers/domain-extension/src/main/resources/META-INF/services/org.keycloak.provider.Spi
index 47b695e..e013bbd 100644
--- a/examples/providers/domain-extension/src/main/resources/META-INF/services/org.keycloak.provider.Spi
+++ b/examples/providers/domain-extension/src/main/resources/META-INF/services/org.keycloak.provider.Spi
@@ -15,4 +15,4 @@
# limitations under the License.
#
-org.keycloak.examples.domainextension.services.spi.ExampleSpi
+org.keycloak.examples.domainextension.spi.ExampleSpi
diff --git a/examples/providers/domain-extension/src/main/resources/META-INF/services/org.keycloak.services.resource.RealmResourceProviderFactory b/examples/providers/domain-extension/src/main/resources/META-INF/services/org.keycloak.services.resource.RealmResourceProviderFactory
index f46b8a6..ea81617 100644
--- a/examples/providers/domain-extension/src/main/resources/META-INF/services/org.keycloak.services.resource.RealmResourceProviderFactory
+++ b/examples/providers/domain-extension/src/main/resources/META-INF/services/org.keycloak.services.resource.RealmResourceProviderFactory
@@ -15,4 +15,4 @@
# limitations under the License.
#
-org.keycloak.examples.domainextension.rest.ExampleResourceProviderFactory
\ No newline at end of file
+org.keycloak.examples.domainextension.rest.ExampleRealmResourceProviderFactory
\ No newline at end of file
model/jpa/pom.xml 5(+5 -0)
diff --git a/model/jpa/pom.xml b/model/jpa/pom.xml
index 47035c5..e1c8742 100755
--- a/model/jpa/pom.xml
+++ b/model/jpa/pom.xml
@@ -102,6 +102,11 @@
<artifactId>jackson-core</artifactId>
<scope>provided</scope>
</dependency>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>test</scope>
+ </dependency>
</dependencies>
<build>
<plugins>
diff --git a/model/jpa/src/main/java/org/keycloak/connections/jpa/entityprovider/JpaEntityProvider.java b/model/jpa/src/main/java/org/keycloak/connections/jpa/entityprovider/JpaEntityProvider.java
index c64c965..567080e 100644
--- a/model/jpa/src/main/java/org/keycloak/connections/jpa/entityprovider/JpaEntityProvider.java
+++ b/model/jpa/src/main/java/org/keycloak/connections/jpa/entityprovider/JpaEntityProvider.java
@@ -44,4 +44,10 @@ public interface JpaEntityProvider extends Provider {
*/
String getChangelogLocation();
+ /**
+ * Return the ID of provider factory, which created this provider. Might be used to "compute" the table name of liquibase changelog table.
+ * @return ID of provider factory
+ */
+ String getFactoryId();
+
}
diff --git a/model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/conn/DefaultLiquibaseConnectionProvider.java b/model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/conn/DefaultLiquibaseConnectionProvider.java
index ca6d722..5a58702 100644
--- a/model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/conn/DefaultLiquibaseConnectionProvider.java
+++ b/model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/conn/DefaultLiquibaseConnectionProvider.java
@@ -18,25 +18,18 @@
package org.keycloak.connections.jpa.updater.liquibase.conn;
import java.sql.Connection;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Set;
import org.jboss.logging.Logger;
import org.keycloak.Config;
-import org.keycloak.connections.jpa.entityprovider.JpaEntityProvider;
-import org.keycloak.connections.jpa.entityprovider.ProxyClassLoader;
import org.keycloak.connections.jpa.updater.liquibase.LiquibaseJpaUpdaterProvider;
import org.keycloak.connections.jpa.updater.liquibase.PostgresPlusDatabase;
import org.keycloak.connections.jpa.updater.liquibase.lock.CustomInsertLockRecordGenerator;
import org.keycloak.connections.jpa.updater.liquibase.lock.CustomLockDatabaseChangeLogGenerator;
import org.keycloak.connections.jpa.updater.liquibase.lock.DummyLockService;
-import org.keycloak.connections.jpa.util.JpaUtils;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import liquibase.Liquibase;
-import liquibase.changelog.ChangeLogParameters;
import liquibase.changelog.ChangeSet;
import liquibase.changelog.DatabaseChangeLog;
import liquibase.database.Database;
@@ -46,8 +39,6 @@ import liquibase.database.jvm.JdbcConnection;
import liquibase.exception.LiquibaseException;
import liquibase.logging.LogFactory;
import liquibase.logging.LogLevel;
-import liquibase.parser.ChangeLogParser;
-import liquibase.parser.ChangeLogParserFactory;
import liquibase.resource.ClassLoaderResourceAccessor;
import liquibase.resource.ResourceAccessor;
import liquibase.servicelocator.ServiceLocator;
@@ -61,12 +52,9 @@ public class DefaultLiquibaseConnectionProvider implements LiquibaseConnectionPr
private static final Logger logger = Logger.getLogger(DefaultLiquibaseConnectionProvider.class);
private volatile boolean initialized = false;
-
- private KeycloakSession keycloakSession;
@Override
public LiquibaseConnectionProvider create(KeycloakSession session) {
- this.keycloakSession = session;
if (!initialized) {
synchronized (this) {
if (!initialized) {
@@ -143,63 +131,26 @@ public class DefaultLiquibaseConnectionProvider implements LiquibaseConnectionPr
}
String changelog = (database instanceof DB2Database) ? LiquibaseJpaUpdaterProvider.DB2_CHANGELOG : LiquibaseJpaUpdaterProvider.CHANGELOG;
- logger.debugf("Using changelog file: %s", changelog);
-
ResourceAccessor resourceAccessor = new ClassLoaderResourceAccessor(getClass().getClassLoader());
- DatabaseChangeLog databaseChangeLog = generateDynamicChangeLog(changelog, resourceAccessor, database);
+
+ logger.debugf("Using changelog file %s and changelogTableName %s", changelog, database.getDatabaseChangeLogTableName());
- return new Liquibase(databaseChangeLog, resourceAccessor, database);
+ return new Liquibase(changelog, resourceAccessor, database);
}
- /**
- * We want to be able to provide extra changesets as an extension to the Keycloak data model.
- * But we do not want users to be able to not execute certain parts of the Keycloak internal data model.
- * Therefore, we generate a dynamic changelog here that always contains the keycloak changelog file
- * and optionally include the user extension changelog files.
- *
- * @param changelog the changelog file location
- * @param resourceAccessor the resource accessor
- * @param database the database
- * @return
- */
- private DatabaseChangeLog generateDynamicChangeLog(String changelog, ResourceAccessor resourceAccessor, Database database) throws LiquibaseException {
- ChangeLogParameters changeLogParameters = new ChangeLogParameters(database);
- ChangeLogParser parser = ChangeLogParserFactory.getInstance().getParser(changelog, resourceAccessor);
- DatabaseChangeLog keycloakDatabaseChangeLog = parser.parse(changelog, changeLogParameters, resourceAccessor);
-
- List<String> locations = new ArrayList<>();
- Set<JpaEntityProvider> entityProviders = keycloakSession.getAllProviders(JpaEntityProvider.class);
- for (JpaEntityProvider entityProvider : entityProviders) {
- String location = entityProvider.getChangelogLocation();
- if (location != null) {
- locations.add(location);
- }
- }
-
- final DatabaseChangeLog dynamicMasterChangeLog;
- if (locations.isEmpty()) {
- // If there are no extra changelog locations, we'll just use the keycloak one.
- dynamicMasterChangeLog = keycloakDatabaseChangeLog;
- } else {
- // A change log is essentially not much more than a (big) collection of changesets.
- // The original (file) destination is not important. So we can just make one big dynamic change log that include all changesets.
- dynamicMasterChangeLog = new DatabaseChangeLog();
- dynamicMasterChangeLog.setChangeLogParameters(changeLogParameters);
- for (ChangeSet changeSet : keycloakDatabaseChangeLog.getChangeSets()) {
- dynamicMasterChangeLog.addChangeSet(changeSet);
- }
- ProxyClassLoader proxyClassLoader = new ProxyClassLoader(JpaUtils.getProvidedEntities(keycloakSession));
- for (String location : locations) {
- ResourceAccessor proxyResourceAccessor = new ClassLoaderResourceAccessor(proxyClassLoader);
- ChangeLogParser locationParser = ChangeLogParserFactory.getInstance().getParser(location, proxyResourceAccessor);
- DatabaseChangeLog locationDatabaseChangeLog = locationParser.parse(location, changeLogParameters, proxyResourceAccessor);
- for (ChangeSet changeSet : locationDatabaseChangeLog.getChangeSets()) {
- dynamicMasterChangeLog.addChangeSet(changeSet);
- }
- }
+ @Override
+ public Liquibase getLiquibaseForCustomUpdate(Connection connection, String defaultSchema, String changelogLocation, ClassLoader classloader, String changelogTableName) throws LiquibaseException {
+ Database database = DatabaseFactory.getInstance().findCorrectDatabaseImplementation(new JdbcConnection(connection));
+ if (defaultSchema != null) {
+ database.setDefaultSchemaName(defaultSchema);
}
-
- return dynamicMasterChangeLog;
+
+ ResourceAccessor resourceAccessor = new ClassLoaderResourceAccessor(classloader);
+ database.setDatabaseChangeLogTableName(changelogTableName);
+
+ logger.debugf("Using changelog file %s and changelogTableName %s", changelogLocation, database.getDatabaseChangeLogTableName());
+
+ return new Liquibase(changelogLocation, resourceAccessor, database);
}
private static class LogWrapper extends LogFactory {
diff --git a/model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/conn/LiquibaseConnectionProvider.java b/model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/conn/LiquibaseConnectionProvider.java
index 5aa81cc..215bd1d 100644
--- a/model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/conn/LiquibaseConnectionProvider.java
+++ b/model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/conn/LiquibaseConnectionProvider.java
@@ -30,4 +30,6 @@ public interface LiquibaseConnectionProvider extends Provider {
Liquibase getLiquibase(Connection connection, String defaultSchema) throws LiquibaseException;
+ Liquibase getLiquibaseForCustomUpdate(Connection connection, String defaultSchema, String changelogLocation, ClassLoader classloader, String changelogTableName) throws LiquibaseException;
+
}
diff --git a/model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/LiquibaseJpaUpdaterProvider.java b/model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/LiquibaseJpaUpdaterProvider.java
index 8e9d253..2610174 100755
--- a/model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/LiquibaseJpaUpdaterProvider.java
+++ b/model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/LiquibaseJpaUpdaterProvider.java
@@ -23,12 +23,17 @@ import liquibase.changelog.ChangeSet;
import liquibase.changelog.RanChangeSet;
import liquibase.exception.LiquibaseException;
import org.jboss.logging.Logger;
+import org.keycloak.common.util.reflections.Reflections;
+import org.keycloak.connections.jpa.entityprovider.JpaEntityProvider;
import org.keycloak.connections.jpa.updater.JpaUpdaterProvider;
import org.keycloak.connections.jpa.updater.liquibase.conn.LiquibaseConnectionProvider;
+import org.keycloak.connections.jpa.util.JpaUtils;
import org.keycloak.models.KeycloakSession;
+import java.lang.reflect.Method;
import java.sql.Connection;
import java.util.List;
+import java.util.Set;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
@@ -54,25 +59,20 @@ public class LiquibaseJpaUpdaterProvider implements JpaUpdaterProvider {
ThreadLocalSessionContext.setCurrentSession(session);
try {
- Liquibase liquibase = getLiquibase(connection, defaultSchema);
-
- List<ChangeSet> changeSets = liquibase.listUnrunChangeSets((Contexts) null);
- if (!changeSets.isEmpty()) {
- if (changeSets.get(0).getId().equals(FIRST_VERSION)) {
- 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");
- }
+ // Run update with keycloak master changelog first
+ Liquibase liquibase = getLiquibaseForKeycloakUpdate(connection, defaultSchema);
+ updateChangeSet(liquibase, liquibase.getChangeLogFile());
+
+ // Run update for each custom JpaEntityProvider
+ Set<JpaEntityProvider> jpaProviders = session.getAllProviders(JpaEntityProvider.class);
+ for (JpaEntityProvider jpaProvider : jpaProviders) {
+ String customChangelog = jpaProvider.getChangelogLocation();
+ if (customChangelog != null) {
+ String factoryId = jpaProvider.getFactoryId();
+ String changelogTableName = JpaUtils.getCustomChangelogTableName(factoryId);
+ liquibase = getLiquibaseForCustomProviderUpdate(connection, defaultSchema, customChangelog, jpaProvider.getClass().getClassLoader(), changelogTableName);
+ updateChangeSet(liquibase, liquibase.getChangeLogFile());
}
-
- liquibase.update((Contexts) null);
- logger.debug("Completed database update");
- } else {
- logger.debug("Database is up to date");
}
} catch (Exception e) {
throw new RuntimeException("Failed to update database", e);
@@ -81,21 +81,50 @@ public class LiquibaseJpaUpdaterProvider implements JpaUpdaterProvider {
}
}
+ protected void updateChangeSet(Liquibase liquibase, String changelog) throws LiquibaseException {
+ List<ChangeSet> changeSets = liquibase.listUnrunChangeSets((Contexts) null);
+ if (!changeSets.isEmpty()) {
+ List<RanChangeSet> ranChangeSets = liquibase.getDatabase().getRanChangeSetList();
+ if (ranChangeSets.isEmpty()) {
+ logger.infov("Initializing database schema. Using changelog {0}", changelog);
+ } else {
+ if (logger.isDebugEnabled()) {
+ logger.debugv("Updating database from {0} to {1}. Using changelog {2}", ranChangeSets.get(ranChangeSets.size() - 1).getId(), changeSets.get(changeSets.size() - 1).getId(), changelog);
+ } else {
+ logger.infov("Updating database. Using changelog {0}", changelog);
+ }
+ }
+
+ liquibase.update((Contexts) null);
+ logger.debugv("Completed database update for changelog {0}", changelog);
+ } else {
+ logger.debugv("Database is up to date for changelog {0}", changelog);
+
+ // Needs to restart liquibase services to clear changeLogHistory.
+ Method resetServices = Reflections.findDeclaredMethod(Liquibase.class, "resetServices");
+ Reflections.invokeMethod(true, resetServices, liquibase);
+ }
+ }
+
@Override
public void validate(Connection connection, String defaultSchema) {
logger.debug("Validating if database is updated");
try {
- Liquibase liquibase = getLiquibase(connection, defaultSchema);
-
- List<ChangeSet> changeSets = liquibase.listUnrunChangeSets((Contexts) null);
- if (!changeSets.isEmpty()) {
- List<RanChangeSet> ranChangeSets = liquibase.getDatabase().getRanChangeSetList();
- String errorMessage = String.format("Failed to validate database schema. Schema needs updating database from %s to %s. Please change databaseSchema to 'update' or use other database",
- ranChangeSets.get(ranChangeSets.size() - 1).getId(), changeSets.get(changeSets.size() - 1).getId());
- throw new RuntimeException(errorMessage);
- } else {
- logger.debug("Validation passed. Database is up-to-date");
+ // Validate with keycloak master changelog first
+ Liquibase liquibase = getLiquibaseForKeycloakUpdate(connection, defaultSchema);
+ validateChangeSet(liquibase, liquibase.getChangeLogFile());
+
+ // Validate each custom JpaEntityProvider
+ Set<JpaEntityProvider> jpaProviders = session.getAllProviders(JpaEntityProvider.class);
+ for (JpaEntityProvider jpaProvider : jpaProviders) {
+ String customChangelog = jpaProvider.getChangelogLocation();
+ if (customChangelog != null) {
+ String factoryId = jpaProvider.getFactoryId();
+ String changelogTableName = JpaUtils.getCustomChangelogTableName(factoryId);
+ liquibase = getLiquibaseForCustomProviderUpdate(connection, defaultSchema, customChangelog, jpaProvider.getClass().getClassLoader(), changelogTableName);
+ validateChangeSet(liquibase, liquibase.getChangeLogFile());
+ }
}
} catch (LiquibaseException e) {
@@ -103,11 +132,28 @@ public class LiquibaseJpaUpdaterProvider implements JpaUpdaterProvider {
}
}
- private Liquibase getLiquibase(Connection connection, String defaultSchema) throws LiquibaseException {
+ protected void validateChangeSet(Liquibase liquibase, String changelog) throws LiquibaseException {
+ List<ChangeSet> changeSets = liquibase.listUnrunChangeSets((Contexts) null);
+ if (!changeSets.isEmpty()) {
+ List<RanChangeSet> ranChangeSets = liquibase.getDatabase().getRanChangeSetList();
+ String errorMessage = String.format("Failed to validate database schema. Schema needs updating database from %s to %s. Please change databaseSchema to 'update' or use other database. Used changelog was %s",
+ ranChangeSets.get(ranChangeSets.size() - 1).getId(), changeSets.get(changeSets.size() - 1).getId(), changelog);
+ throw new RuntimeException(errorMessage);
+ } else {
+ logger.debugf("Validation passed. Database is up-to-date for changelog %s", changelog);
+ }
+ }
+
+ private Liquibase getLiquibaseForKeycloakUpdate(Connection connection, String defaultSchema) throws LiquibaseException {
LiquibaseConnectionProvider liquibaseProvider = session.getProvider(LiquibaseConnectionProvider.class);
return liquibaseProvider.getLiquibase(connection, defaultSchema);
}
+ private Liquibase getLiquibaseForCustomProviderUpdate(Connection connection, String defaultSchema, String changelogLocation, ClassLoader classloader, String changelogTableName) throws LiquibaseException {
+ LiquibaseConnectionProvider liquibaseProvider = session.getProvider(LiquibaseConnectionProvider.class);
+ return liquibaseProvider.getLiquibaseForCustomUpdate(connection, defaultSchema, changelogLocation, classloader, changelogTableName);
+ }
+
@Override
public void close() {
}
diff --git a/model/jpa/src/main/java/org/keycloak/connections/jpa/util/JpaUtils.java b/model/jpa/src/main/java/org/keycloak/connections/jpa/util/JpaUtils.java
index d93c02b..5ac7d2f 100644
--- a/model/jpa/src/main/java/org/keycloak/connections/jpa/util/JpaUtils.java
+++ b/model/jpa/src/main/java/org/keycloak/connections/jpa/util/JpaUtils.java
@@ -82,4 +82,16 @@ public class JpaUtils {
return providedEntityClasses;
}
+ /**
+ * Get the name of custom table for liquibase updates for give ID of JpaEntityProvider
+ * @param jpaEntityProviderFactoryId
+ * @return table name
+ */
+ public static String getCustomChangelogTableName(String jpaEntityProviderFactoryId) {
+ String upperCased = jpaEntityProviderFactoryId.toUpperCase();
+ upperCased = upperCased.replaceAll("-", "_");
+ upperCased = upperCased.replaceAll("[^A-Z_]", "");
+ return "DATABASECHANGELOG_" + upperCased.substring(0, Math.min(10, upperCased.length()));
+ }
+
}
testsuite/integration/pom.xml 4(+4 -0)
diff --git a/testsuite/integration/pom.xml b/testsuite/integration/pom.xml
index d394e2a..c1d0412 100755
--- a/testsuite/integration/pom.xml
+++ b/testsuite/integration/pom.xml
@@ -140,6 +140,10 @@
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
+ <artifactId>keycloak-authz-client</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
<artifactId>keycloak-saml-servlet-filter-adapter</artifactId>
</dependency>
<dependency>