diff --git a/SUMMARY.adoc b/SUMMARY.adoc
index 3d89bd4c0e..44948340d9 100755
--- a/SUMMARY.adoc
+++ b/SUMMARY.adoc
@@ -21,4 +21,5 @@
.. link:topics/user-storage/augmenting.adoc[Augmenting External Storage]
.. link:topics/user-storage/import.adoc[Import Implementation Strategy]
.. link:topics/user-storage/cache.adoc[User Caches]
- .. link:topics/user-storage/javaee.adoc[Leveraging Java EE ]
+ .. link:topics/user-storage/javaee.adoc[Leveraging Java EE]
+ .. link:topics/user-storage/rest.adoc[REST Management API]
diff --git a/topics/providers.adoc b/topics/providers.adoc
index 619f67bf7c..0b028c3c39 100755
--- a/topics/providers.adoc
+++ b/topics/providers.adoc
@@ -175,12 +175,29 @@ public class MyEventListenerProviderFactory implements EventListenerProviderFact
=== Registering provider implementations
-Keycloak can load provider implementations from JBoss Modules or directly from the file-system.
+There are two ways to register provider implementations. The easiest way is to just throw your provider jar within
+the Keycloak `deploy/` directory. Keycloak supports hot deployment as well in this scenario. This is also the best
+solution.
+
+The alternative is not really recommended, but exists for legacy purposes as the Keycloak deployer didn't exist in
+previous versions of the project. Keycloak can load provider implementations from JBoss Modules or directly from the file-system.
Using Modules is recommended as you can control exactly what classes are available to your provider.
-Any providers loaded from the file-system uses a classloader with the Keycloak classloader as its parent.
+Any providers loaded from the file-system uses a classloader with the Keycloak classloader as its parent.
+
+==== Using the Keycloak Deployer
+
+If you throw your provider jar within the Keycloak `deploy/` directory, your provider will automatically be deployed.
+Hot deployment works too. Additionally, your provider jar works similarly to other components deployed in a JBoss/Wildfly
+environment in that they can use facilities like the `jboss-deployment-structure.xml` file. This file allows you to
+set up dependencies on other components and load third-party jars and modules.
+
+Provider jars can also be contained within other deployable units like EARs and WARs. Deploying with a EAR actually makes
+it really easy to use third party jars as you can just put these libraries in the EAR's `lib/` directory.
==== Register a provider using Modules
+WARNING: We don't recommend this approach.
+
To register a provider using Modules first create a module.
To do this you can either use the jboss-cli script or manually create a folder inside `KEYCLOAK_HOME/modules` and add your jar and a `module.xml`.
For example to add the event listener sysout example provider using the `jboss-cli` script execute:
@@ -275,7 +292,83 @@ For example to disable the Infinispan user cache provider add:
-----
+----
+
+=== Leveraging Java EE
+
+The can be packaged within any Java EE component so long as you set up the `META-INF/services`
+file correctly to point to your providers. For example, if your provider needs to use third party libraries, you
+can package up your provider within an ear and store these third pary libraries in the ear's `lib/` directory.
+Also note that provider jars can make use of the `jboss-deployment-structure.xml` file that EJBs, WARS, and EARs
+can use in a JBoss/Wildfly environment. See the JBoss/Wildfly documentation for more details on this file. It
+allows you to pull in external dependencies among other fine grain actions.
+
+`ProviderFactory` implementations are required to be plain java objects. But, we also currently support
+implementing provider classes as Stateful EJBs. TThis is how you would do it:
+
+[source,java]
+----
+@Stateful
+@Local(EjbExampleUserStorageProvider.class)
+public class EjbExampleUserStorageProvider implements UserStorageProvider,
+ UserLookupProvider,
+ UserRegistrationProvider,
+ UserQueryProvider,
+ CredentialInputUpdater,
+ CredentialInputValidator,
+ OnUserCache
+{
+ @PersistenceContext
+ protected EntityManager em;
+
+ protected ComponentModel model;
+ protected KeycloakSession session;
+
+ public void setModel(ComponentModel model) {
+ this.model = model;
+ }
+
+ public void setSession(KeycloakSession session) {
+ this.session = session;
+ }
+
+
+ @Remove
+ @Override
+ public void close() {
+ }
+...
+}
+----
+
+You have to define the `@Local` annotation and specify your provider class there. If you don't do this, EJB will
+not proxy the provider instance correctly and your provider won't work.
+
+You must put the `@Remove` annotation on the `close()` method of your provider. If you don't, the stateful bean
+will never be cleaned up and you may eventually see error messages.
+
+Implementations of `ProviderFactory` are required to be plain java objects. Your factory class would
+perform a JNDI lookup of the Stateful EJB in its create() method.
+
+[source,java]
+----
+public class EjbExampleUserStorageProviderFactory
+ implements UserStorageProviderFactory {
+
+ @Override
+ public EjbExampleUserStorageProvider create(KeycloakSession session, ComponentModel model) {
+ try {
+ InitialContext ctx = new InitialContext();
+ EjbExampleUserStorageProvider provider = (EjbExampleUserStorageProvider)ctx.lookup(
+ "java:global/user-storage-jpa-example/" + EjbExampleUserStorageProvider.class.getSimpleName());
+ provider.setModel(model);
+ provider.setSession(session);
+ return provider;
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+----
=== Available SPIs
diff --git a/topics/user-storage/import.adoc b/topics/user-storage/import.adoc
index a1b409c5ff..8f2a2ad131 100644
--- a/topics/user-storage/import.adoc
+++ b/topics/user-storage/import.adoc
@@ -69,6 +69,11 @@ For your providers, you can use this to _intercept_ other methods on the local `
with your extern store. For example, get methods could make sure that the local store is in sync. Set methods
keep external store in sync with local one.
+NOTE: If your provider is implementing the `UserRegistrationProvider` interface, your `removeUser()` method does not
+ need to remove the user from local storage. The runtime will automatically perform this operation. Also
+ note that `removeUser()` will be invoked before it is removed from local storage.
+
+
==== ImportedUserValidation Interface
If you remember earlier in this chapter, we discussed how querying for a user worked. Local storage is queried first,
diff --git a/topics/user-storage/registration-query.adoc b/topics/user-storage/registration-query.adoc
index 65af4b36ee..b4eb9ebfd7 100644
--- a/topics/user-storage/registration-query.adoc
+++ b/topics/user-storage/registration-query.adoc
@@ -56,6 +56,10 @@ Notice that when adding a user we set the password value of the property map to
we can't have null values for a property in the property value. We also have to modify the `CredentialInputValidator`
methods to reflect this.
+`addUser()` will be called if the provider implements the `UserRegistrationProvider` interface. If your provider has
+a configuration switch to turn of adding a user, returning `null` from this method will skip the provider and call
+the next one.
+
.PropertyFileUserStorageProvider
[source,java]
----
diff --git a/topics/user-storage/rest.adoc b/topics/user-storage/rest.adoc
new file mode 100644
index 0000000000..49668ecdae
--- /dev/null
+++ b/topics/user-storage/rest.adoc
@@ -0,0 +1,102 @@
+
+=== REST Management API
+
+You can create, remove, and update your user storage provider deployments through the admin REST api. The User Storage SPI
+is built on top of a generic component interface so you will be using that generic API to manage your providers.
+
+The REST Component API lives under your realm admin resource.
+
+----
+/admin/realms/{realm-name}/components
+----
+
+We will only show this REST API interaction with the Java client. Hopefully you can extract how do do this from
+curl from this api.
+
+[source,java]
+----
+public interface ComponentsResource {
+ @GET
+ @Produces(MediaType.APPLICATION_JSON)
+ public List query();
+
+ @GET
+ @Produces(MediaType.APPLICATION_JSON)
+ public List query(@QueryParam("parent") String parent);
+
+ @GET
+ @Produces(MediaType.APPLICATION_JSON)
+ public List query(@QueryParam("parent") String parent, @QueryParam("type") String type);
+
+ @GET
+ @Produces(MediaType.APPLICATION_JSON)
+ public List query(@QueryParam("parent") String parent,
+ @QueryParam("type") String type,
+ @QueryParam("name") String name);
+
+ @POST
+ @Consumes(MediaType.APPLICATION_JSON)
+ Response add(ComponentRepresentation rep);
+
+ @Path("{id}")
+ ComponentResource component(@PathParam("id") String id);
+}
+
+public interface ComponentResource {
+ @GET
+ public ComponentRepresentation toRepresentation();
+
+ @PUT
+ @Consumes(MediaType.APPLICATION_JSON)
+ public void update(ComponentRepresentation rep);
+
+ @DELETE
+ public void remove();
+}
+
+----
+
+To create a user storage provider, you must specify the provider id, a provider type of the string `org.keycloak.storage.UserStorageProvider`,
+as well as the configuration.
+
+[source,java]
+----
+import org.keycloak.admin.client.Keycloak;
+import org.keycloak.representations.idm.RealmRepresentation;
+...
+
+Keycloak keycloak = Keycloak.getInstance(
+ "http://localhost:8080/auth",
+ "master",
+ "admin",
+ "password",
+ "admin-cli");
+RealmResource realmResource = keycloak.realm("master");
+RealmRepresentation realm = realmResource.toRepresentation();
+
+ComponentRepresentation component = new ComponentRepresentation();
+component.setName("home");
+component.setProviderId("readonly-property-file");
+component.setProviderType("org.keycloak.storage.UserStorageProvider");
+component.setParentId(realm.getId());
+component.setConfig(new MultivaluedHashMap());
+component.getConfig().putSingle("path", "~/users.properties");
+
+realmResource.components().add(component);
+
+// retrieve a component
+
+List components = realmResource.components().query(realm.getId(),
+ "org.keycloak.storage.UserStorageProvider",
+ "home");
+component = components.get(0);
+
+// Update a component
+
+component.getConfig().putSingle("path", "~/my-users.properties");
+realmResource.components().component(component.getId()).update(component);
+
+// Remove a component
+
+realmREsource.components().component(component.getId()).remove();
+----