From 07edd5f230ce6a04a93875d99835192d1a69ae46 Mon Sep 17 00:00:00 2001 From: Pedro Igor Date: Tue, 8 Mar 2022 10:50:22 -0300 Subject: [PATCH] Update user-storage-jpa to work with the new distribution Closes #303 --- pom.xml | 1 + scripts/run-tests.sh | 2 +- user-storage-jpa-legacy/README.md | 81 +++++ user-storage-jpa-legacy/pom.xml | 200 ++++++++++++ .../user/EjbExampleUserStorageProvider.java | 0 .../EjbExampleUserStorageProviderFactory.java | 0 .../quickstart/storage/user/UserAdapter.java | 135 ++++++++ .../quickstart/storage/user/UserEntity.java | 86 +++++ .../main/resources/META-INF/persistence.xml | 17 + ...eycloak.storage.UserStorageProviderFactory | 1 + .../quickstart/ArquillianJpaStorageTest.java | 0 .../src/test/resources/arquillian.xml | 0 .../src/test/resources/quickstart-realm.json | 0 user-storage-jpa/README.md | 53 +--- user-storage-jpa/conf/quarkus.properties | 3 + user-storage-jpa/pom.xml | 115 +------ .../MyExampleUserStorageProviderFactory.java | 53 ++++ .../storage/user/MyUserStorageProvider.java | 297 ++++++++++++++++++ .../src/main/resources/META-INF/beans.xml | 0 .../main/resources/META-INF/persistence.xml | 9 +- ...eycloak.storage.UserStorageProviderFactory | 2 +- 21 files changed, 903 insertions(+), 152 deletions(-) create mode 100755 user-storage-jpa-legacy/README.md create mode 100755 user-storage-jpa-legacy/pom.xml rename {user-storage-jpa => user-storage-jpa-legacy}/src/main/java/org/keycloak/quickstart/storage/user/EjbExampleUserStorageProvider.java (100%) rename {user-storage-jpa => user-storage-jpa-legacy}/src/main/java/org/keycloak/quickstart/storage/user/EjbExampleUserStorageProviderFactory.java (100%) create mode 100644 user-storage-jpa-legacy/src/main/java/org/keycloak/quickstart/storage/user/UserAdapter.java create mode 100644 user-storage-jpa-legacy/src/main/java/org/keycloak/quickstart/storage/user/UserEntity.java create mode 100644 user-storage-jpa-legacy/src/main/resources/META-INF/persistence.xml create mode 100644 user-storage-jpa-legacy/src/main/resources/META-INF/services/org.keycloak.storage.UserStorageProviderFactory rename {user-storage-jpa => user-storage-jpa-legacy}/src/test/java/org/keycloak/quickstart/ArquillianJpaStorageTest.java (100%) rename {user-storage-jpa => user-storage-jpa-legacy}/src/test/resources/arquillian.xml (100%) rename {user-storage-jpa => user-storage-jpa-legacy}/src/test/resources/quickstart-realm.json (100%) create mode 100644 user-storage-jpa/conf/quarkus.properties create mode 100644 user-storage-jpa/src/main/java/org/keycloak/quickstart/storage/user/MyExampleUserStorageProviderFactory.java create mode 100644 user-storage-jpa/src/main/java/org/keycloak/quickstart/storage/user/MyUserStorageProvider.java create mode 100644 user-storage-jpa/src/main/resources/META-INF/beans.xml diff --git a/pom.xml b/pom.xml index 3d83877d4..2935ea6c1 100644 --- a/pom.xml +++ b/pom.xml @@ -328,6 +328,7 @@ fuse70 user-storage-simple user-storage-jpa + user-storage-jpa-legacy event-listener-sysout event-store-mem extend-account-console diff --git a/scripts/run-tests.sh b/scripts/run-tests.sh index aae482297..6e1afdc45 100755 --- a/scripts/run-tests.sh +++ b/scripts/run-tests.sh @@ -85,7 +85,7 @@ run_tests fuse63 # no tests but at least let's try to compile it run_tests fuse70 # no tests but at least let's try to compile it run_tests service-jee-jaxrs -Pwildfly-managed run_tests service-springboot-rest -Pspring-boot -run_tests user-storage-jpa -Pkeycloak-remote +run_tests user-storage-jpa-legacy -Pkeycloak-remote run_tests user-storage-simple -Pkeycloak-remote mvn -f service-springboot-rest spring-boot:run >/dev/null& diff --git a/user-storage-jpa-legacy/README.md b/user-storage-jpa-legacy/README.md new file mode 100755 index 000000000..2f86623a5 --- /dev/null +++ b/user-storage-jpa-legacy/README.md @@ -0,0 +1,81 @@ +user-storage-jpa: User Storage Provider with EJB and JPA +======================================================== + +Level: Beginner +Technologies: JavaEE, EJB, JPA +Summary: User Storage Provider with EJB and JPA +Target Product: Keycloak +Source: + + +What is it? +----------- + +This is an example of the User Storage SPI implemented using EJB and JPA. It shows you how you might use these components +to integrate Keycloak with an existing external custom user database. The example integrates with a simple relational +database schema that has one user table that stores a username, email, phone number, and password for one particular user. +Using the User Storage SPI this table is mapped to the Keycloak user metamodel so that it can be consumed by the Keycloak +runtime. Before using this example, you should probably read the User Storage SPI chapter of our server developer guide. + + +System Requirements +------------------- + +You need to have Keycloak running. + +All you need to build this project is Java 8.0 (Java SDK 1.8) or later and Maven 3.3.3 or later. + + +Build and Deploy the Quickstart +------------------------------- + +You must first deploy the datasource it uses. +Start up the Keycloak server. Then in the directory of this example type the following maven command: + + ```` + mvn -Padd-datasource install + ```` + +**Note**: If the server runs on different port than `10090`, you have to specify it by setting a +maven property `keycloak.management.port`. + + ```` + mvn -Padd-datasource install -Dkeycloak.management.port=9990 + ```` + +You only need to execute this maven command once. If you execute this again, then you will get an error message that the datasource +already exists. + +If you open the pom.xml file you'll see that the add-datasource profile creates an XA datasource using the built +in H2 database that comes with the server. An XA datasource is required because you cannot use two non-xa datasources +in the same transaction. The Keycloak database is non-xa. + +Another thing to note is that the xa-datasource created is in-memory only. If you reboot the server, any users you've +added or changes you've made to users loaded by this provider will be wiped clean. + +To deploy the provider, run the following maven command: + + ```` + mvn clean install wildfly:deploy + ```` + +If you want to play with and modify the example, simply rerun the maven deploy command above and the new version will be hot deployed. + +Enable the Provider for a Realm +------------------------------- +Login to the Keycloak Admin Console and got to the User Federation tab. You should now see your deployed provider in the add-provider list box. +Add the provider, save it. This will now enable the provider for the 'master' realm. Because this provider implements the UserRegistrationProvider interface, any new user you create in the +admin console or on the registration pages of Keycloak, will be created in the custom store used by the provider. If you go +to the Users tab in the Admin Console and create a new user, you'll be able to see the provider in action. + +Integration test of the Quickstart +---------------------------------- + +1. Make sure you have an Keycloak server running with an admin user in the `master` realm or use the provided docker image. +2. You need to have Chrome browser installed and updated to the latest version. +3. Run `mvn test -Pkeycloak-remote`. (The datasource will be deployed automatically.) + +More Information +---------------- +The User Storage SPI and how you can use Java EE to implement it is covered in detail in our server developer guide. + diff --git a/user-storage-jpa-legacy/pom.xml b/user-storage-jpa-legacy/pom.xml new file mode 100755 index 000000000..135adc2af --- /dev/null +++ b/user-storage-jpa-legacy/pom.xml @@ -0,0 +1,200 @@ + + + + + org.keycloak.quickstarts + keycloak-quickstart-parent + 18.0.0-SNAPSHOT + ../pom.xml + + 4.0.0 + keycloak-user-storage-jpa-legacy + jar + Keycloak Quickstart: user-storage-jpa-legacy + User Storage Provider with EJB and JPA + + + + Apache License, Version 2.0 + http://www.apache.org/licenses/LICENSE-2.0 + repo + + + + + 3.5.1 + 1.0.0.Final + 1.0.0.Final + + + + + org.keycloak + keycloak-core + provided + + + org.keycloak + keycloak-server-spi + provided + + + org.jboss.logging + jboss-logging + provided + + + org.hibernate.javax.persistence + hibernate-jpa-2.1-api + ${version.hibernate.javax.persistence} + provided + + + org.jboss.spec.javax.ejb + jboss-ejb-api_3.2_spec + ${version.jboss-ejb-api} + provided + + + org.jboss.arquillian.graphene + graphene-webdriver + ${arquillian-graphene.version} + pom + test + + + org.wildfly.extras.creaper + creaper-core + ${version.creaper} + test + + + com.google.guava + guava + + + + + org.wildfly.core + wildfly-cli + test + ${version.wildfly} + + + + + + user-storage-jpa-example-legacy + + + org.apache.maven.plugins + maven-compiler-plugin + ${version.compiler.maven.plugin} + + 1.8 + 1.8 + + + + org.wildfly.plugins + wildfly-maven-plugin + ${version.wildfly.maven.plugin} + + false + ${keycloak.management.port} + + + + maven-enforcer-plugin + + + enforce-quickstart-realm-file-exist + validate + + + + + org.apache.maven.plugins + maven-surefire-plugin + ${version.surefire.plugin} + + + ${keycloak.management.port} + ${project.build.directory} + + + + + + + + + add-datasource + + + + org.wildfly.plugins + wildfly-maven-plugin + + false + true + + + + add-datasource + install + + add-resource + + + ${keycloak.management.port} + +
subsystem=datasources
+ + +
xa-data-source=java:jboss/datasources/ExampleXADS
+ + java:jboss/datasources/ExampleXADS + true + h2 + + + +
+ xa-datasource-properties=URL +
+ + jdbc:h2:mem:test + +
+
+
+
+
+
+
+
+
+
+
+
+ +
diff --git a/user-storage-jpa/src/main/java/org/keycloak/quickstart/storage/user/EjbExampleUserStorageProvider.java b/user-storage-jpa-legacy/src/main/java/org/keycloak/quickstart/storage/user/EjbExampleUserStorageProvider.java similarity index 100% rename from user-storage-jpa/src/main/java/org/keycloak/quickstart/storage/user/EjbExampleUserStorageProvider.java rename to user-storage-jpa-legacy/src/main/java/org/keycloak/quickstart/storage/user/EjbExampleUserStorageProvider.java diff --git a/user-storage-jpa/src/main/java/org/keycloak/quickstart/storage/user/EjbExampleUserStorageProviderFactory.java b/user-storage-jpa-legacy/src/main/java/org/keycloak/quickstart/storage/user/EjbExampleUserStorageProviderFactory.java similarity index 100% rename from user-storage-jpa/src/main/java/org/keycloak/quickstart/storage/user/EjbExampleUserStorageProviderFactory.java rename to user-storage-jpa-legacy/src/main/java/org/keycloak/quickstart/storage/user/EjbExampleUserStorageProviderFactory.java diff --git a/user-storage-jpa-legacy/src/main/java/org/keycloak/quickstart/storage/user/UserAdapter.java b/user-storage-jpa-legacy/src/main/java/org/keycloak/quickstart/storage/user/UserAdapter.java new file mode 100644 index 000000000..dfc71a067 --- /dev/null +++ b/user-storage-jpa-legacy/src/main/java/org/keycloak/quickstart/storage/user/UserAdapter.java @@ -0,0 +1,135 @@ +/* + * Copyright 2016 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.keycloak.quickstart.storage.user; + +import org.jboss.logging.Logger; +import org.keycloak.common.util.MultivaluedHashMap; +import org.keycloak.component.ComponentModel; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.RealmModel; +import org.keycloak.storage.StorageId; +import org.keycloak.storage.adapter.AbstractUserAdapterFederatedStorage; + +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +public class UserAdapter extends AbstractUserAdapterFederatedStorage { + private static final Logger logger = Logger.getLogger(UserAdapter.class); + protected UserEntity entity; + protected String keycloakId; + + public UserAdapter(KeycloakSession session, RealmModel realm, ComponentModel model, UserEntity entity) { + super(session, realm, model); + this.entity = entity; + keycloakId = StorageId.keycloakId(model, entity.getId()); + } + + public String getPassword() { + return entity.getPassword(); + } + + public void setPassword(String password) { + entity.setPassword(password); + } + + @Override + public String getUsername() { + return entity.getUsername(); + } + + @Override + public void setUsername(String username) { + entity.setUsername(username); + + } + + @Override + public void setEmail(String email) { + entity.setEmail(email); + } + + @Override + public String getEmail() { + return entity.getEmail(); + } + + @Override + public String getId() { + return keycloakId; + } + + @Override + public void setSingleAttribute(String name, String value) { + if (name.equals("phone")) { + entity.setPhone(value); + } else { + super.setSingleAttribute(name, value); + } + } + + @Override + public void removeAttribute(String name) { + if (name.equals("phone")) { + entity.setPhone(null); + } else { + super.removeAttribute(name); + } + } + + @Override + public void setAttribute(String name, List values) { + if (name.equals("phone")) { + entity.setPhone(values.get(0)); + } else { + super.setAttribute(name, values); + } + } + + @Override + public String getFirstAttribute(String name) { + if (name.equals("phone")) { + return entity.getPhone(); + } else { + return super.getFirstAttribute(name); + } + } + + @Override + public Map> getAttributes() { + Map> attrs = super.getAttributes(); + MultivaluedHashMap all = new MultivaluedHashMap<>(); + all.putAll(attrs); + all.add("phone", entity.getPhone()); + return all; + } + + @Override + public List getAttribute(String name) { + if (name.equals("phone")) { + List phone = new LinkedList<>(); + phone.add(entity.getPhone()); + return phone; + } else { + return super.getAttribute(name); + } + } +} diff --git a/user-storage-jpa-legacy/src/main/java/org/keycloak/quickstart/storage/user/UserEntity.java b/user-storage-jpa-legacy/src/main/java/org/keycloak/quickstart/storage/user/UserEntity.java new file mode 100644 index 000000000..969ed0a3f --- /dev/null +++ b/user-storage-jpa-legacy/src/main/java/org/keycloak/quickstart/storage/user/UserEntity.java @@ -0,0 +1,86 @@ +/* + * Copyright 2016 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.keycloak.quickstart.storage.user; + +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.NamedQueries; +import javax.persistence.NamedQuery; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +@NamedQueries({ + @NamedQuery(name="getUserByUsername", query="select u from UserEntity u where u.username = :username"), + @NamedQuery(name="getUserByEmail", query="select u from UserEntity u where u.email = :email"), + @NamedQuery(name="getUserCount", query="select count(u) from UserEntity u"), + @NamedQuery(name="getAllUsers", query="select u from UserEntity u"), + @NamedQuery(name="searchForUser", query="select u from UserEntity u where " + + "( lower(u.username) like :search or u.email like :search ) order by u.username"), +}) +@Entity +public class UserEntity { + @Id + private String id; + + + private String username; + private String email; + private String password; + private String phone; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public String getPhone() { + return phone; + } + + public void setPhone(String phone) { + this.phone = phone; + } +} diff --git a/user-storage-jpa-legacy/src/main/resources/META-INF/persistence.xml b/user-storage-jpa-legacy/src/main/resources/META-INF/persistence.xml new file mode 100644 index 000000000..923ca066a --- /dev/null +++ b/user-storage-jpa-legacy/src/main/resources/META-INF/persistence.xml @@ -0,0 +1,17 @@ + + + + java:jboss/datasources/ExampleXADS + + org.keycloak.quickstart.storage.user.UserEntity + + + + + + + diff --git a/user-storage-jpa-legacy/src/main/resources/META-INF/services/org.keycloak.storage.UserStorageProviderFactory b/user-storage-jpa-legacy/src/main/resources/META-INF/services/org.keycloak.storage.UserStorageProviderFactory new file mode 100644 index 000000000..3cc851963 --- /dev/null +++ b/user-storage-jpa-legacy/src/main/resources/META-INF/services/org.keycloak.storage.UserStorageProviderFactory @@ -0,0 +1 @@ +org.keycloak.quickstart.storage.user.EjbExampleUserStorageProviderFactory \ No newline at end of file diff --git a/user-storage-jpa/src/test/java/org/keycloak/quickstart/ArquillianJpaStorageTest.java b/user-storage-jpa-legacy/src/test/java/org/keycloak/quickstart/ArquillianJpaStorageTest.java similarity index 100% rename from user-storage-jpa/src/test/java/org/keycloak/quickstart/ArquillianJpaStorageTest.java rename to user-storage-jpa-legacy/src/test/java/org/keycloak/quickstart/ArquillianJpaStorageTest.java diff --git a/user-storage-jpa/src/test/resources/arquillian.xml b/user-storage-jpa-legacy/src/test/resources/arquillian.xml similarity index 100% rename from user-storage-jpa/src/test/resources/arquillian.xml rename to user-storage-jpa-legacy/src/test/resources/arquillian.xml diff --git a/user-storage-jpa/src/test/resources/quickstart-realm.json b/user-storage-jpa-legacy/src/test/resources/quickstart-realm.json similarity index 100% rename from user-storage-jpa/src/test/resources/quickstart-realm.json rename to user-storage-jpa-legacy/src/test/resources/quickstart-realm.json diff --git a/user-storage-jpa/README.md b/user-storage-jpa/README.md index 2f86623a5..c382883cf 100755 --- a/user-storage-jpa/README.md +++ b/user-storage-jpa/README.md @@ -2,8 +2,8 @@ user-storage-jpa: User Storage Provider with EJB and JPA ======================================================== Level: Beginner -Technologies: JavaEE, EJB, JPA -Summary: User Storage Provider with EJB and JPA +Technologies: JPA +Summary: User Storage Provider with JPA Target Product: Keycloak Source: @@ -11,7 +11,7 @@ Source: What is it? ----------- -This is an example of the User Storage SPI implemented using EJB and JPA. It shows you how you might use these components +This is an example of the User Storage SPI implemented using JPA. It shows you how you might use these components to integrate Keycloak with an existing external custom user database. The example integrates with a simple relational database schema that has one user table that stores a username, email, phone number, and password for one particular user. Using the User Storage SPI this table is mapped to the Keycloak user metamodel so that it can be consumed by the Keycloak @@ -21,45 +21,27 @@ runtime. Before using this example, you should probably read the User Storage SP System Requirements ------------------- -You need to have Keycloak running. - -All you need to build this project is Java 8.0 (Java SDK 1.8) or later and Maven 3.3.3 or later. - +All you need to build this project is Java 11 (Java SDK 11) or later and Maven 3.3.3 or later. Build and Deploy the Quickstart ------------------------------- -You must first deploy the datasource it uses. -Start up the Keycloak server. Then in the directory of this example type the following maven command: - - ```` - mvn -Padd-datasource install - ```` +You must first configure the datasource it uses. +For that, copy the [conf/quarkus.properties](conf/quarkus.properties) to the `conf` directory of the server distribution. +In this file, you have a named datasource using the same name as the [src/resources/META-INF/persistence.xml](persistence unit) from the quickstart. By using the same name, +you make sure the persistence unit will be using the correct datasource. -**Note**: If the server runs on different port than `10090`, you have to specify it by setting a -maven property `keycloak.management.port`. +To build the provider, run the following maven command: ```` - mvn -Padd-datasource install -Dkeycloak.management.port=9990 + mvn clean install ```` -You only need to execute this maven command once. If you execute this again, then you will get an error message that the datasource -already exists. - -If you open the pom.xml file you'll see that the add-datasource profile creates an XA datasource using the built -in H2 database that comes with the server. An XA datasource is required because you cannot use two non-xa datasources -in the same transaction. The Keycloak database is non-xa. +To install the provider, copy the [target/user-storage-jpa-example.jar] JAR file to the `providers` directory of the server distribution. -Another thing to note is that the xa-datasource created is in-memory only. If you reboot the server, any users you've -added or changes you've made to users loaded by this provider will be wiped clean. +Finally, start the server as follows: -To deploy the provider, run the following maven command: - - ```` - mvn clean install wildfly:deploy - ```` - -If you want to play with and modify the example, simply rerun the maven deploy command above and the new version will be hot deployed. + kc.[sh|bat] start-dev Enable the Provider for a Realm ------------------------------- @@ -68,14 +50,7 @@ Add the provider, save it. This will now enable the provider for the 'master' r admin console or on the registration pages of Keycloak, will be created in the custom store used by the provider. If you go to the Users tab in the Admin Console and create a new user, you'll be able to see the provider in action. -Integration test of the Quickstart ----------------------------------- - -1. Make sure you have an Keycloak server running with an admin user in the `master` realm or use the provided docker image. -2. You need to have Chrome browser installed and updated to the latest version. -3. Run `mvn test -Pkeycloak-remote`. (The datasource will be deployed automatically.) - More Information ---------------- -The User Storage SPI and how you can use Java EE to implement it is covered in detail in our server developer guide. +The User Storage SPI and how you can use it is covered in detail in our server developer guide. diff --git a/user-storage-jpa/conf/quarkus.properties b/user-storage-jpa/conf/quarkus.properties new file mode 100644 index 000000000..44c30d9bf --- /dev/null +++ b/user-storage-jpa/conf/quarkus.properties @@ -0,0 +1,3 @@ +quarkus.datasource.user-store.db-kind=h2 +quarkus.datasource.user-store.username=sa +quarkus.datasource.user-store.jdbc.url=jdbc:h2:mem:user-store;DB_CLOSE_DELAY=-1 \ No newline at end of file diff --git a/user-storage-jpa/pom.xml b/user-storage-jpa/pom.xml index c13dbe7ac..cd2cf2a34 100755 --- a/user-storage-jpa/pom.xml +++ b/user-storage-jpa/pom.xml @@ -27,7 +27,7 @@ keycloak-user-storage-jpa jar Keycloak Quickstart: user-storage-jpa - User Storage Provider with EJB and JPA + User Storage Provider with JPA @@ -38,6 +38,7 @@ + 2.7.2.Final 3.5.1 1.0.0.Final 1.0.0.Final @@ -54,6 +55,12 @@ keycloak-server-spi provided + + org.keycloak + keycloak-model-jpa + ${version.keycloak} + provided + org.jboss.logging jboss-logging @@ -65,38 +72,6 @@ ${version.hibernate.javax.persistence} provided - - org.jboss.spec.javax.ejb - jboss-ejb-api_3.2_spec - ${version.jboss-ejb-api} - provided - - - org.jboss.arquillian.graphene - graphene-webdriver - ${arquillian-graphene.version} - pom - test - - - org.wildfly.extras.creaper - creaper-core - ${version.creaper} - test - - - com.google.guava - guava - - - - - org.wildfly.core - wildfly-cli - test - ${version.wildfly} - - @@ -111,90 +86,16 @@ 1.8 - - org.wildfly.plugins - wildfly-maven-plugin - ${version.wildfly.maven.plugin} - - false - ${keycloak.management.port} - - - - maven-enforcer-plugin - - - enforce-quickstart-realm-file-exist - validate - - - org.apache.maven.plugins maven-surefire-plugin ${version.surefire.plugin} - ${keycloak.management.port} ${project.build.directory} - - - - add-datasource - - - - org.wildfly.plugins - wildfly-maven-plugin - - false - true - - - - add-datasource - install - - add-resource - - - ${keycloak.management.port} - -
subsystem=datasources
- - -
xa-data-source=java:jboss/datasources/ExampleXADS
- - java:jboss/datasources/ExampleXADS - true - h2 - - - -
- xa-datasource-properties=URL -
- - jdbc:h2:mem:test - -
-
-
-
-
-
-
-
-
-
-
-
- diff --git a/user-storage-jpa/src/main/java/org/keycloak/quickstart/storage/user/MyExampleUserStorageProviderFactory.java b/user-storage-jpa/src/main/java/org/keycloak/quickstart/storage/user/MyExampleUserStorageProviderFactory.java new file mode 100644 index 000000000..a927a9103 --- /dev/null +++ b/user-storage-jpa/src/main/java/org/keycloak/quickstart/storage/user/MyExampleUserStorageProviderFactory.java @@ -0,0 +1,53 @@ +/* + * Copyright 2016 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.keycloak.quickstart.storage.user; + +import org.jboss.logging.Logger; +import org.keycloak.component.ComponentModel; +import org.keycloak.models.KeycloakSession; +import org.keycloak.storage.UserStorageProviderFactory; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +public class MyExampleUserStorageProviderFactory implements UserStorageProviderFactory { + public static final String PROVIDER_ID = "example-user-storage-jpa"; + + private static final Logger logger = Logger.getLogger(MyExampleUserStorageProviderFactory.class); + + @Override + public MyUserStorageProvider create(KeycloakSession session, ComponentModel model) { + return new MyUserStorageProvider(session, model); + } + + @Override + public String getId() { + return PROVIDER_ID; + } + + @Override + public String getHelpText() { + return "JPA Example User Storage Provider"; + } + + @Override + public void close() { + logger.info("<<<<<< Closing factory"); + + } +} diff --git a/user-storage-jpa/src/main/java/org/keycloak/quickstart/storage/user/MyUserStorageProvider.java b/user-storage-jpa/src/main/java/org/keycloak/quickstart/storage/user/MyUserStorageProvider.java new file mode 100644 index 000000000..8b4ab1d0e --- /dev/null +++ b/user-storage-jpa/src/main/java/org/keycloak/quickstart/storage/user/MyUserStorageProvider.java @@ -0,0 +1,297 @@ +/* + * Copyright 2016 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.keycloak.quickstart.storage.user; + +import org.jboss.logging.Logger; +import org.keycloak.component.ComponentModel; +import org.keycloak.connections.jpa.JpaConnectionProvider; +import org.keycloak.credential.CredentialInput; +import org.keycloak.credential.CredentialInputUpdater; +import org.keycloak.credential.CredentialInputValidator; +import org.keycloak.credential.CredentialModel; +import org.keycloak.models.GroupModel; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.RealmModel; +import org.keycloak.models.RoleModel; +import org.keycloak.models.UserCredentialModel; +import org.keycloak.models.UserModel; +import org.keycloak.models.cache.CachedUserModel; +import org.keycloak.models.cache.OnUserCache; +import org.keycloak.storage.StorageId; +import org.keycloak.storage.UserStorageProvider; +import org.keycloak.storage.user.UserLookupProvider; +import org.keycloak.storage.user.UserQueryProvider; +import org.keycloak.storage.user.UserRegistrationProvider; + +import javax.persistence.EntityManager; +import javax.persistence.TypedQuery; +import java.util.Collections; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.UUID; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +public class MyUserStorageProvider implements UserStorageProvider, + UserLookupProvider, + UserRegistrationProvider, + UserQueryProvider, + CredentialInputUpdater, + CredentialInputValidator, + OnUserCache +{ + private static final Logger logger = Logger.getLogger(MyUserStorageProvider.class); + public static final String PASSWORD_CACHE_KEY = UserAdapter.class.getName() + ".password"; + + protected EntityManager em; + + protected ComponentModel model; + protected KeycloakSession session; + + MyUserStorageProvider(KeycloakSession session, ComponentModel model) { + this.session = session; + this.model = model; + em = session.getProvider(JpaConnectionProvider.class, "user-store").getEntityManager(); + } + + @Override + public void preRemove(RealmModel realm) { + + } + + @Override + public void preRemove(RealmModel realm, GroupModel group) { + + } + + @Override + public void preRemove(RealmModel realm, RoleModel role) { + + } + + @Override + public void close() { + } + + @Override + public UserModel getUserById(String id, RealmModel realm) { + logger.info("getUserById: " + id); + String persistenceId = StorageId.externalId(id); + UserEntity entity = em.find(UserEntity.class, persistenceId); + if (entity == null) { + logger.info("could not find user by id: " + id); + return null; + } + return new UserAdapter(session, realm, model, entity); + } + + @Override + public UserModel getUserByUsername(String username, RealmModel realm) { + logger.info("getUserByUsername: " + username); + TypedQuery query = em.createNamedQuery("getUserByUsername", UserEntity.class); + query.setParameter("username", username); + List result = query.getResultList(); + if (result.isEmpty()) { + logger.info("could not find username: " + username); + return null; + } + + return new UserAdapter(session, realm, model, result.get(0)); + } + + @Override + public UserModel getUserByEmail(String email, RealmModel realm) { + TypedQuery query = em.createNamedQuery("getUserByEmail", UserEntity.class); + query.setParameter("email", email); + List result = query.getResultList(); + if (result.isEmpty()) return null; + return new UserAdapter(session, realm, model, result.get(0)); + } + + @Override + public UserModel addUser(RealmModel realm, String username) { + UserEntity entity = new UserEntity(); + entity.setId(UUID.randomUUID().toString()); + entity.setUsername(username); + em.persist(entity); + logger.info("added user: " + username); + return new UserAdapter(session, realm, model, entity); + } + + @Override + public boolean removeUser(RealmModel realm, UserModel user) { + String persistenceId = StorageId.externalId(user.getId()); + UserEntity entity = em.find(UserEntity.class, persistenceId); + if (entity == null) return false; + em.remove(entity); + return true; + } + + @Override + public void onCache(RealmModel realm, CachedUserModel user, UserModel delegate) { + String password = ((UserAdapter)delegate).getPassword(); + if (password != null) { + user.getCachedWith().put(PASSWORD_CACHE_KEY, password); + } + } + + @Override + public boolean supportsCredentialType(String credentialType) { + return CredentialModel.PASSWORD.equals(credentialType); + } + + @Override + public boolean updateCredential(RealmModel realm, UserModel user, CredentialInput input) { + if (!supportsCredentialType(input.getType()) || !(input instanceof UserCredentialModel)) return false; + UserCredentialModel cred = (UserCredentialModel)input; + UserAdapter adapter = getUserAdapter(user); + adapter.setPassword(cred.getValue()); + + return true; + } + + public UserAdapter getUserAdapter(UserModel user) { + UserAdapter adapter = null; + if (user instanceof CachedUserModel) { + adapter = (UserAdapter)((CachedUserModel)user).getDelegateForUpdate(); + } else { + adapter = (UserAdapter)user; + } + return adapter; + } + + @Override + public void disableCredentialType(RealmModel realm, UserModel user, String credentialType) { + if (!supportsCredentialType(credentialType)) return; + + getUserAdapter(user).setPassword(null); + + } + + @Override + public Set getDisableableCredentialTypes(RealmModel realm, UserModel user) { + if (getUserAdapter(user).getPassword() != null) { + Set set = new HashSet<>(); + set.add(CredentialModel.PASSWORD); + return set; + } else { + return Collections.emptySet(); + } + } + + @Override + public boolean isConfiguredFor(RealmModel realm, UserModel user, String credentialType) { + return supportsCredentialType(credentialType) && getPassword(user) != null; + } + + @Override + public boolean isValid(RealmModel realm, UserModel user, CredentialInput input) { + if (!supportsCredentialType(input.getType()) || !(input instanceof UserCredentialModel)) return false; + UserCredentialModel cred = (UserCredentialModel)input; + String password = getPassword(user); + return password != null && password.equals(cred.getValue()); + } + + public String getPassword(UserModel user) { + String password = null; + if (user instanceof CachedUserModel) { + password = (String)((CachedUserModel)user).getCachedWith().get(PASSWORD_CACHE_KEY); + } else if (user instanceof UserAdapter) { + password = ((UserAdapter)user).getPassword(); + } + return password; + } + + @Override + public int getUsersCount(RealmModel realm) { + Object count = em.createNamedQuery("getUserCount") + .getSingleResult(); + return ((Number)count).intValue(); + } + + @Override + public List getUsers(RealmModel realm) { + return getUsers(realm, -1, -1); + } + + @Override + public List getUsers(RealmModel realm, int firstResult, int maxResults) { + + TypedQuery query = em.createNamedQuery("getAllUsers", UserEntity.class); + if (firstResult != -1) { + query.setFirstResult(firstResult); + } + if (maxResults != -1) { + query.setMaxResults(maxResults); + } + List results = query.getResultList(); + List users = new LinkedList<>(); + for (UserEntity entity : results) users.add(new UserAdapter(session, realm, model, entity)); + return users; + } + + @Override + public List searchForUser(String search, RealmModel realm) { + return searchForUser(search, realm, -1, -1); + } + + @Override + public List searchForUser(String search, RealmModel realm, int firstResult, int maxResults) { + TypedQuery query = em.createNamedQuery("searchForUser", UserEntity.class); + query.setParameter("search", "%" + search.toLowerCase() + "%"); + if (firstResult != -1) { + query.setFirstResult(firstResult); + } + if (maxResults != -1) { + query.setMaxResults(maxResults); + } + List results = query.getResultList(); + List users = new LinkedList<>(); + for (UserEntity entity : results) users.add(new UserAdapter(session, realm, model, entity)); + return users; + } + + @Override + public List searchForUser(Map params, RealmModel realm) { + return Collections.EMPTY_LIST; + } + + @Override + public List searchForUser(Map params, RealmModel realm, int firstResult, int maxResults) { + return Collections.EMPTY_LIST; + } + + @Override + public List getGroupMembers(RealmModel realm, GroupModel group, int firstResult, int maxResults) { + return Collections.EMPTY_LIST; + } + + @Override + public List getGroupMembers(RealmModel realm, GroupModel group) { + return Collections.EMPTY_LIST; + } + + @Override + public List searchForUserByUserAttribute(String attrName, String attrValue, RealmModel realm) { + return Collections.EMPTY_LIST; + } +} diff --git a/user-storage-jpa/src/main/resources/META-INF/beans.xml b/user-storage-jpa/src/main/resources/META-INF/beans.xml new file mode 100644 index 000000000..e69de29bb diff --git a/user-storage-jpa/src/main/resources/META-INF/persistence.xml b/user-storage-jpa/src/main/resources/META-INF/persistence.xml index 923ca066a..c513c257a 100644 --- a/user-storage-jpa/src/main/resources/META-INF/persistence.xml +++ b/user-storage-jpa/src/main/resources/META-INF/persistence.xml @@ -4,12 +4,13 @@ xsi:schemaLocation=" http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd"> - - java:jboss/datasources/ExampleXADS - + org.keycloak.quickstart.storage.user.UserEntity - + + + + diff --git a/user-storage-jpa/src/main/resources/META-INF/services/org.keycloak.storage.UserStorageProviderFactory b/user-storage-jpa/src/main/resources/META-INF/services/org.keycloak.storage.UserStorageProviderFactory index 3cc851963..6a6811b7e 100644 --- a/user-storage-jpa/src/main/resources/META-INF/services/org.keycloak.storage.UserStorageProviderFactory +++ b/user-storage-jpa/src/main/resources/META-INF/services/org.keycloak.storage.UserStorageProviderFactory @@ -1 +1 @@ -org.keycloak.quickstart.storage.user.EjbExampleUserStorageProviderFactory \ No newline at end of file +org.keycloak.quickstart.storage.user.MyExampleUserStorageProviderFactory \ No newline at end of file