keycloak-aplcache

federation docs

8/4/2014 1:25:11 PM

Changes

docbook/reference/en/en-US/modules/authentication-spi.xml 66(+0 -66)

docbook/reference/en/en-US/modules/ldap.xml 12(+0 -12)

Details

diff --git a/docbook/reference/en/en-US/master.xml b/docbook/reference/en/en-US/master.xml
index b8785df..10c0684 100755
--- a/docbook/reference/en/en-US/master.xml
+++ b/docbook/reference/en/en-US/master.xml
@@ -27,8 +27,7 @@
                 <!ENTITY Timeouts SYSTEM "modules/timeouts.xml">
                 <!ENTITY Audit SYSTEM "modules/audit.xml">
                 <!ENTITY AdminApi SYSTEM "modules/admin-rest-api.xml">
-                <!ENTITY Authentication SYSTEM "modules/authentication-spi.xml">
-                <!ENTITY Ldap SYSTEM "modules/ldap.xml">
+                <!ENTITY UserFederation SYSTEM "modules/user-federation.xml">
                 <!ENTITY ExportImport SYSTEM "modules/export-import.xml">
                 <!ENTITY ServerCache SYSTEM "modules/cache.xml">
                 ]>
@@ -115,8 +114,7 @@ This one is short
     &Timeouts;
     &AdminApi;
     &Audit;
-    &Authentication;
-    &Ldap;
+    &UserFederation;
     &ExportImport;
     &ServerCache;
     &Migration;
diff --git a/docbook/reference/en/en-US/modules/user-federation.xml b/docbook/reference/en/en-US/modules/user-federation.xml
new file mode 100755
index 0000000..6360c7e
--- /dev/null
+++ b/docbook/reference/en/en-US/modules/user-federation.xml
@@ -0,0 +1,140 @@
+<chapter id="user_federation">
+    <title>User Federation SPI and LDAP/AD Integration</title>
+    <para>
+        Keycloak can federate external user databases.  Out of the box we have support for LDAP and Active Directory.
+        Before you dive into this, you should understand how Keycloak does federation.
+    </para>
+    <para>
+        Keycloak performs federation a bit differently than other products/projects.  The vision of Keycloak is that it
+        is an out of the box solution that should provide a core set of feature irregardless of the backend user storage you
+        want to use.  Because of this requirement/vision, Keycloak has a set data model that all of its services use.
+        Most of the time when you want to federate an external user store, much of the metadata that would be needed to
+        provide this complete feature set does not exist in that external store.  For example your LDAP server may only
+        provide password validation, but not support TOTP or user role mappings.  The Keycloak User Federation SPI was
+        written to support these completely variable locations
+    </para>
+    <para>
+        The way user federation works is that Keycloak will import your federated users on demand to its local storage.  How
+        much metadata that is imported depends on the underlying federation plugin and how that plugin is configured.  Some
+        federation plugins may only import the username into Keycloak storage, others might import everything from name,
+        address, and phone number, to user role mappings.  Some plugins might want to import credentials directly into
+        Keycloak storage and let Keycloak handle credential validation.  Others might want to handle credential validation
+        themselves.  Thegoal of the Federation SPI is to support all of these scenarios.
+    </para>
+    <section>
+        <title>LDAP and Active Directory Plugin</title>
+        <para>
+            Keycloak comes with a built-in LDAP/AD plugin.  Currently it is set up only to import username, email, first and last name.
+            It supports password validation via LDAP/AD protocols and different user metadata synchronization modes.  To configure
+            a federated LDAP store go to the admin console.  Click on the <literal>Users</literal> menu option to get you
+            to the user management page.  Then click on the <literal>Federation</literal> submenu option.  When
+            you get to this page there is an "Add Provider" select box.  You should see "ldap" within this list.  Selecting
+            "ldap" will bring you to the ldap configuration page.
+        </para>
+        <section>
+            <title>Edit Mode</title>
+            <para>
+                Edit mode defines various synchronization options with your LDAP store depending on what privileges
+                you have.
+                <variablelist>
+                    <varlistentry>
+                        <term>READONLY</term>
+                        <listitem>
+                            <para>
+                                Username, email, first and last name will be unchangable.  Keycloak will show an error
+                                anytime anybody tries to update these fields.  Also, password updates will not be supported.
+                            </para>
+                        </listitem>
+                    </varlistentry>
+                    <varlistentry>
+                        <term>WRITABLE</term>
+                        <listitem>
+                            <para>
+                                Username, email, first and last name, and passwords can all be updated and will
+                                be synchronized automatically with your LDAP store.
+                            </para>
+                        </listitem>
+                    </varlistentry>
+                    <varlistentry>
+                        <term>UNSYNCED</term>
+                        <listitem>
+                            <para>
+                                Any changes to username, email, first and last name, and passwords will be stored
+                                in Keycloak local storage. It is up to you to figure out how to synchronize back to
+                                LDAP.
+                            </para>
+                        </listitem>
+                    </varlistentry>
+                </variablelist>
+            </para>
+        </section>
+        <section>
+            <title>Other config options</title>
+            <para>
+                <variablelist>
+                    <varlistentry>
+                        <term>Display Name</term>
+                        <listitem>
+                            <para>
+                                Name used when this provider is referenced in the admin consle
+                            </para>
+                        </listitem>
+                    </varlistentry>
+                    <varlistentry>
+                        <term>Priority</term>
+                        <listitem>
+                            <para>
+                                The priority of this provider when looking up users or for adding registrations.
+                            </para>
+                        </listitem>
+                    </varlistentry>
+                    <varlistentry>
+                        <term>Sync Registrations</term>
+                        <listitem>
+                            <para>
+                                If a new user is added through a registration page or admin console, should the user
+                                be eligible to be synchronized to this provider.
+                            </para>
+                        </listitem>
+                    </varlistentry>
+                    <varlistentry>
+                        <term>Other options</term>
+                        <listitem>
+                            <para>
+                                The rest of the configuration options should be self explanatory.
+                            </para>
+                        </listitem>
+                    </varlistentry>
+                </variablelist>
+            </para>
+        </section>
+    </section>
+    <section>
+        <title>Writing your own User Federation Provider</title>
+        <para>
+            The keycloak examples directory contains an example of a simple User Federation Provider backed by
+            a simple properties file.  See <literal>examples/providers/federation-provider</literal>.  Most of how
+            to create a federation provider is explain directly within the example code, but some information is here too.
+        </para>
+        <para>
+            Writing a User Federation Provider starts by implementing the <literal>UserFederationProvider</literal>
+            and <literal>UserFederationProviderFactory</literal> interfaces.  Please see the Javadoc and example
+            for complete details on on how to do this.  Some important methods of note:
+            getUserByUsername() and getUserByEmail() require that you query your federated storage and if the user exists
+            create and import the user into Keycloak storage.  How much metadata you import is fully up to you.  This
+            import is done by invoking methods on the object returned <literal>KeycloakSession.userStorage()</literal>
+            to add and import user information.  The proxy() method will be called whenever Keycloak has found an imported
+            UserModel.  This allows the federation provider to proxy the UserModel which is useful if you want to support
+            external storage updates on demand.
+        </para>
+        <para>
+            After your code is written you must package up all your classes within a JAR file.  This jar file must
+            contain a file called <literal>org.keycloak.models.UserFederationProviderFactory</literal>
+            within the <literal>META-INF/services</literal> directory of the JAR.  This file is a list
+            of fully qualified classnames of all implementations of <literal>UserFederationProviderFactory</literal>.
+            This is how Keycloak discovers which providers have been deployment.  Place the JAR in the
+            keycloak WAR deployment in the <literal>WEB-INF/lib</literal> directory.
+        </para>
+    </section>
+
+</chapter>
\ No newline at end of file
diff --git a/examples/providers/federation-provider/README.md b/examples/providers/federation-provider/README.md
new file mode 100755
index 0000000..bdb4a1b
--- /dev/null
+++ b/examples/providers/federation-provider/README.md
@@ -0,0 +1,16 @@
+Example User Federation Provider
+===================================================
+
+This is an example of user federation backed by a simple properties file.  This properties file only contains username/password
+key pairs.  To deploy, build this directory then take the jar and copy it to the WEB-INF/lib of the keycloak server's
+WAR file.
+
+The ClasspathPropertiesFederationProvider is an example of a readonly provider.  If you go to the Users/Federation
+  page of the admin console you will see this provider listed under "classpath-properties.  To configure this provider you 
+specify a classpath to a properties file in the "path" field of the admin page for this plugin.  This example includes
+a "test-users.properties" within the JAR that you can use as the variable.
+  
+The FilePropertiesFederationProvider is an exxample of a writable provider.  It synchronizes changes made to
+username and password with the properties file.  If you go to the Users/Federation page of the admin console you will 
+see this provider listed under "file-properties".  To configure this provider you specify a fully qualified file path to 
+a properties file in the "path" field of the admin page for this plugin.  
\ No newline at end of file
diff --git a/examples/providers/federation-provider/src/main/java/org/keycloak/examples/federation/properties/BasePropertiesFederationFactory.java b/examples/providers/federation-provider/src/main/java/org/keycloak/examples/federation/properties/BasePropertiesFederationFactory.java
index 057ce50..04845fb 100755
--- a/examples/providers/federation-provider/src/main/java/org/keycloak/examples/federation/properties/BasePropertiesFederationFactory.java
+++ b/examples/providers/federation-provider/src/main/java/org/keycloak/examples/federation/properties/BasePropertiesFederationFactory.java
@@ -27,10 +27,12 @@ public abstract class BasePropertiesFederationFactory implements UserFederationP
 
     @Override
     public UserFederationProvider getInstance(KeycloakSession session, UserFederationProviderModel model) {
+        // first get the path to our properties file from the stored configuration of this provider instance.
         String path = model.getConfig().get("path");
         if (path == null) {
             throw new IllegalStateException("Path attribute not configured for provider");
         }
+        // see if we already loaded the config file
         Properties props = files.get(path);
         if (props != null) return createProvider(session, model, props);
 
@@ -43,6 +45,7 @@ public abstract class BasePropertiesFederationFactory implements UserFederationP
         } catch (IOException e) {
             throw new RuntimeException(e);
         }
+        // remember the properties file for next time
         files.put(path, props);
         return createProvider(session, model, props);
     }
@@ -51,7 +54,12 @@ public abstract class BasePropertiesFederationFactory implements UserFederationP
 
     protected abstract BasePropertiesFederationProvider createProvider(KeycloakSession session, UserFederationProviderModel model, Properties props);
 
-
+    /**
+     * List the configuration options to render and display in the admin console's generic management page for this
+     * plugin
+     *
+     * @return
+     */
     @Override
     public Set<String> getConfigurationOptions() {
         return configOptions;
@@ -62,6 +70,11 @@ public abstract class BasePropertiesFederationFactory implements UserFederationP
         return null;
     }
 
+    /**
+     * You can import additional plugin configuration from keycloak-server.json here.
+     *
+     * @param config
+     */
     @Override
     public void init(Config.Scope config) {
 
diff --git a/examples/providers/federation-provider/src/main/java/org/keycloak/examples/federation/properties/BasePropertiesFederationProvider.java b/examples/providers/federation-provider/src/main/java/org/keycloak/examples/federation/properties/BasePropertiesFederationProvider.java
index 6a8460e..0d63474 100755
--- a/examples/providers/federation-provider/src/main/java/org/keycloak/examples/federation/properties/BasePropertiesFederationProvider.java
+++ b/examples/providers/federation-provider/src/main/java/org/keycloak/examples/federation/properties/BasePropertiesFederationProvider.java
@@ -32,10 +32,6 @@ public abstract class BasePropertiesFederationProvider implements UserFederation
         this.properties = properties;
     }
 
-    public static Set<String> getSupportedCredentialTypes() {
-        return supportedCredentialTypes;
-    }
-
     static
     {
         supportedCredentialTypes.add(UserCredentialModel.PASSWORD);
@@ -71,12 +67,22 @@ public abstract class BasePropertiesFederationProvider implements UserFederation
         return null;
     }
 
+    /**
+     * We only search for Usernames as that is all that is stored in the properties file.  Not that if the user
+     * does exist in the properties file, we only import it if the user hasn't been imported already.
+     *
+     * @param attributes
+     * @param realm
+     * @param maxResults
+     * @return
+     */
     @Override
     public List<UserModel> searchByAttributes(Map<String, String> attributes, RealmModel realm, int maxResults) {
         String username = attributes.get(USERNAME);
         if (username != null) {
             // make sure user isn't already in storage
             if (session.userStorage().getUserByUsername(username, realm) == null) {
+                // user is not already imported, so let's import it until local storage.
                 UserModel user = getUserByUsername(realm, username);
                 if (user != null) {
                     List<UserModel> list = new ArrayList<UserModel>(1);
@@ -90,19 +96,32 @@ public abstract class BasePropertiesFederationProvider implements UserFederation
 
     @Override
     public void preRemove(RealmModel realm) {
-
+       // complete  We don't care about the realm being removed
     }
 
     @Override
     public void preRemove(RealmModel realm, RoleModel role) {
+        // complete we dont'care if a role is removed
 
     }
 
+    /**
+     * See if the user is still in the properties file
+     *
+     * @param local
+     * @return
+     */
     @Override
     public boolean isValid(UserModel local) {
         return properties.containsKey(local.getUsername());
     }
 
+    /**
+     * hardcoded to only return PASSWORD
+     *
+     * @param user
+     * @return
+     */
     @Override
     public Set<String> getSupportedCredentialTypes(UserModel user) {
         return supportedCredentialTypes;
diff --git a/examples/providers/federation-provider/src/main/java/org/keycloak/examples/federation/properties/ClasspathPropertiesFederationProvider.java b/examples/providers/federation-provider/src/main/java/org/keycloak/examples/federation/properties/ClasspathPropertiesFederationProvider.java
index 107f164..5f52fca 100755
--- a/examples/providers/federation-provider/src/main/java/org/keycloak/examples/federation/properties/ClasspathPropertiesFederationProvider.java
+++ b/examples/providers/federation-provider/src/main/java/org/keycloak/examples/federation/properties/ClasspathPropertiesFederationProvider.java
@@ -26,21 +26,43 @@ public class ClasspathPropertiesFederationProvider extends BasePropertiesFederat
         super(session, model, properties);
     }
 
+    /**
+     * Keycloak will call this method if it finds an imported UserModel.  Here we proxy the UserModel with
+     * a Readonly proxy which will barf if password is updated.
+     *
+     * @param local
+     * @return
+     */
     @Override
     public UserModel proxy(UserModel local) {
         return new ReadonlyUserModelProxy(local);
     }
 
+    /**
+     * The properties file is readonly so don't suppport registration.
+     *
+     * @return
+     */
     @Override
     public boolean synchronizeRegistrations() {
         return false;
     }
 
+    /**
+     * The properties file is readonly so don't suppport registration.
+     *
+     * @return
+     */
     @Override
     public UserModel register(RealmModel realm, UserModel user) {
         throw new IllegalStateException("Registration not supported");
     }
 
+    /**
+     * The properties file is readonly so don't removing a user
+     *
+     * @return
+     */
     @Override
     public boolean removeUser(RealmModel realm, UserModel user) {
         throw new IllegalStateException("Remove not supported");
diff --git a/examples/providers/federation-provider/src/main/java/org/keycloak/examples/federation/properties/FilePropertiesFederationFactory.java b/examples/providers/federation-provider/src/main/java/org/keycloak/examples/federation/properties/FilePropertiesFederationFactory.java
index 80906a3..5c64b73 100755
--- a/examples/providers/federation-provider/src/main/java/org/keycloak/examples/federation/properties/FilePropertiesFederationFactory.java
+++ b/examples/providers/federation-provider/src/main/java/org/keycloak/examples/federation/properties/FilePropertiesFederationFactory.java
@@ -27,8 +27,12 @@ public class FilePropertiesFederationFactory extends BasePropertiesFederationFac
     }
 
 
-
-
+    /**
+     * Name of the provider.  This will show up under the "Add Provider" select box on the Federation page in the
+     * admin console
+     *
+     * @return
+     */
     @Override
     public String getId() {
         return "file-properties";
diff --git a/examples/providers/federation-provider/src/main/java/org/keycloak/examples/federation/properties/FilePropertiesFederationProvider.java b/examples/providers/federation-provider/src/main/java/org/keycloak/examples/federation/properties/FilePropertiesFederationProvider.java
index dbebfcc..725daee 100755
--- a/examples/providers/federation-provider/src/main/java/org/keycloak/examples/federation/properties/FilePropertiesFederationProvider.java
+++ b/examples/providers/federation-provider/src/main/java/org/keycloak/examples/federation/properties/FilePropertiesFederationProvider.java
@@ -28,11 +28,23 @@ public class FilePropertiesFederationProvider extends BasePropertiesFederationPr
         super(session, model, properties);
     }
 
+    /**
+     * Keycloak will call this method if it finds an imported UserModel.  Here we proxy the UserModel with
+     * a Writable proxy which will synchronize updates to username and password back to the properties file
+     *
+     * @param local
+     * @return
+     */
     @Override
     public UserModel proxy(UserModel local) {
         return new WritableUserModelProxy(local, this);
     }
 
+    /**
+     * Adding new users is supported
+     *
+     * @return
+     */
     @Override
     public boolean synchronizeRegistrations() {
         return true;
@@ -49,6 +61,13 @@ public class FilePropertiesFederationProvider extends BasePropertiesFederationPr
         }
     }
 
+    /**
+     * Update the properties file with the new user.
+     *
+     * @param realm
+     * @param user
+     * @return
+     */
     @Override
     public UserModel register(RealmModel realm, UserModel user) {
         synchronized (properties) {
diff --git a/examples/providers/federation-provider/src/main/java/org/keycloak/examples/federation/properties/ReadonlyUserModelProxy.java b/examples/providers/federation-provider/src/main/java/org/keycloak/examples/federation/properties/ReadonlyUserModelProxy.java
index 0844c99..e63a9e9 100755
--- a/examples/providers/federation-provider/src/main/java/org/keycloak/examples/federation/properties/ReadonlyUserModelProxy.java
+++ b/examples/providers/federation-provider/src/main/java/org/keycloak/examples/federation/properties/ReadonlyUserModelProxy.java
@@ -6,6 +6,8 @@ import org.keycloak.models.UserModel;
 import org.keycloak.models.utils.UserModelDelegate;
 
 /**
+ * Readonly proxy for a UserModel that prevents passwords from being updated.
+ *
  * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
  * @version $Revision: 1 $
  */
diff --git a/examples/providers/federation-provider/src/main/java/org/keycloak/examples/federation/properties/WritableUserModelProxy.java b/examples/providers/federation-provider/src/main/java/org/keycloak/examples/federation/properties/WritableUserModelProxy.java
index 300f292..a2b4886 100755
--- a/examples/providers/federation-provider/src/main/java/org/keycloak/examples/federation/properties/WritableUserModelProxy.java
+++ b/examples/providers/federation-provider/src/main/java/org/keycloak/examples/federation/properties/WritableUserModelProxy.java
@@ -10,6 +10,8 @@ import java.io.IOException;
 import java.util.Properties;
 
 /**
+ * Proxy that will synchronize password updates to the properties file.
+ *
  * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
  * @version $Revision: 1 $
  */
@@ -21,6 +23,13 @@ public class WritableUserModelProxy extends UserModelDelegate {
         this.provider = provider;
     }
 
+
+    /**
+     * Updates the properties file if the username changes.  If you have a more complex user storage, you can
+     * override other methods on UserModel to synchronize updates back to your external storage.
+     *
+     * @param username
+     */
     @Override
     public void setUsername(String username) {
         if (delegate.getUsername().equals(username)) return;
diff --git a/model/api/src/main/java/org/keycloak/models/UserFederationProvider.java b/model/api/src/main/java/org/keycloak/models/UserFederationProvider.java
index d142464..9403a39 100755
--- a/model/api/src/main/java/org/keycloak/models/UserFederationProvider.java
+++ b/model/api/src/main/java/org/keycloak/models/UserFederationProvider.java
@@ -60,6 +60,14 @@ public interface UserFederationProvider extends Provider {
      * @return
      */
     boolean synchronizeRegistrations();
+
+    /**
+     * Called if this federation provider has priority and supports synchronized registrations.
+     *
+     * @param realm
+     * @param user
+     * @return
+     */
     UserModel register(RealmModel realm, UserModel user);
     boolean removeUser(RealmModel realm, UserModel user);
 
@@ -91,7 +99,19 @@ public interface UserFederationProvider extends Provider {
      */
     List<UserModel> searchByAttributes(Map<String, String> attributes, RealmModel realm, int maxResults);
 
+    /**
+     * called whenever a Realm is removed
+     *
+     * @param realm
+     */
     void preRemove(RealmModel realm);
+
+    /**
+     * called before a role is removed.
+     *
+     * @param realm
+     * @param role
+     */
     void preRemove(RealmModel realm, RoleModel role);
 
     /**
@@ -111,7 +131,8 @@ public interface UserFederationProvider extends Provider {
     Set<String> getSupportedCredentialTypes(UserModel user);
 
     /**
-     * Validate credentials for this user.
+     * Validate credentials for this user.  This method will only be called with credential parameters supported
+     * by this provider
      *
      * @param realm
      * @param user
diff --git a/model/api/src/main/java/org/keycloak/models/UserFederationProviderFactory.java b/model/api/src/main/java/org/keycloak/models/UserFederationProviderFactory.java
index 7b00987..047c2d2 100755
--- a/model/api/src/main/java/org/keycloak/models/UserFederationProviderFactory.java
+++ b/model/api/src/main/java/org/keycloak/models/UserFederationProviderFactory.java
@@ -10,6 +10,13 @@ import java.util.Set;
  * @version $Revision: 1 $
  */
 public interface UserFederationProviderFactory extends ProviderFactory<UserFederationProvider> {
+    /**
+     * called per Keycloak transaction.
+     *
+     * @param session
+     * @param model
+     * @return
+     */
     UserFederationProvider getInstance(KeycloakSession session, UserFederationProviderModel model);
 
     /**
@@ -18,4 +25,12 @@ public interface UserFederationProviderFactory extends ProviderFactory<UserFeder
      * @return
      */
     Set<String> getConfigurationOptions();
+
+    /**
+     * This is the name of the provider and will be showed in the admin console as an option.
+     *
+     * @return
+     */
+    @Override
+    String getId();
 }
diff --git a/model/api/src/main/java/org/keycloak/models/UserFederationProviderModel.java b/model/api/src/main/java/org/keycloak/models/UserFederationProviderModel.java
index 2542b90..3f6c451 100755
--- a/model/api/src/main/java/org/keycloak/models/UserFederationProviderModel.java
+++ b/model/api/src/main/java/org/keycloak/models/UserFederationProviderModel.java
@@ -4,6 +4,8 @@ import java.util.HashMap;
 import java.util.Map;
 
 /**
+ * Stored configuration of a User Federation provider instance.
+ *
  * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
  * @author <a href="mailto:bburke@redhat.com">Bill Burke</a>
  */