diff --git a/docbook/reference/en/en-US/master.xml b/docbook/reference/en/en-US/master.xml
index b8785dff6c..10c0684307 100755
--- a/docbook/reference/en/en-US/master.xml
+++ b/docbook/reference/en/en-US/master.xml
@@ -27,8 +27,7 @@
-
-
+
]>
@@ -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/authentication-spi.xml b/docbook/reference/en/en-US/modules/authentication-spi.xml
deleted file mode 100644
index c19dffa9b7..0000000000
--- a/docbook/reference/en/en-US/modules/authentication-spi.xml
+++ /dev/null
@@ -1,66 +0,0 @@
-
- Authentication SPI
-
- Keycloak provides Authentication SPI, which allows to choose the AuthenticationProvider for authenticating users.
- AuthenticationProvider is the interface, which states how will be your usernames/passwords validated. You can choose from
- the set of available AuthenticationProviders or you can even implement and plug your own AuthenticationProvider, which
- will allow to provide your own way how will Keycloak validates users and their passwords.
-
-
- Available Authentication Providers
-
-
- Model - This provider validates users and their passwords based on the Keycloak model. So it just delegates
- to model implementation provided either by RDBMS or Mongo at this moment. This is default AuthenticationProvider,
- which is configured for keycloak-admin realm by default and it's also automatically configured for newly created realms.
-
- External-model - This provider also uses Keycloak model, but it uses different realm to validate your users against.
- For example if you want to create new realm "foo" and you want all users of already existing realm "bar" that they are automatically
- able to login into realm "foo" with their usernames and passwords, you can choose this provider.
-
- Picketlink - This provider delegates Authentication to Picketlink IDM
- framework. Right now, Picketlink IDM in Keycloak is configured to always use LDAP based Identity store, which means that picketlink provider
- allows you to authenticate your users against LDAP server. Note that you will first need to configure LDAP server as described
- here . PicketlinkAuthenticationProvider configured for the realm will automatically use LDAP configuration for this realm.
-
-
-
-
-
- Features and configuration
-
-
-
- You can configure AuthenticationProviders separately for each realm. So for example you can choose that just realm
- "foo" will use PicketlinkAuthenticationProvider and authenticate users against LDAP but realm "keycloak-admin" will still use default ModelAuthenticationProvider.
-
-
- There is also possibility to choose more authentication providers for the realm, which actually means that Keycloak
- will use first available AuthenticationProvider and just in case that user doesn't exist here,
- it will fallback to second AuthenticationProvider in chain. So this may allow for example scenario, in which
- you authenticate user against Keycloak database (model) and just if he doesn't exist in database, it will fallback to LDAP (picketlink).
-
-
- You can configure for each AuthenticationProvider if you want to update passwords - option passwordUpdateSupported.
- This means that when user update password or his profile through Keycloak UI, this change will be propagated into AuthenticationProvider.
- So for example password in LDAP will be updated if it's true, but for read-only LDAP, you will likely switch it to false.
- It also means that newly registered users will be propagated to particular AuthenticationProvider too,
- but note that each user is always bind just to one AuthenticationProvider.
-
-
- You can add/edit/remove AuthenticationProviders in the Authentication tab in admin console, which is under URL
- http://localhost:8080/auth/admin/keycloak-admin/console/#/realms/YOUR_REALM/auth-settings
-
-
-
-
-
-
- Creating your own Authentication Provider
-
- You need to implement interface AuthenticationProvider and add the name of your AuthenticationProviderFactory class into
- META-INF/services/org.keycloak.authentication.AuthenticationProviderFactory file inside your JAR with AuthenticationProvider. You also need to copy this JAR into
- standalone/deployments/auth-server.war/WEB-INF/lib . The best is to look at example and try it out.
-
-
-
\ No newline at end of file
diff --git a/docbook/reference/en/en-US/modules/ldap.xml b/docbook/reference/en/en-US/modules/ldap.xml
deleted file mode 100644
index dd7d4aba61..0000000000
--- a/docbook/reference/en/en-US/modules/ldap.xml
+++ /dev/null
@@ -1,12 +0,0 @@
-
- LDAP Integration
-
- Right now, LDAP server is configured separately for each Realm. Configuration is in admin console in tab Ldap
- under realm settings. It's under URL like http://localhost:8080/auth/admin/keycloak-admin/console/index.html#/realms/YOUR_REALM/ldap-settings .
- There is nothing like "shared" LDAP server for more realms in Keycloak, but it's planned for the future.
-
-
- LDAP is currently used just for authentication of users done through PicketlinkAuthenticationProvider as described here .
- In the future, we have plan to have full Sync SPI, which will allow one-way or two-way synchronization between LDAP server and Keycloak database including users and roles.
-
-
\ No newline at end of file
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 0000000000..6360c7ea93
--- /dev/null
+++ b/docbook/reference/en/en-US/modules/user-federation.xml
@@ -0,0 +1,140 @@
+
+ User Federation SPI and LDAP/AD Integration
+
+ 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.
+
+
+ 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
+
+
+ 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.
+
+
+ LDAP and Active Directory Plugin
+
+ 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 Users menu option to get you
+ to the user management page. Then click on the Federation 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.
+
+
+ Edit Mode
+
+ Edit mode defines various synchronization options with your LDAP store depending on what privileges
+ you have.
+
+
+ READONLY
+
+
+ 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.
+
+
+
+
+ WRITABLE
+
+
+ Username, email, first and last name, and passwords can all be updated and will
+ be synchronized automatically with your LDAP store.
+
+
+
+
+ UNSYNCED
+
+
+ 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.
+
+
+
+
+
+
+
+ Other config options
+
+
+
+ Display Name
+
+
+ Name used when this provider is referenced in the admin consle
+
+
+
+
+ Priority
+
+
+ The priority of this provider when looking up users or for adding registrations.
+
+
+
+
+ Sync Registrations
+
+
+ If a new user is added through a registration page or admin console, should the user
+ be eligible to be synchronized to this provider.
+
+
+
+
+ Other options
+
+
+ The rest of the configuration options should be self explanatory.
+
+
+
+
+
+
+
+
+ Writing your own User Federation Provider
+
+ The keycloak examples directory contains an example of a simple User Federation Provider backed by
+ a simple properties file. See examples/providers/federation-provider. Most of how
+ to create a federation provider is explain directly within the example code, but some information is here too.
+
+
+ Writing a User Federation Provider starts by implementing the UserFederationProvider
+ and UserFederationProviderFactory 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 KeycloakSession.userStorage()
+ 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.
+
+
+ After your code is written you must package up all your classes within a JAR file. This jar file must
+ contain a file called org.keycloak.models.UserFederationProviderFactory
+ within the META-INF/services directory of the JAR. This file is a list
+ of fully qualified classnames of all implementations of UserFederationProviderFactory.
+ This is how Keycloak discovers which providers have been deployment. Place the JAR in the
+ keycloak WAR deployment in the WEB-INF/lib directory.
+
+
+
+
\ 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 0000000000..bdb4a1b2ac
--- /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 057ce5076c..04845fb2b3 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 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 6a8460eea0..0d63474e14 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 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 searchByAttributes(Map 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 list = new ArrayList(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 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 107f164028..5f52fcafa0 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 80906a30d8..5c64b73316 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 dbebfccdad..725daee20c 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 0844c991ed..e63a9e9cc6 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 Bill Burke
* @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 300f292cc9..a2b4886f9b 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 Bill Burke
* @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 d142464adc..9403a395ef 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 searchByAttributes(Map 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 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 7b00987cd0..047c2d2241 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 {
+ /**
+ * called per Keycloak transaction.
+ *
+ * @param session
+ * @param model
+ * @return
+ */
UserFederationProvider getInstance(KeycloakSession session, UserFederationProviderModel model);
/**
@@ -18,4 +25,12 @@ public interface UserFederationProviderFactory extends ProviderFactory 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 2542b9084b..3f6c45121a 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 Marek Posolda
* @author Bill Burke
*/