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 */