keycloak-uncached
Changes
federation/ldap/src/main/java/org/keycloak/federation/ldap/ReadonlyLDAPUserModelDelegate.java 57(+57 -0)
federation/ldap/src/main/java/org/keycloak/federation/ldap/UnsyncedLDAPUserModelDelegate.java 28(+28 -0)
federation/ldap/src/main/java/org/keycloak/federation/ldap/WritableLDAPUserModelDelegate.java 132(+132 -0)
forms/common-themes/src/main/resources/theme/admin/base/resources/partials/federated-ldap.html 13(+13 -0)
Details
diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPFederationProvider.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPFederationProvider.java
index 5ad1136..587cc39 100755
--- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPFederationProvider.java
+++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPFederationProvider.java
@@ -1,6 +1,7 @@
package org.keycloak.federation.ldap;
import org.jboss.logging.Logger;
+import org.keycloak.models.UserCredentialValueModel;
import org.keycloak.models.UserFederationProvider;
import org.keycloak.models.UserFederationProviderModel;
import org.keycloak.models.KeycloakSession;
@@ -19,6 +20,7 @@ import org.picketlink.idm.model.basic.BasicModel;
import org.picketlink.idm.model.basic.User;
import org.picketlink.idm.query.IdentityQuery;
+import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
@@ -35,10 +37,12 @@ public class LDAPFederationProvider implements UserFederationProvider {
private static final Logger logger = Logger.getLogger(LDAPFederationProvider.class);
public static final String LDAP_ID = "LDAP_ID";
public static final String SYNC_REGISTRATIONS = "syncRegistrations";
+ public static final String EDIT_MODE = "editMode";
protected KeycloakSession session;
protected UserFederationProviderModel model;
protected PartitionManager partitionManager;
+ protected EditMode editMode;
protected static final Set<String> supportedCredentialTypes = new HashSet<String>();
@@ -51,6 +55,9 @@ public class LDAPFederationProvider implements UserFederationProvider {
this.session = session;
this.model = model;
this.partitionManager = partitionManager;
+ String editModeString = model.getConfig().get(EDIT_MODE);
+ if (editModeString == null) editMode = EditMode.READ_ONLY;
+ editMode = EditMode.valueOf(editModeString);
}
private ModelException convertIDMException(IdentityManagementException ie) {
@@ -77,22 +84,37 @@ public class LDAPFederationProvider implements UserFederationProvider {
@Override
public UserModel proxy(UserModel local) {
- // todo
- return new LDAPUserModelDelegate(local, this);
+ switch (editMode) {
+ case READ_ONLY:
+ return new ReadonlyLDAPUserModelDelegate(local, this);
+ case WRITABLE:
+ return new WritableLDAPUserModelDelegate(local, this);
+ case UNSYNCED:
+ return new UnsyncedLDAPUserModelDelegate(local, this);
+ }
+ return local;
}
@Override
- public Set<String> getSupportedCredentialTypes() {
+ public Set<String> getSupportedCredentialTypes(UserModel local) {
+ if (editMode == EditMode.UNSYNCED ) {
+ for (UserCredentialValueModel cred : local.getCredentialsDirectly()) {
+ if (cred.getType().equals(UserCredentialModel.PASSWORD)) {
+ return Collections.emptySet();
+ }
+ }
+ }
return supportedCredentialTypes;
}
@Override
public boolean synchronizeRegistrations() {
- return "true".equalsIgnoreCase(model.getConfig().get(SYNC_REGISTRATIONS));
+ return "true".equalsIgnoreCase(model.getConfig().get(SYNC_REGISTRATIONS)) && editMode == EditMode.WRITABLE;
}
@Override
public UserModel register(RealmModel realm, UserModel user) {
+ if (editMode == EditMode.READ_ONLY || editMode == EditMode.UNSYNCED) throw new IllegalStateException("Registration is not supported by this ldap server");;
if (!synchronizeRegistrations()) throw new IllegalStateException("Registration is not supported by this ldap server");
IdentityManager identityManager = getIdentityManager();
@@ -112,6 +134,8 @@ public class LDAPFederationProvider implements UserFederationProvider {
@Override
public boolean removeUser(RealmModel realm, UserModel user) {
+ if (editMode == EditMode.READ_ONLY || editMode == EditMode.UNSYNCED) return false;
+
IdentityManager identityManager = getIdentityManager();
try {
diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/ReadonlyLDAPUserModelDelegate.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/ReadonlyLDAPUserModelDelegate.java
new file mode 100755
index 0000000..30dca4c
--- /dev/null
+++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/ReadonlyLDAPUserModelDelegate.java
@@ -0,0 +1,57 @@
+package org.keycloak.federation.ldap;
+
+import org.jboss.logging.Logger;
+import org.keycloak.models.ModelException;
+import org.keycloak.models.UserCredentialModel;
+import org.keycloak.models.UserModel;
+import org.keycloak.models.utils.UserModelDelegate;
+import org.picketlink.idm.IdentityManagementException;
+import org.picketlink.idm.IdentityManager;
+import org.picketlink.idm.credential.Password;
+import org.picketlink.idm.credential.TOTPCredential;
+import org.picketlink.idm.model.basic.BasicModel;
+import org.picketlink.idm.model.basic.User;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class ReadonlyLDAPUserModelDelegate extends UserModelDelegate implements UserModel {
+ private static final Logger logger = Logger.getLogger(ReadonlyLDAPUserModelDelegate.class);
+
+ protected LDAPFederationProvider provider;
+
+ public ReadonlyLDAPUserModelDelegate(UserModel delegate, LDAPFederationProvider provider) {
+ super(delegate);
+ this.provider = provider;
+ }
+
+ @Override
+ public void setUsername(String username) {
+ throw new IllegalStateException("Federated storage is not writable");
+ }
+
+ @Override
+ public void setLastName(String lastName) {
+ throw new IllegalStateException("Federated storage is not writable");
+ }
+
+ @Override
+ public void setFirstName(String first) {
+ throw new IllegalStateException("Federated storage is not writable");
+ }
+
+ @Override
+ public void updateCredential(UserCredentialModel cred) {
+ if (provider.getSupportedCredentialTypes(delegate).contains(cred.getType())) {
+ throw new IllegalStateException("Federated storage is not writable");
+ }
+ delegate.updateCredential(cred);
+ }
+
+ @Override
+ public void setEmail(String email) {
+ throw new IllegalStateException("Federated storage is not writable");
+ }
+
+}
diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/UnsyncedLDAPUserModelDelegate.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/UnsyncedLDAPUserModelDelegate.java
new file mode 100755
index 0000000..043f8d7
--- /dev/null
+++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/UnsyncedLDAPUserModelDelegate.java
@@ -0,0 +1,28 @@
+package org.keycloak.federation.ldap;
+
+import org.jboss.logging.Logger;
+import org.keycloak.models.ModelException;
+import org.keycloak.models.UserCredentialModel;
+import org.keycloak.models.UserModel;
+import org.keycloak.models.utils.UserModelDelegate;
+import org.picketlink.idm.IdentityManagementException;
+import org.picketlink.idm.IdentityManager;
+import org.picketlink.idm.credential.Password;
+import org.picketlink.idm.credential.TOTPCredential;
+import org.picketlink.idm.model.basic.BasicModel;
+import org.picketlink.idm.model.basic.User;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class UnsyncedLDAPUserModelDelegate extends UserModelDelegate implements UserModel {
+ private static final Logger logger = Logger.getLogger(UnsyncedLDAPUserModelDelegate.class);
+
+ protected LDAPFederationProvider provider;
+
+ public UnsyncedLDAPUserModelDelegate(UserModel delegate, LDAPFederationProvider provider) {
+ super(delegate);
+ this.provider = provider;
+ }
+}
diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/WritableLDAPUserModelDelegate.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/WritableLDAPUserModelDelegate.java
new file mode 100755
index 0000000..a98d2b8
--- /dev/null
+++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/WritableLDAPUserModelDelegate.java
@@ -0,0 +1,132 @@
+package org.keycloak.federation.ldap;
+
+import org.jboss.logging.Logger;
+import org.keycloak.models.ApplicationModel;
+import org.keycloak.models.AuthenticationLinkModel;
+import org.keycloak.models.ModelException;
+import org.keycloak.models.RoleModel;
+import org.keycloak.models.UserCredentialModel;
+import org.keycloak.models.UserCredentialValueModel;
+import org.keycloak.models.UserModel;
+import org.keycloak.models.utils.UserModelDelegate;
+import org.picketlink.idm.IdentityManagementException;
+import org.picketlink.idm.IdentityManager;
+import org.picketlink.idm.credential.Password;
+import org.picketlink.idm.credential.TOTPCredential;
+import org.picketlink.idm.model.basic.BasicModel;
+import org.picketlink.idm.model.basic.User;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class WritableLDAPUserModelDelegate extends UserModelDelegate implements UserModel {
+ private static final Logger logger = Logger.getLogger(WritableLDAPUserModelDelegate.class);
+
+ protected LDAPFederationProvider provider;
+
+ public WritableLDAPUserModelDelegate(UserModel delegate, LDAPFederationProvider provider) {
+ super(delegate);
+ this.provider = provider;
+ }
+
+ @Override
+ public void setUsername(String username) {
+ IdentityManager identityManager = provider.getIdentityManager();
+
+ try {
+ User picketlinkUser = BasicModel.getUser(identityManager, delegate.getUsername());
+ if (picketlinkUser == null) {
+ throw new IllegalStateException("User not found in LDAP storage!");
+ }
+ picketlinkUser.setLoginName(username);
+ identityManager.update(picketlinkUser);
+ } catch (IdentityManagementException ie) {
+ throw new ModelException(ie);
+ }
+ delegate.setUsername(username);
+ }
+
+ @Override
+ public void setLastName(String lastName) {
+ IdentityManager identityManager = provider.getIdentityManager();
+
+ try {
+ User picketlinkUser = BasicModel.getUser(identityManager, delegate.getUsername());
+ if (picketlinkUser == null) {
+ throw new IllegalStateException("User not found in LDAP storage!");
+ }
+ picketlinkUser.setLastName(lastName);
+ identityManager.update(picketlinkUser);
+ } catch (IdentityManagementException ie) {
+ throw new ModelException(ie);
+ }
+ delegate.setLastName(lastName);
+ }
+
+ @Override
+ public void setFirstName(String first) {
+ IdentityManager identityManager = provider.getIdentityManager();
+
+ try {
+ User picketlinkUser = BasicModel.getUser(identityManager, delegate.getUsername());
+ if (picketlinkUser == null) {
+ throw new IllegalStateException("User not found in LDAP storage!");
+ }
+ picketlinkUser.setFirstName(first);
+ identityManager.update(picketlinkUser);
+ } catch (IdentityManagementException ie) {
+ throw new ModelException(ie);
+ }
+ delegate.setFirstName(first);
+ }
+
+ @Override
+ public void updateCredential(UserCredentialModel cred) {
+ if (!provider.getSupportedCredentialTypes(delegate).contains(cred.getType())) {
+ delegate.updateCredential(cred);
+ return;
+ }
+ IdentityManager identityManager = provider.getIdentityManager();
+
+ try {
+ User picketlinkUser = BasicModel.getUser(identityManager, getUsername());
+ if (picketlinkUser == null) {
+ logger.debugf("User '%s' doesn't exists. Skip password update", getUsername());
+ throw new IllegalStateException("User doesn't exist in LDAP storage");
+ }
+ if (cred.getType().equals(UserCredentialModel.PASSWORD)) {
+ identityManager.updateCredential(picketlinkUser, new Password(cred.getValue().toCharArray()));
+ } else if (cred.getType().equals(UserCredentialModel.TOTP)) {
+ TOTPCredential credential = new TOTPCredential(cred.getValue());
+ credential.setDevice(cred.getDevice());
+ identityManager.updateCredential(picketlinkUser, credential);
+ }
+ } catch (IdentityManagementException ie) {
+ throw new ModelException(ie);
+ }
+
+ }
+
+ @Override
+ public void setEmail(String email) {
+ IdentityManager identityManager = provider.getIdentityManager();
+
+ try {
+ User picketlinkUser = BasicModel.getUser(identityManager, delegate.getUsername());
+ if (picketlinkUser == null) {
+ throw new IllegalStateException("User not found in LDAP storage!");
+ }
+ picketlinkUser.setEmail(email);
+ identityManager.update(picketlinkUser);
+ } catch (IdentityManagementException ie) {
+ throw new ModelException(ie);
+ }
+ delegate.setEmail(email);
+ }
+
+}
diff --git a/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/federated-ldap.html b/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/federated-ldap.html
index ffac6b7..1be350b 100755
--- a/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/federated-ldap.html
+++ b/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/federated-ldap.html
@@ -33,6 +33,19 @@
<input class="form-control" id="priority" type="text" ng-model="instance.priority">
</div>
</div>
+ <div class="form-group">
+ <label class="col-sm-2 control-label" for="editMode">Edit mode</label>
+ <div class="col-sm-4">
+ <div class="select-kc">
+ <select id="editMode"
+ ng-model="instance.config.editMode">
+ <option>READ_ONLY</option>
+ <option>WRITABLE</option>
+ <option>UNSYNCED</option>
+ </select>
+ </div>
+ </div>
+ </div>
<div class="form-group clearfix block">
<label class="col-sm-2 control-label" for="syncRegistrations">Sync Registrations</label>
<div class="col-sm-4">
diff --git a/model/api/src/main/java/org/keycloak/models/UserFederationManager.java b/model/api/src/main/java/org/keycloak/models/UserFederationManager.java
index 787ca90..0e887d3 100755
--- a/model/api/src/main/java/org/keycloak/models/UserFederationManager.java
+++ b/model/api/src/main/java/org/keycloak/models/UserFederationManager.java
@@ -307,7 +307,7 @@ public class UserFederationManager implements UserProvider {
UserFederationProvider link = getFederationLink(realm, user);
if (link != null) {
validateUser(realm, user);
- if (link.getSupportedCredentialTypes().size() > 0) {
+ if (link.getSupportedCredentialTypes(user).size() > 0) {
List<UserCredentialModel> fedCreds = new ArrayList<UserCredentialModel>();
List<UserCredentialModel> localCreds = new ArrayList<UserCredentialModel>();
for (UserCredentialModel cred : input) {
@@ -331,7 +331,7 @@ public class UserFederationManager implements UserProvider {
UserFederationProvider link = getFederationLink(realm, user);
if (link != null) {
validateUser(realm, user);
- Set<String> supportedCredentialTypes = link.getSupportedCredentialTypes();
+ Set<String> supportedCredentialTypes = link.getSupportedCredentialTypes(user);
if (supportedCredentialTypes.size() > 0) {
List<UserCredentialModel> fedCreds = new ArrayList<UserCredentialModel>();
List<UserCredentialModel> localCreds = new ArrayList<UserCredentialModel>();
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 c45812f..c0923e4 100755
--- a/model/api/src/main/java/org/keycloak/models/UserFederationProvider.java
+++ b/model/api/src/main/java/org/keycloak/models/UserFederationProvider.java
@@ -20,6 +20,28 @@ public interface UserFederationProvider extends Provider {
public static final String LAST_NAME = UserModel.LAST_NAME;
/**
+ * Optional type that can be by implementations to describe edit mode of federation storage
+ *
+ */
+ enum EditMode {
+ /**
+ * federation storage is read-only
+ */
+ READ_ONLY,
+ /**
+ * federation storage is writable
+ *
+ */
+ WRITABLE,
+ /**
+ * updates to user are stored locally and not synced with federation storage.
+ *
+ */
+ UNSYNCED
+ }
+
+
+ /**
* Gives the provider an option to proxy UserModels loaded from local storage.
* This method is called whenever a UserModel is pulled from local storage.
* For example, the LDAP provider proxies the UserModel and does on-demand synchronization with
@@ -81,12 +103,12 @@ public interface UserFederationProvider extends Provider {
boolean isValid(UserModel local);
/**
- * What UserCredentialModel types are supported by this provider. Keycloak will only call
+ * What UserCredentialModel types should be handled by this provider for this user? Keycloak will only call
* validCredentials() with the credential types specified in this method.
*
* @return
*/
- Set<String> getSupportedCredentialTypes();
+ Set<String> getSupportedCredentialTypes(UserModel user);
/**
* Validate credentials for this user.
@@ -98,4 +120,6 @@ public interface UserFederationProvider extends Provider {
*/
boolean validCredentials(RealmModel realm, UserModel user, List<UserCredentialModel> input);
boolean validCredentials(RealmModel realm, UserModel user, UserCredentialModel... input);
- void close();}
+ void close();
+
+}
diff --git a/testsuite/integration/src/main/java/org/keycloak/testutils/DummyUserFederationProvider.java b/testsuite/integration/src/main/java/org/keycloak/testutils/DummyUserFederationProvider.java
index 7ebf592..2559b39 100755
--- a/testsuite/integration/src/main/java/org/keycloak/testutils/DummyUserFederationProvider.java
+++ b/testsuite/integration/src/main/java/org/keycloak/testutils/DummyUserFederationProvider.java
@@ -67,7 +67,7 @@ public class DummyUserFederationProvider implements UserFederationProvider {
}
@Override
- public Set<String> getSupportedCredentialTypes() {
+ public Set<String> getSupportedCredentialTypes(UserModel user) {
return Collections.emptySet();
}
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/FederationProvidersIntegrationTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/FederationProvidersIntegrationTest.java
index 81304c8..73959d0 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/FederationProvidersIntegrationTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/FederationProvidersIntegrationTest.java
@@ -12,6 +12,8 @@ import org.junit.runners.MethodSorters;
import org.keycloak.OAuth2Constants;
import org.keycloak.federation.ldap.LDAPFederationProvider;
import org.keycloak.federation.ldap.LDAPFederationProviderFactory;
+import org.keycloak.models.UserCredentialValueModel;
+import org.keycloak.models.UserFederationProvider;
import org.keycloak.models.UserFederationProviderModel;
import org.keycloak.testutils.LDAPEmbeddedServer;
import org.keycloak.testsuite.LDAPTestUtils;
@@ -66,10 +68,11 @@ public class FederationProvidersIntegrationTest {
String vendor = ldapServer.getVendor();
ldapConfig.put(LDAPConstants.VENDOR, vendor);
ldapConfig.put(LDAPFederationProvider.SYNC_REGISTRATIONS, "true");
+ ldapConfig.put(LDAPFederationProvider.EDIT_MODE, UserFederationProvider.EditMode.WRITABLE.toString());
- ldapModel = appRealm.addUserFederationProvider(LDAPFederationProviderFactory.PROVIDER_NAME, ldapConfig, 0, null);
+ ldapModel = appRealm.addUserFederationProvider(LDAPFederationProviderFactory.PROVIDER_NAME, ldapConfig, 0, "test-ldap");
// Configure LDAP
ldapRule.getEmbeddedServer().setupLdapInRealm(appRealm);
@@ -201,4 +204,76 @@ public class FederationProvidersIntegrationTest {
Assert.assertEquals(user.getFederationLink(), ldapModel.getId());
keycloakRule.stopSession(session, false);
}
+
+ @Test
+ public void testReadonly() {
+ KeycloakSession session = keycloakRule.startSession();
+ RealmModel appRealm = session.realms().getRealmByName("test");
+
+ UserFederationProviderModel model = new UserFederationProviderModel(ldapModel.getId(), ldapModel.getProviderName(), ldapModel.getConfig(), ldapModel.getPriority(), ldapModel.getDisplayName());
+ model.getConfig().put(LDAPFederationProvider.EDIT_MODE, UserFederationProvider.EditMode.READ_ONLY.toString());
+ appRealm.updateUserFederationProvider(model);
+ UserModel user = session.users().getUserByUsername("johnkeycloak", appRealm);
+ Assert.assertNotNull(user);
+ Assert.assertNotNull(user.getFederationLink());
+ Assert.assertEquals(user.getFederationLink(), ldapModel.getId());
+ try {
+ user.setEmail("error@error.com");
+ Assert.fail("should fail");
+ } catch (Exception e) {
+
+ }
+ try {
+ user.setLastName("Berk");
+ Assert.fail("should fail");
+ } catch (Exception e) {
+
+ }
+ try {
+ user.setFirstName("Bilbo");
+ Assert.fail("should fail");
+ } catch (Exception e) {
+
+ }
+ try {
+ UserCredentialModel cred = UserCredentialModel.password("poop");
+ user.updateCredential(cred);
+ Assert.fail("should fail");
+ } catch (Exception e) {
+
+ }
+ session.getTransaction().rollback();
+ session.close();
+ session = keycloakRule.startSession();
+ appRealm = session.realms().getRealmByName("test");
+ Assert.assertEquals(UserFederationProvider.EditMode.WRITABLE.toString(), appRealm.getUserFederationProviders().get(0).getConfig().get(LDAPFederationProvider.EDIT_MODE));
+ keycloakRule.stopSession(session, false);
+ }
+
+ @Test
+ public void testUnsynced() {
+ KeycloakSession session = keycloakRule.startSession();
+ RealmModel appRealm = session.realms().getRealmByName("test");
+
+ UserFederationProviderModel model = new UserFederationProviderModel(ldapModel.getId(), ldapModel.getProviderName(), ldapModel.getConfig(), ldapModel.getPriority(), ldapModel.getDisplayName());
+ model.getConfig().put(LDAPFederationProvider.EDIT_MODE, UserFederationProvider.EditMode.UNSYNCED.toString());
+ appRealm.updateUserFederationProvider(model);
+ UserModel user = session.users().getUserByUsername("johnkeycloak", appRealm);
+ Assert.assertNotNull(user);
+ Assert.assertNotNull(user.getFederationLink());
+ Assert.assertEquals(user.getFederationLink(), ldapModel.getId());
+
+ UserCredentialModel cred = UserCredentialModel.password("candy");
+ user.updateCredential(cred);
+ UserCredentialValueModel userCredentialValueModel = user.getCredentialsDirectly().get(0);
+ Assert.assertEquals(UserCredentialModel.PASSWORD, userCredentialValueModel.getType());
+ Assert.assertTrue(session.users().validCredentials(appRealm, user, cred));
+ session.getTransaction().rollback();
+ session.close();
+ session = keycloakRule.startSession();
+ appRealm = session.realms().getRealmByName("test");
+ Assert.assertEquals(UserFederationProvider.EditMode.WRITABLE.toString(), appRealm.getUserFederationProviders().get(0).getConfig().get(LDAPFederationProvider.EDIT_MODE));
+ keycloakRule.stopSession(session, false);
+ }
+
}