Merge pull request #3851 from patriot1burke/master

LDAP No-Import
This commit is contained in:
Bill Burke 2017-02-09 10:29:55 -05:00 committed by GitHub
commit 75909a0add
30 changed files with 2813 additions and 222 deletions

View file

@ -34,6 +34,7 @@ import org.keycloak.models.LDAPConstants;
import org.keycloak.models.ModelDuplicateException;
import org.keycloak.models.ModelException;
import org.keycloak.models.ModelReadOnlyException;
import org.keycloak.models.utils.ReadOnlyUserModelDelegate;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleModel;
import org.keycloak.models.UserCredentialModel;
@ -43,6 +44,8 @@ import org.keycloak.models.cache.UserCache;
import org.keycloak.models.credential.PasswordUserCredentialModel;
import org.keycloak.storage.StorageId;
import org.keycloak.storage.UserStorageProvider;
import org.keycloak.storage.UserStorageProviderModel;
import org.keycloak.storage.adapter.InMemoryUserAdapter;
import org.keycloak.storage.ldap.idm.model.LDAPObject;
import org.keycloak.storage.ldap.idm.query.Condition;
import org.keycloak.storage.ldap.idm.query.EscapeStrategy;
@ -86,7 +89,7 @@ public class LDAPStorageProvider implements UserStorageProvider,
protected LDAPStorageProviderFactory factory;
protected KeycloakSession session;
protected ComponentModel model;
protected UserStorageProviderModel model;
protected LDAPIdentityStore ldapIdentityStore;
protected EditMode editMode;
protected LDAPProviderKerberosConfig kerberosConfig;
@ -94,12 +97,16 @@ public class LDAPStorageProvider implements UserStorageProvider,
protected LDAPStorageMapperManager mapperManager;
protected LDAPStorageUserManager userManager;
// these exist to make sure that we only hit ldap once per transaction
//protected Map<String, UserModel> noImportSessionCache = new HashMap<>();
protected final Set<String> supportedCredentialTypes = new HashSet<>();
public LDAPStorageProvider(LDAPStorageProviderFactory factory, KeycloakSession session, ComponentModel model, LDAPIdentityStore ldapIdentityStore) {
this.factory = factory;
this.session = session;
this.model = model;
this.model = new UserStorageProviderModel(model);
this.ldapIdentityStore = ldapIdentityStore;
this.kerberosConfig = new LDAPProviderKerberosConfig(model);
this.editMode = ldapIdentityStore.getConfig().getEditMode();
@ -128,7 +135,7 @@ public class LDAPStorageProvider implements UserStorageProvider,
return editMode;
}
public ComponentModel getModel() {
public UserStorageProviderModel getModel() {
return model;
}
@ -163,7 +170,11 @@ public class LDAPStorageProvider implements UserStorageProvider,
switch (editMode) {
case READ_ONLY:
proxied = new ReadonlyLDAPUserModelDelegate(local, this);
if (model.isImportEnabled()) {
proxied = new ReadonlyLDAPUserModelDelegate(local, this);
} else {
proxied = new ReadOnlyUserModelDelegate(local);
}
break;
case WRITABLE:
proxied = new WritableLDAPUserModelDelegate(local, this, ldapObject);
@ -217,8 +228,14 @@ public class LDAPStorageProvider implements UserStorageProvider,
if (!synchronizeRegistrations()) {
return null;
}
UserModel user = session.userLocalStorage().addUser(realm, username);
user.setFederationLink(model.getId());
UserModel user = null;
if (model.isImportEnabled()) {
user = session.userLocalStorage().addUser(realm, username);
user.setFederationLink(model.getId());
} else {
user = new InMemoryUserAdapter(session, realm, new StorageId(model.getId(), username).getId());
user.setUsername(username);
}
LDAPObject ldapUser = LDAPUtils.addUserToLDAP(this, realm, user);
LDAPUtils.checkUuid(ldapUser, ldapIdentityStore.getConfig());
user.setSingleAttribute(LDAPConstants.LDAP_ID, ldapUser.getUuid());
@ -248,6 +265,9 @@ public class LDAPStorageProvider implements UserStorageProvider,
@Override
public UserModel getUserById(String id, RealmModel realm) {
UserModel alreadyLoadedInSession = userManager.getManagedProxiedUser(id);
if (alreadyLoadedInSession != null) return alreadyLoadedInSession;
StorageId storageId = new StorageId(id);
return getUserByUsername(storageId.getExternalId(), realm);
}
@ -341,7 +361,7 @@ public class LDAPStorageProvider implements UserStorageProvider,
UserModel kcUser = session.users().getUserByUsername(username, realm);
if (kcUser == null) {
logger.warnf("User '%s' referenced by membership wasn't found in LDAP", username);
} else if (!model.getId().equals(kcUser.getFederationLink())) {
} else if (model.isImportEnabled() && !model.getId().equals(kcUser.getFederationLink())) {
logger.warnf("Incorrect federation provider of user '%s'", kcUser.getUsername());
} else {
result.add(kcUser);
@ -434,7 +454,14 @@ public class LDAPStorageProvider implements UserStorageProvider,
String ldapUsername = LDAPUtils.getUsername(ldapUser, ldapIdentityStore.getConfig());
LDAPUtils.checkUuid(ldapUser, ldapIdentityStore.getConfig());
UserModel imported = session.userLocalStorage().addUser(realm, ldapUsername);
UserModel imported = null;
if (model.isImportEnabled()) {
imported = session.userLocalStorage().addUser(realm, ldapUsername);
} else {
InMemoryUserAdapter adapter = new InMemoryUserAdapter(session, realm, new StorageId(model.getId(), ldapUsername).getId());
adapter.addDefaults();
imported = adapter;
}
imported.setEnabled(true);
List<ComponentModel> mappers = realm.getComponents(model.getId(), LDAPStorageMapper.class.getName());
@ -448,13 +475,15 @@ public class LDAPStorageProvider implements UserStorageProvider,
}
String userDN = ldapUser.getDn().toString();
imported.setFederationLink(model.getId());
if (model.isImportEnabled()) imported.setFederationLink(model.getId());
imported.setSingleAttribute(LDAPConstants.LDAP_ID, ldapUser.getUuid());
imported.setSingleAttribute(LDAPConstants.LDAP_ENTRY_DN, userDN);
logger.debugf("Imported new user from LDAP to Keycloak DB. Username: [%s], Email: [%s], LDAP_ID: [%s], LDAP Entry DN: [%s]", imported.getUsername(), imported.getEmail(),
ldapUser.getUuid(), userDN);
return proxy(realm, imported, ldapUser);
UserModel proxy = proxy(realm, imported, ldapUser);
return proxy;
}
protected LDAPObject queryByEmail(RealmModel realm, String email) {

View file

@ -95,6 +95,10 @@ public class LDAPStorageProviderFactory implements UserStorageProviderFactory<LD
.property().name(LDAPConstants.EDIT_MODE)
.type(ProviderConfigProperty.STRING_TYPE)
.add()
.property().name(UserStorageProviderModel.IMPORT_ENABLED)
.type(ProviderConfigProperty.BOOLEAN_TYPE)
.defaultValue("true")
.add()
.property().name(LDAPConstants.SYNC_REGISTRATIONS)
.type(ProviderConfigProperty.BOOLEAN_TYPE)
.defaultValue("false")

View file

@ -26,6 +26,7 @@ import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.models.RealmModel;
import org.keycloak.provider.ProviderConfigProperty;
import org.keycloak.representations.idm.UserFederationMapperSyncConfigRepresentation;
import org.keycloak.storage.UserStorageProviderModel;
import java.util.Collections;
import java.util.List;
@ -82,6 +83,10 @@ public interface LDAPStorageMapperFactory<T extends LDAPStorageMapper> extends S
}
default void onParentUpdate(RealmModel realm, UserStorageProviderModel oldParent, UserStorageProviderModel newParent, ComponentModel mapperModel) {
}
/**
* Called when UserStorageProviderModel is created. This allows you to do initialization of any additional configuration
* you need to add. For example, you may be introspecting a database or ldap schema to automatically create mappings.

View file

@ -24,6 +24,7 @@ import org.keycloak.models.RealmModel;
import org.keycloak.provider.ProviderConfigProperty;
import org.keycloak.provider.ProviderConfigurationBuilder;
import org.keycloak.storage.UserStorageProvider;
import org.keycloak.storage.UserStorageProviderModel;
import org.keycloak.storage.ldap.LDAPConfig;
import org.keycloak.storage.ldap.LDAPStorageProvider;
@ -42,38 +43,43 @@ public class UserAttributeLDAPStorageMapperFactory extends AbstractLDAPStorageMa
configProperties = props;
}
private static List<ProviderConfigProperty> getConfigProps(ComponentModel parent) {
private static List<ProviderConfigProperty> getConfigProps(ComponentModel p) {
String readOnly = "false";
if (parent != null) {
UserStorageProviderModel parent = new UserStorageProviderModel();
if (p != null) {
parent = new UserStorageProviderModel(p);
LDAPConfig ldapConfig = new LDAPConfig(parent.getConfig());
readOnly = ldapConfig.getEditMode() == UserStorageProvider.EditMode.WRITABLE ? "false" : "true";
}
return ProviderConfigurationBuilder.create()
.property().name(UserAttributeLDAPStorageMapper.USER_MODEL_ATTRIBUTE)
.label("User Model Attribute")
.helpText("Name of mapped UserModel property or UserModel attribute in Keycloak DB. For example 'firstName', 'lastName, 'email', 'street' etc.")
.type(ProviderConfigProperty.STRING_TYPE)
.add()
.property().name(UserAttributeLDAPStorageMapper.LDAP_ATTRIBUTE).label("LDAP Attribute").helpText("Name of mapped attribute on LDAP object. For example 'cn', 'sn, 'mail', 'street' etc.")
.type(ProviderConfigProperty.STRING_TYPE)
.add()
.property().name(UserAttributeLDAPStorageMapper.READ_ONLY).label("Read Only")
.helpText("Read-only attribute is imported from LDAP to Keycloak DB, but it's not saved back to LDAP when user is updated in Keycloak.")
.type(ProviderConfigProperty.BOOLEAN_TYPE)
.defaultValue(readOnly)
.add()
.property().name(UserAttributeLDAPStorageMapper.ALWAYS_READ_VALUE_FROM_LDAP).label("Always Read Value From LDAP")
.helpText("If on, then during reading of the user will be value of attribute from LDAP always used instead of the value from Keycloak DB")
.type(ProviderConfigProperty.BOOLEAN_TYPE).defaultValue("false").add()
.property().name(UserAttributeLDAPStorageMapper.IS_MANDATORY_IN_LDAP).label("Is Mandatory In LDAP")
.helpText("If true, attribute is mandatory in LDAP. Hence if there is no value in Keycloak DB, the empty value will be set to be propagated to LDAP")
.type(ProviderConfigProperty.BOOLEAN_TYPE)
.defaultValue("false").add()
.property().name(UserAttributeLDAPStorageMapper.IS_BINARY_ATTRIBUTE).label("Is Binary Attribute")
.helpText("Should be true for binary LDAP attributes")
.type(ProviderConfigProperty.BOOLEAN_TYPE)
.defaultValue("false").add()
.build();
ProviderConfigurationBuilder config = ProviderConfigurationBuilder.create()
.property().name(UserAttributeLDAPStorageMapper.USER_MODEL_ATTRIBUTE)
.label("User Model Attribute")
.helpText("Name of the UserModel property or attribute you want to map the LDAP attribute into. For example 'firstName', 'lastName, 'email', 'street' etc.")
.type(ProviderConfigProperty.STRING_TYPE)
.add()
.property().name(UserAttributeLDAPStorageMapper.LDAP_ATTRIBUTE).label("LDAP Attribute").helpText("Name of mapped attribute on LDAP object. For example 'cn', 'sn, 'mail', 'street' etc.")
.type(ProviderConfigProperty.STRING_TYPE)
.add()
.property().name(UserAttributeLDAPStorageMapper.READ_ONLY).label("Read Only")
.helpText("Read-only attribute is imported from LDAP to UserModel, but it's not saved back to LDAP when user is updated in Keycloak.")
.type(ProviderConfigProperty.BOOLEAN_TYPE)
.defaultValue(readOnly)
.add();
if (parent.isImportEnabled()) {
config.
property().name(UserAttributeLDAPStorageMapper.ALWAYS_READ_VALUE_FROM_LDAP).label("Always Read Value From LDAP")
.helpText("If on, then during reading of the LDAP attribute value will always used instead of the value from Keycloak DB")
.type(ProviderConfigProperty.BOOLEAN_TYPE).defaultValue("false").add();
}
config.property().name(UserAttributeLDAPStorageMapper.IS_MANDATORY_IN_LDAP).label("Is Mandatory In LDAP")
.helpText("If true, attribute is mandatory in LDAP. Hence if there is no value in Keycloak DB, the empty value will be set to be propagated to LDAP")
.type(ProviderConfigProperty.BOOLEAN_TYPE)
.defaultValue("false").add()
.property().name(UserAttributeLDAPStorageMapper.IS_BINARY_ATTRIBUTE).label("Is Binary Attribute")
.helpText("Should be true for binary LDAP attributes")
.type(ProviderConfigProperty.BOOLEAN_TYPE)
.defaultValue("false").add();
return config.build();
}
@Override

View file

@ -25,6 +25,7 @@ import org.keycloak.models.RealmModel;
import org.keycloak.provider.ProviderConfigProperty;
import org.keycloak.provider.ProviderConfigurationBuilder;
import org.keycloak.storage.UserStorageProvider;
import org.keycloak.storage.UserStorageProviderModel;
import org.keycloak.storage.ldap.LDAPConfig;
import org.keycloak.storage.ldap.LDAPStorageProvider;
import org.keycloak.storage.ldap.LDAPUtils;
@ -53,6 +54,7 @@ public class GroupLDAPStorageMapperFactory extends AbstractLDAPStorageMapperFact
protected static final Map<String, UserRolesRetrieveStrategy> userGroupsStrategies = new LinkedHashMap<>();
protected static final List<String> MEMBERSHIP_TYPES = new LinkedList<>();
protected static final List<String> MODES = new LinkedList<>();
protected static final List<String> NO_IMPORT_MODES = new LinkedList<>();
protected static final List<String> ROLE_RETRIEVERS;
// TODO: Merge with RoleLDAPFederationMapperFactory as there are lot of similar properties
@ -66,6 +68,8 @@ public class GroupLDAPStorageMapperFactory extends AbstractLDAPStorageMapperFact
for (LDAPGroupMapperMode mode : LDAPGroupMapperMode.values()) {
MODES.add(mode.toString());
}
NO_IMPORT_MODES.add(LDAPGroupMapperMode.LDAP_ONLY.toString());
NO_IMPORT_MODES.add(LDAPGroupMapperMode.READ_ONLY.toString());
ROLE_RETRIEVERS = new LinkedList<>(userGroupsStrategies.keySet());
List<ProviderConfigProperty> config = getProps(null);
@ -76,98 +80,112 @@ public class GroupLDAPStorageMapperFactory extends AbstractLDAPStorageMapperFact
String roleObjectClasses = LDAPConstants.GROUP_OF_NAMES;
String mode = LDAPGroupMapperMode.LDAP_ONLY.toString();
String membershipUserAttribute = LDAPConstants.UID;
boolean importEnabled = true;
if (parent != null) {
LDAPConfig config = new LDAPConfig(parent.getConfig());
roleObjectClasses = config.isActiveDirectory() ? LDAPConstants.GROUP : LDAPConstants.GROUP_OF_NAMES;
mode = config.getEditMode() == UserStorageProvider.EditMode.WRITABLE ? LDAPGroupMapperMode.LDAP_ONLY.toString() : LDAPGroupMapperMode.READ_ONLY.toString();
membershipUserAttribute = config.getUsernameLdapAttribute();
importEnabled = new UserStorageProviderModel(parent).isImportEnabled();
}
return ProviderConfigurationBuilder.create()
.property().name(GroupMapperConfig.GROUPS_DN)
.label("LDAP Groups DN")
.helpText("LDAP DN where are groups of this tree saved. For example 'ou=groups,dc=example,dc=org' ")
.type(ProviderConfigProperty.STRING_TYPE)
.add()
.property().name(GroupMapperConfig.GROUP_NAME_LDAP_ATTRIBUTE)
.label("Group Name LDAP Attribute")
.helpText("Name of LDAP attribute, which is used in group objects for name and RDN of group. Usually it will be 'cn' . In this case typical group/role object may have DN like 'cn=Group1,ou=groups,dc=example,dc=org' ")
.type(ProviderConfigProperty.STRING_TYPE)
.defaultValue(LDAPConstants.CN)
.add()
.property().name(GroupMapperConfig.GROUP_OBJECT_CLASSES)
.label("Group Object Classes")
.helpText("Object class (or classes) of the group object. It's divided by comma if more classes needed. In typical LDAP deployment it could be 'groupOfNames' . In Active Directory it's usually 'group' ")
.type(ProviderConfigProperty.STRING_TYPE)
.defaultValue(roleObjectClasses)
.add()
.property().name(GroupMapperConfig.PRESERVE_GROUP_INHERITANCE)
.label("Preserve Group Inheritance")
.helpText("Flag whether group inheritance from LDAP should be propagated to Keycloak. If false, then all LDAP groups will be mapped as flat top-level groups in Keycloak. Otherwise group inheritance is " +
"preserved into Keycloak, but the group sync might fail if LDAP structure contains recursions or multiple parent groups per child groups")
.type(ProviderConfigProperty.BOOLEAN_TYPE)
.defaultValue("true")
.add()
.property().name(GroupMapperConfig.MEMBERSHIP_LDAP_ATTRIBUTE)
.label("Membership LDAP Attribute")
.helpText("Name of LDAP attribute on group, which is used for membership mappings. Usually it will be 'member' ." +
"However when 'Membership Attribute Type' is 'UID' then 'Membership LDAP Attribute' could be typically 'memberUid' .")
.type(ProviderConfigProperty.STRING_TYPE)
.defaultValue(LDAPConstants.MEMBER)
.add()
.property().name(GroupMapperConfig.MEMBERSHIP_ATTRIBUTE_TYPE)
.label("Membership Attribute Type")
.helpText("DN means that LDAP group has it's members declared in form of their full DN. For example 'member: uid=john,ou=users,dc=example,dc=com' . " +
"UID means that LDAP group has it's members declared in form of pure user uids. For example 'memberUid: john' .")
.type(ProviderConfigProperty.LIST_TYPE)
.options(MEMBERSHIP_TYPES)
.defaultValue(MembershipType.DN.toString())
.add()
.property().name(RoleMapperConfig.MEMBERSHIP_USER_LDAP_ATTRIBUTE)
.label("Membership User LDAP Attribute")
.helpText("Used just if Membership Attribute Type is UID. It is name of LDAP attribute on user, which is used for membership mappings. Usually it will be 'uid' . For example if value of " +
"'Membership User LDAP Attribute' is 'uid' and " +
" LDAP group has 'memberUid: john', then it is expected that particular LDAP user will have attribute 'uid: john' .")
.type(ProviderConfigProperty.STRING_TYPE)
.defaultValue(membershipUserAttribute)
.add()
.property().name(GroupMapperConfig.GROUPS_LDAP_FILTER)
.label("LDAP Filter")
.helpText("LDAP Filter adds additional custom filter to the whole query for retrieve LDAP groups. Leave this empty if no additional filtering is needed and you want to retrieve all groups from LDAP. Otherwise make sure that filter starts with '(' and ends with ')'")
.type(ProviderConfigProperty.STRING_TYPE)
.add()
.property().name(GroupMapperConfig.MODE)
.label("Mode")
.helpText("LDAP_ONLY means that all group mappings of users are retrieved from LDAP and saved into LDAP. READ_ONLY is Read-only LDAP mode where group mappings are " +
ProviderConfigurationBuilder config = ProviderConfigurationBuilder.create()
.property().name(GroupMapperConfig.GROUPS_DN)
.label("LDAP Groups DN")
.helpText("LDAP DN where are groups of this tree saved. For example 'ou=groups,dc=example,dc=org' ")
.type(ProviderConfigProperty.STRING_TYPE)
.add()
.property().name(GroupMapperConfig.GROUP_NAME_LDAP_ATTRIBUTE)
.label("Group Name LDAP Attribute")
.helpText("Name of LDAP attribute, which is used in group objects for name and RDN of group. Usually it will be 'cn' . In this case typical group/role object may have DN like 'cn=Group1,ou=groups,dc=example,dc=org' ")
.type(ProviderConfigProperty.STRING_TYPE)
.defaultValue(LDAPConstants.CN)
.add()
.property().name(GroupMapperConfig.GROUP_OBJECT_CLASSES)
.label("Group Object Classes")
.helpText("Object class (or classes) of the group object. It's divided by comma if more classes needed. In typical LDAP deployment it could be 'groupOfNames' . In Active Directory it's usually 'group' ")
.type(ProviderConfigProperty.STRING_TYPE)
.defaultValue(roleObjectClasses)
.add()
.property().name(GroupMapperConfig.PRESERVE_GROUP_INHERITANCE)
.label("Preserve Group Inheritance")
.helpText("Flag whether group inheritance from LDAP should be propagated to Keycloak. If false, then all LDAP groups will be mapped as flat top-level groups in Keycloak. Otherwise group inheritance is " +
"preserved into Keycloak, but the group sync might fail if LDAP structure contains recursions or multiple parent groups per child groups")
.type(ProviderConfigProperty.BOOLEAN_TYPE)
.defaultValue("true")
.add()
.property().name(GroupMapperConfig.MEMBERSHIP_LDAP_ATTRIBUTE)
.label("Membership LDAP Attribute")
.helpText("Name of LDAP attribute on group, which is used for membership mappings. Usually it will be 'member' ." +
"However when 'Membership Attribute Type' is 'UID' then 'Membership LDAP Attribute' could be typically 'memberUid' .")
.type(ProviderConfigProperty.STRING_TYPE)
.defaultValue(LDAPConstants.MEMBER)
.add()
.property().name(GroupMapperConfig.MEMBERSHIP_ATTRIBUTE_TYPE)
.label("Membership Attribute Type")
.helpText("DN means that LDAP group has it's members declared in form of their full DN. For example 'member: uid=john,ou=users,dc=example,dc=com' . " +
"UID means that LDAP group has it's members declared in form of pure user uids. For example 'memberUid: john' .")
.type(ProviderConfigProperty.LIST_TYPE)
.options(MEMBERSHIP_TYPES)
.defaultValue(MembershipType.DN.toString())
.add()
.property().name(RoleMapperConfig.MEMBERSHIP_USER_LDAP_ATTRIBUTE)
.label("Membership User LDAP Attribute")
.helpText("Used just if Membership Attribute Type is UID. It is name of LDAP attribute on user, which is used for membership mappings. Usually it will be 'uid' . For example if value of " +
"'Membership User LDAP Attribute' is 'uid' and " +
" LDAP group has 'memberUid: john', then it is expected that particular LDAP user will have attribute 'uid: john' .")
.type(ProviderConfigProperty.STRING_TYPE)
.defaultValue(membershipUserAttribute)
.add()
.property().name(GroupMapperConfig.GROUPS_LDAP_FILTER)
.label("LDAP Filter")
.helpText("LDAP Filter adds additional custom filter to the whole query for retrieve LDAP groups. Leave this empty if no additional filtering is needed and you want to retrieve all groups from LDAP. Otherwise make sure that filter starts with '(' and ends with ')'")
.type(ProviderConfigProperty.STRING_TYPE)
.add();
if (importEnabled) {
config.property().name(GroupMapperConfig.MODE)
.label("Mode")
.helpText("LDAP_ONLY means that all group mappings of users are retrieved from LDAP and saved into LDAP. READ_ONLY is Read-only LDAP mode where group mappings are " +
"retrieved from both LDAP and DB and merged together. New group joins are not saved to LDAP but to DB. IMPORT is Read-only LDAP mode where group mappings are " +
"retrieved from LDAP just at the time when user is imported from LDAP and then " +
"they are saved to local keycloak DB.")
.type(ProviderConfigProperty.LIST_TYPE)
.options(MODES)
.defaultValue(mode)
.add()
.property().name(GroupMapperConfig.USER_ROLES_RETRIEVE_STRATEGY)
.label("User Groups Retrieve Strategy")
.helpText("Specify how to retrieve groups of user. LOAD_GROUPS_BY_MEMBER_ATTRIBUTE means that roles of user will be retrieved by sending LDAP query to retrieve all groups where 'member' is our user. " +
"GET_GROUPS_FROM_USER_MEMBEROF_ATTRIBUTE means that groups of user will be retrieved from 'memberOf' attribute of our user. " +
"LOAD_GROUPS_BY_MEMBER_ATTRIBUTE_RECURSIVELY is applicable just in Active Directory and it means that groups of user will be retrieved recursively with usage of LDAP_MATCHING_RULE_IN_CHAIN Ldap extension.")
.type(ProviderConfigProperty.LIST_TYPE)
.options(ROLE_RETRIEVERS)
.defaultValue(GroupMapperConfig.LOAD_GROUPS_BY_MEMBER_ATTRIBUTE)
.add()
.property().name(GroupMapperConfig.MAPPED_GROUP_ATTRIBUTES)
.label("Mapped Group Attributes")
.helpText("List of names of attributes divided by comma. This points to the list of attributes on LDAP group, which will be mapped as attributes of Group in Keycloak. " +
"Leave this empty if no additional group attributes are required to be mapped in Keycloak. ")
.type(ProviderConfigProperty.STRING_TYPE)
.add()
.property().name(GroupMapperConfig.DROP_NON_EXISTING_GROUPS_DURING_SYNC)
.label("Drop non-existing groups during sync")
.helpText("If this flag is true, then during sync of groups from LDAP to Keycloak, we will keep just those Keycloak groups, which still exists in LDAP. Rest will be deleted")
.type(ProviderConfigProperty.BOOLEAN_TYPE)
.defaultValue("false")
.add()
.build();
.type(ProviderConfigProperty.LIST_TYPE)
.options(MODES)
.defaultValue(mode)
.add();
} else {
config.property().name(GroupMapperConfig.MODE)
.label("Mode")
.helpText("LDAP_ONLY means that specified group mappings are writable to LDAP. "
+ "READ_ONLY means that group mappings are not writable to LDAP.")
.type(ProviderConfigProperty.LIST_TYPE)
.options(NO_IMPORT_MODES)
.defaultValue(mode)
.add();
}
config.property().name(GroupMapperConfig.USER_ROLES_RETRIEVE_STRATEGY)
.label("User Groups Retrieve Strategy")
.helpText("Specify how to retrieve groups of user. LOAD_GROUPS_BY_MEMBER_ATTRIBUTE means that roles of user will be retrieved by sending LDAP query to retrieve all groups where 'member' is our user. " +
"GET_GROUPS_FROM_USER_MEMBEROF_ATTRIBUTE means that groups of user will be retrieved from 'memberOf' attribute of our user. " +
"LOAD_GROUPS_BY_MEMBER_ATTRIBUTE_RECURSIVELY is applicable just in Active Directory and it means that groups of user will be retrieved recursively with usage of LDAP_MATCHING_RULE_IN_CHAIN Ldap extension.")
.type(ProviderConfigProperty.LIST_TYPE)
.options(ROLE_RETRIEVERS)
.defaultValue(GroupMapperConfig.LOAD_GROUPS_BY_MEMBER_ATTRIBUTE)
.add()
.property().name(GroupMapperConfig.MAPPED_GROUP_ATTRIBUTES)
.label("Mapped Group Attributes")
.helpText("List of names of attributes divided by comma. This points to the list of attributes on LDAP group, which will be mapped as attributes of Group in Keycloak. " +
"Leave this empty if no additional group attributes are required to be mapped in Keycloak. ")
.type(ProviderConfigProperty.STRING_TYPE)
.add()
.property().name(GroupMapperConfig.DROP_NON_EXISTING_GROUPS_DURING_SYNC)
.label("Drop non-existing groups during sync")
.helpText("If this flag is true, then during sync of groups from LDAP to Keycloak, we will keep just those Keycloak groups, which still exists in LDAP. Rest will be deleted")
.type(ProviderConfigProperty.BOOLEAN_TYPE)
.defaultValue("false")
.add();
return config.build();
}
@Override
@ -197,6 +215,37 @@ public class GroupLDAPStorageMapperFactory extends AbstractLDAPStorageMapperFact
}
@Override
public void onParentUpdate(RealmModel realm, UserStorageProviderModel oldParent, UserStorageProviderModel newParent, ComponentModel mapperModel) {
if (!newParent.isImportEnabled()) {
if (new RoleMapperConfig(mapperModel).getMode() == LDAPGroupMapperMode.IMPORT) {
mapperModel.getConfig().putSingle(GroupMapperConfig.MODE, LDAPGroupMapperMode.READ_ONLY.toString());
realm.updateComponent(mapperModel);
}
}
}
@Override
public void onCreate(KeycloakSession session, RealmModel realm, ComponentModel model) {
ComponentModel parentModel = realm.getComponent(model.getParentId());
UserStorageProviderModel parent = new UserStorageProviderModel(parentModel);
onParentUpdate(realm, parent, parent, model);
}
@Override
public void onUpdate(KeycloakSession session, RealmModel realm, ComponentModel oldModel, ComponentModel newModel) {
ComponentModel parentModel = realm.getComponent(newModel.getParentId());
UserStorageProviderModel parent = new UserStorageProviderModel(parentModel);
onParentUpdate(realm, parent, parent, newModel);
}
@Override
public List<ProviderConfigProperty> getConfigProperties(RealmModel realm, ComponentModel parent) {
return getProps(parent);
}
@Override
public void validateConfiguration(KeycloakSession session, RealmModel realm, ComponentModel config) throws ComponentValidationException {
checkMandatoryConfigAttribute(GroupMapperConfig.GROUPS_DN, "LDAP Groups DN", config);

View file

@ -25,6 +25,7 @@ import org.keycloak.models.RealmModel;
import org.keycloak.provider.ProviderConfigProperty;
import org.keycloak.provider.ProviderConfigurationBuilder;
import org.keycloak.storage.UserStorageProvider;
import org.keycloak.storage.UserStorageProviderModel;
import org.keycloak.storage.ldap.LDAPConfig;
import org.keycloak.storage.ldap.LDAPStorageProvider;
import org.keycloak.storage.ldap.LDAPUtils;
@ -51,6 +52,7 @@ public class RoleLDAPStorageMapperFactory extends AbstractLDAPStorageMapperFacto
protected static final Map<String, UserRolesRetrieveStrategy> userRolesStrategies = new LinkedHashMap<>();
protected static final List<String> MEMBERSHIP_TYPES = new LinkedList<>();
protected static final List<String> MODES = new LinkedList<>();
protected static final List<String> NO_IMPORT_MODES = new LinkedList<>();
protected static final List<String> roleRetrievers;
static {
@ -65,6 +67,8 @@ public class RoleLDAPStorageMapperFactory extends AbstractLDAPStorageMapperFacto
for (LDAPGroupMapperMode mode : LDAPGroupMapperMode.values()) {
MODES.add(mode.toString());
}
NO_IMPORT_MODES.add(LDAPGroupMapperMode.LDAP_ONLY.toString());
NO_IMPORT_MODES.add(LDAPGroupMapperMode.READ_ONLY.toString());
roleRetrievers = new LinkedList<>(userRolesStrategies.keySet());
List<ProviderConfigProperty> config = getProps(null);
@ -75,91 +79,132 @@ public class RoleLDAPStorageMapperFactory extends AbstractLDAPStorageMapperFacto
String roleObjectClasses = LDAPConstants.GROUP_OF_NAMES;
String mode = LDAPGroupMapperMode.LDAP_ONLY.toString();
String membershipUserAttribute = LDAPConstants.UID;
boolean importEnabled = true;
if (parent != null) {
LDAPConfig config = new LDAPConfig(parent.getConfig());
roleObjectClasses = config.isActiveDirectory() ? LDAPConstants.GROUP : LDAPConstants.GROUP_OF_NAMES;
mode = config.getEditMode() == UserStorageProvider.EditMode.WRITABLE ? LDAPGroupMapperMode.LDAP_ONLY.toString() : LDAPGroupMapperMode.READ_ONLY.toString();
membershipUserAttribute = config.getUsernameLdapAttribute();
importEnabled = new UserStorageProviderModel(parent).isImportEnabled();
}
return ProviderConfigurationBuilder.create()
.property().name(RoleMapperConfig.ROLES_DN)
.label("LDAP Roles DN")
.helpText("LDAP DN where are roles of this tree saved. For example 'ou=finance,dc=example,dc=org' ")
.type(ProviderConfigProperty.STRING_TYPE)
.add()
.property().name(RoleMapperConfig.ROLE_NAME_LDAP_ATTRIBUTE)
.label("Role Name LDAP Attribute")
.helpText("Name of LDAP attribute, which is used in role objects for name and RDN of role. Usually it will be 'cn' . In this case typical group/role object may have DN like 'cn=role1,ou=finance,dc=example,dc=org' ")
.type(ProviderConfigProperty.STRING_TYPE)
.defaultValue(LDAPConstants.CN)
.add()
.property().name(RoleMapperConfig.ROLE_OBJECT_CLASSES)
.label("Role Object Classes")
.helpText("Object class (or classes) of the role object. It's divided by comma if more classes needed. In typical LDAP deployment it could be 'groupOfNames' . In Active Directory it's usually 'group' ")
.type(ProviderConfigProperty.STRING_TYPE)
.defaultValue(roleObjectClasses)
.add()
.property().name(RoleMapperConfig.MEMBERSHIP_LDAP_ATTRIBUTE)
.label("Membership LDAP Attribute")
.helpText("Name of LDAP attribute on role, which is used for membership mappings. Usually it will be 'member' ." +
"However when 'Membership Attribute Type' is 'UID' then 'Membership LDAP Attribute' could be typically 'memberUid' .")
.type(ProviderConfigProperty.STRING_TYPE)
.defaultValue(LDAPConstants.MEMBER)
.add()
.property().name(RoleMapperConfig.MEMBERSHIP_ATTRIBUTE_TYPE)
.label("Membership Attribute Type")
.helpText("DN means that LDAP role has it's members declared in form of their full DN. For example 'member: uid=john,ou=users,dc=example,dc=com' . " +
"UID means that LDAP role has it's members declared in form of pure user uids. For example 'memberUid: john' .")
.type(ProviderConfigProperty.LIST_TYPE)
.options(MEMBERSHIP_TYPES)
.defaultValue(MembershipType.DN.toString())
.add()
.property().name(RoleMapperConfig.MEMBERSHIP_USER_LDAP_ATTRIBUTE)
.label("Membership User LDAP Attribute")
.helpText("Used just if Membership Attribute Type is UID. It is name of LDAP attribute on user, which is used for membership mappings. Usually it will be 'uid' . For example if value of " +
"'Membership User LDAP Attribute' is 'uid' and " +
" LDAP group has 'memberUid: john', then it is expected that particular LDAP user will have attribute 'uid: john' .")
.type(ProviderConfigProperty.STRING_TYPE)
.defaultValue(membershipUserAttribute)
.add()
.property().name(RoleMapperConfig.ROLES_LDAP_FILTER)
.label("LDAP Filter")
.helpText("LDAP Filter adds additional custom filter to the whole query for retrieve LDAP roles. Leave this empty if no additional filtering is needed and you want to retrieve all roles from LDAP. Otherwise make sure that filter starts with '(' and ends with ')'")
.type(ProviderConfigProperty.STRING_TYPE)
.add()
.property().name(RoleMapperConfig.MODE)
.label("Mode")
.helpText("LDAP_ONLY means that all role mappings are retrieved from LDAP and saved into LDAP. READ_ONLY is Read-only LDAP mode where role mappings are " +
ProviderConfigurationBuilder config = ProviderConfigurationBuilder.create()
.property().name(RoleMapperConfig.ROLES_DN)
.label("LDAP Roles DN")
.helpText("LDAP DN where are roles of this tree saved. For example 'ou=finance,dc=example,dc=org' ")
.type(ProviderConfigProperty.STRING_TYPE)
.add()
.property().name(RoleMapperConfig.ROLE_NAME_LDAP_ATTRIBUTE)
.label("Role Name LDAP Attribute")
.helpText("Name of LDAP attribute, which is used in role objects for name and RDN of role. Usually it will be 'cn' . In this case typical group/role object may have DN like 'cn=role1,ou=finance,dc=example,dc=org' ")
.type(ProviderConfigProperty.STRING_TYPE)
.defaultValue(LDAPConstants.CN)
.add()
.property().name(RoleMapperConfig.ROLE_OBJECT_CLASSES)
.label("Role Object Classes")
.helpText("Object class (or classes) of the role object. It's divided by comma if more classes needed. In typical LDAP deployment it could be 'groupOfNames' . In Active Directory it's usually 'group' ")
.type(ProviderConfigProperty.STRING_TYPE)
.defaultValue(roleObjectClasses)
.add()
.property().name(RoleMapperConfig.MEMBERSHIP_LDAP_ATTRIBUTE)
.label("Membership LDAP Attribute")
.helpText("Name of LDAP attribute on role, which is used for membership mappings. Usually it will be 'member' ." +
"However when 'Membership Attribute Type' is 'UID' then 'Membership LDAP Attribute' could be typically 'memberUid' .")
.type(ProviderConfigProperty.STRING_TYPE)
.defaultValue(LDAPConstants.MEMBER)
.add()
.property().name(RoleMapperConfig.MEMBERSHIP_ATTRIBUTE_TYPE)
.label("Membership Attribute Type")
.helpText("DN means that LDAP role has it's members declared in form of their full DN. For example 'member: uid=john,ou=users,dc=example,dc=com' . " +
"UID means that LDAP role has it's members declared in form of pure user uids. For example 'memberUid: john' .")
.type(ProviderConfigProperty.LIST_TYPE)
.options(MEMBERSHIP_TYPES)
.defaultValue(MembershipType.DN.toString())
.add()
.property().name(RoleMapperConfig.MEMBERSHIP_USER_LDAP_ATTRIBUTE)
.label("Membership User LDAP Attribute")
.helpText("Used just if Membership Attribute Type is UID. It is name of LDAP attribute on user, which is used for membership mappings. Usually it will be 'uid' . For example if value of " +
"'Membership User LDAP Attribute' is 'uid' and " +
" LDAP group has 'memberUid: john', then it is expected that particular LDAP user will have attribute 'uid: john' .")
.type(ProviderConfigProperty.STRING_TYPE)
.defaultValue(membershipUserAttribute)
.add()
.property().name(RoleMapperConfig.ROLES_LDAP_FILTER)
.label("LDAP Filter")
.helpText("LDAP Filter adds additional custom filter to the whole query for retrieve LDAP roles. Leave this empty if no additional filtering is needed and you want to retrieve all roles from LDAP. Otherwise make sure that filter starts with '(' and ends with ')'")
.type(ProviderConfigProperty.STRING_TYPE)
.add();
if (importEnabled) {
config.property().name(RoleMapperConfig.MODE)
.label("Mode")
.helpText("LDAP_ONLY means that all role mappings are retrieved from LDAP and saved into LDAP. READ_ONLY is Read-only LDAP mode where role mappings are " +
"retrieved from both LDAP and DB and merged together. New role grants are not saved to LDAP but to DB. IMPORT is Read-only LDAP mode where role mappings are retrieved from LDAP just at the time when user is imported from LDAP and then " +
"they are saved to local keycloak DB.")
.type(ProviderConfigProperty.LIST_TYPE)
.options(MODES)
.defaultValue(mode)
.add()
.property().name(RoleMapperConfig.USER_ROLES_RETRIEVE_STRATEGY)
.label("User Roles Retrieve Strategy")
.helpText("Specify how to retrieve roles of user. LOAD_ROLES_BY_MEMBER_ATTRIBUTE means that roles of user will be retrieved by sending LDAP query to retrieve all roles where 'member' is our user. " +
"GET_ROLES_FROM_USER_MEMBEROF_ATTRIBUTE means that roles of user will be retrieved from 'memberOf' attribute of our user. " +
"LOAD_ROLES_BY_MEMBER_ATTRIBUTE_RECURSIVELY is applicable just in Active Directory and it means that roles of user will be retrieved recursively with usage of LDAP_MATCHING_RULE_IN_CHAIN Ldap extension.")
.type(ProviderConfigProperty.LIST_TYPE)
.options(roleRetrievers)
.defaultValue(RoleMapperConfig.LOAD_ROLES_BY_MEMBER_ATTRIBUTE)
.add()
.property().name(RoleMapperConfig.USE_REALM_ROLES_MAPPING)
.label("Use Realm Roles Mapping")
.helpText("If true, then LDAP role mappings will be mapped to realm role mappings in Keycloak. Otherwise it will be mapped to client role mappings")
.type(ProviderConfigProperty.BOOLEAN_TYPE)
.defaultValue("true")
.add()
.property().name(RoleMapperConfig.CLIENT_ID)
.label("Client ID")
.helpText("Client ID of client to which LDAP role mappings will be mapped. Applicable just if 'Use Realm Roles Mapping' is false")
.type(ProviderConfigProperty.CLIENT_LIST_TYPE)
.add()
.build();
.type(ProviderConfigProperty.LIST_TYPE)
.options(MODES)
.defaultValue(mode)
.add();
} else {
config.property().name(RoleMapperConfig.MODE)
.label("Mode")
.helpText("LDAP_ONLY means that specified role mappings are writable to LDAP. READ_ONLY means LDAP is readonly.")
.type(ProviderConfigProperty.LIST_TYPE)
.options(NO_IMPORT_MODES)
.defaultValue(mode)
.add();
}
config.property().name(RoleMapperConfig.USER_ROLES_RETRIEVE_STRATEGY)
.label("User Roles Retrieve Strategy")
.helpText("Specify how to retrieve roles of user. LOAD_ROLES_BY_MEMBER_ATTRIBUTE means that roles of user will be retrieved by sending LDAP query to retrieve all roles where 'member' is our user. " +
"GET_ROLES_FROM_USER_MEMBEROF_ATTRIBUTE means that roles of user will be retrieved from 'memberOf' attribute of our user. " +
"LOAD_ROLES_BY_MEMBER_ATTRIBUTE_RECURSIVELY is applicable just in Active Directory and it means that roles of user will be retrieved recursively with usage of LDAP_MATCHING_RULE_IN_CHAIN Ldap extension.")
.type(ProviderConfigProperty.LIST_TYPE)
.options(roleRetrievers)
.defaultValue(RoleMapperConfig.LOAD_ROLES_BY_MEMBER_ATTRIBUTE)
.add()
.property().name(RoleMapperConfig.USE_REALM_ROLES_MAPPING)
.label("Use Realm Roles Mapping")
.helpText("If true, then LDAP role mappings will be mapped to realm role mappings in Keycloak. Otherwise it will be mapped to client role mappings")
.type(ProviderConfigProperty.BOOLEAN_TYPE)
.defaultValue("true")
.add()
.property().name(RoleMapperConfig.CLIENT_ID)
.label("Client ID")
.helpText("Client ID of client to which LDAP role mappings will be mapped. Applicable just if 'Use Realm Roles Mapping' is false")
.type(ProviderConfigProperty.CLIENT_LIST_TYPE)
.add();
return config.build();
}
@Override
public void onParentUpdate(RealmModel realm, UserStorageProviderModel oldParent, UserStorageProviderModel newParent, ComponentModel mapperModel) {
if (!newParent.isImportEnabled()) {
if (new RoleMapperConfig(mapperModel).getMode() == LDAPGroupMapperMode.IMPORT) {
mapperModel.getConfig().putSingle(RoleMapperConfig.MODE, LDAPGroupMapperMode.READ_ONLY.toString());
realm.updateComponent(mapperModel);
}
}
}
@Override
public void onCreate(KeycloakSession session, RealmModel realm, ComponentModel model) {
ComponentModel parentModel = realm.getComponent(model.getParentId());
UserStorageProviderModel parent = new UserStorageProviderModel(parentModel);
onParentUpdate(realm, parent, parent, model);
}
@Override
public void onUpdate(KeycloakSession session, RealmModel realm, ComponentModel oldModel, ComponentModel newModel) {
ComponentModel parentModel = realm.getComponent(newModel.getParentId());
UserStorageProviderModel parent = new UserStorageProviderModel(parentModel);
onParentUpdate(realm, parent, parent, newModel);
}
@Override
public String getHelpText() {
@ -171,6 +216,11 @@ public class RoleLDAPStorageMapperFactory extends AbstractLDAPStorageMapperFacto
return configProperties;
}
@Override
public List<ProviderConfigProperty> getConfigProperties(RealmModel realm, ComponentModel parent) {
return getProps(parent);
}
@Override
public String getId() {
return PROVIDER_ID;

View file

@ -36,13 +36,37 @@ public interface UserStorageProviderResource {
* Action can be "triggerFullSync" or "triggerChangedUsersSync"
*
*
* @param componentId
* @param action
* @return
*/
@POST
@Path("{componentId}/sync")
@Produces(MediaType.APPLICATION_JSON)
SynchronizationResultRepresentation syncUsers(@QueryParam("action") String action);
SynchronizationResultRepresentation syncUsers(@PathParam("componentId") String componentId, @QueryParam("action") String action);
/**
* Remove imported users
*
*
* @param componentId
* @return
*/
@POST
@Path("{componentId}/remove-imported-users")
@Produces(MediaType.APPLICATION_JSON)
void removeImportedUsers(@PathParam("componentId") String componentId);
/**
* Unlink imported users from a storage provider
*
* @param componentId
* @return
*/
@POST
@Path("{componentId}/unlink-users")
@Produces(MediaType.APPLICATION_JSON)
void unlink(@PathParam("componentId") String componentId);
/**
* REST invocation for initiating sync for an ldap mapper. This method may be moved in the future. Right now

View file

@ -868,6 +868,21 @@ public class UserCacheSession implements UserCache {
}
@Override
public void removeImportedUsers(RealmModel realm, String storageProviderId) {
getDelegate().removeImportedUsers(realm, storageProviderId);
clear();
addRealmInvalidation(realm.getId()); // easier to just invalidate whole realm
}
@Override
public void unlinkUsers(RealmModel realm, String storageProviderId) {
getDelegate().unlinkUsers(realm, storageProviderId);
clear();
addRealmInvalidation(realm.getId()); // easier to just invalidate whole realm
}
private void addRealmInvalidation(String realmId) {
realmInvalidations.add(realmId);
invalidationEvents.add(UserCacheRealmInvalidationEvent.create(realmId));

View file

@ -380,38 +380,47 @@ public class JpaUserProvider implements UserProvider, UserCredentialStore {
.setParameter("realmId", realm.getId()).executeUpdate();
}
public void removeUserDataByLink(RealmModel realm, String linkId) {
@Override
public void removeImportedUsers(RealmModel realm, String storageProviderId) {
int num = em.createNamedQuery("deleteUserRoleMappingsByRealmAndLink")
.setParameter("realmId", realm.getId())
.setParameter("link", linkId)
.setParameter("link", storageProviderId)
.executeUpdate();
num = em.createNamedQuery("deleteUserRequiredActionsByRealmAndLink")
.setParameter("realmId", realm.getId())
.setParameter("link", linkId)
.setParameter("link", storageProviderId)
.executeUpdate();
num = em.createNamedQuery("deleteFederatedIdentityByRealmAndLink")
.setParameter("realmId", realm.getId())
.setParameter("link", linkId)
.setParameter("link", storageProviderId)
.executeUpdate();
num = em.createNamedQuery("deleteCredentialAttributeByRealmAndLink")
.setParameter("realmId", realm.getId())
.setParameter("link", linkId)
.setParameter("link", storageProviderId)
.executeUpdate();
num = em.createNamedQuery("deleteCredentialsByRealmAndLink")
.setParameter("realmId", realm.getId())
.setParameter("link", linkId)
.setParameter("link", storageProviderId)
.executeUpdate();
num = em.createNamedQuery("deleteUserAttributesByRealmAndLink")
.setParameter("realmId", realm.getId())
.setParameter("link", linkId)
.setParameter("link", storageProviderId)
.executeUpdate();
num = em.createNamedQuery("deleteUserGroupMembershipsByRealmAndLink")
.setParameter("realmId", realm.getId())
.setParameter("link", linkId)
.setParameter("link", storageProviderId)
.executeUpdate();
num = em.createNamedQuery("deleteUsersByRealmAndLink")
.setParameter("realmId", realm.getId())
.setParameter("link", linkId)
.setParameter("link", storageProviderId)
.executeUpdate();
}
@Override
public void unlinkUsers(RealmModel realm, String storageProviderId) {
em.createNamedQuery("unlinkUsers")
.setParameter("realmId", realm.getId())
.setParameter("link", storageProviderId)
.executeUpdate();
}
@ -714,7 +723,7 @@ public class JpaUserProvider implements UserProvider, UserCredentialStore {
@Override
public void preRemove(RealmModel realm, ComponentModel component) {
if (!component.getProviderType().equals(UserStorageProvider.class.getName())) return;
removeUserDataByLink(realm, component.getId());
removeImportedUsers(realm, component.getId());
}

View file

@ -50,7 +50,8 @@ import java.util.Collection;
@NamedQuery(name="getRealmUserByServiceAccount", query="select u from UserEntity u where u.serviceAccountClientLink = :clientInternalId and u.realmId = :realmId"),
@NamedQuery(name="getRealmUserCount", query="select count(u) from UserEntity u where u.realmId = :realmId"),
@NamedQuery(name="deleteUsersByRealm", query="delete from UserEntity u where u.realmId = :realmId"),
@NamedQuery(name="deleteUsersByRealmAndLink", query="delete from UserEntity u where u.realmId = :realmId and u.federationLink=:link")
@NamedQuery(name="deleteUsersByRealmAndLink", query="delete from UserEntity u where u.realmId = :realmId and u.federationLink=:link"),
@NamedQuery(name="unlinkUsers", query="update UserEntity u set u.federationLink = null where u.realmId = :realmId and u.federationLink=:link")
})
@Entity
@Table(name="USER_ENTITY", uniqueConstraints = {

View file

@ -60,7 +60,6 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern;
import org.keycloak.models.mongo.keycloak.entities.UserEntity;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
@ -621,8 +620,14 @@ public class MongoUserProvider implements UserProvider, UserCredentialStore {
@Override
public void preRemove(RealmModel realm, ComponentModel component) {
if (!component.getProviderType().equals(UserStorageProvider.class.getName())) return;
String providerId = component.getId();
removeImportedUsers(realm, providerId);
}
@Override
public void removeImportedUsers(RealmModel realm, String providerId) {
DBObject query = new QueryBuilder()
.and("federationLink").is(component.getId())
.and("federationLink").is(providerId)
.get();
List<MongoUserEntity> mongoUsers = getMongoStore().loadEntities(MongoUserEntity.class, query, invocationContext);
@ -635,6 +640,22 @@ public class MongoUserProvider implements UserProvider, UserCredentialStore {
}
}
@Override
public void unlinkUsers(RealmModel realm, String storageProviderId) {
DBObject query = new QueryBuilder()
.and("federationLink").is(storageProviderId)
.get();
List<MongoUserEntity> mongoUsers = getMongoStore().loadEntities(MongoUserEntity.class, query, invocationContext);
for (MongoUserEntity userEntity : mongoUsers) {
// Doing this way to ensure UserRemovedEvent triggered with proper callbacks.
UserAdapter user = new UserAdapter(session, realm, userEntity, invocationContext);
user.setFederationLink(null);
}
}
@Override
public void updateCredential(RealmModel realm, UserModel user, CredentialModel cred) {
MongoUserEntity mongoUser = getMongoUserEntity(user);

View file

@ -0,0 +1,136 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.models.utils;
import org.keycloak.models.GroupModel;
import org.keycloak.models.ModelReadOnlyException;
import org.keycloak.models.RoleModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.utils.UserModelDelegate;
import org.keycloak.storage.ReadOnlyException;
import java.util.List;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class ReadOnlyUserModelDelegate extends UserModelDelegate {
public ReadOnlyUserModelDelegate(UserModel delegate) {
super(delegate);
}
@Override
public void setUsername(String username) {
throw new ModelReadOnlyException();
}
@Override
public void setEnabled(boolean enabled) {
throw new ModelReadOnlyException();
}
@Override
public void setSingleAttribute(String name, String value) {
throw new ModelReadOnlyException();
}
@Override
public void setAttribute(String name, List<String> values) {
throw new ModelReadOnlyException();
}
@Override
public void removeAttribute(String name) {
throw new ModelReadOnlyException();
}
@Override
public void addRequiredAction(String action) {
throw new ModelReadOnlyException();
}
@Override
public void removeRequiredAction(String action) {
throw new ModelReadOnlyException();
}
@Override
public void addRequiredAction(RequiredAction action) {
throw new ModelReadOnlyException();
}
@Override
public void removeRequiredAction(RequiredAction action) {
throw new ModelReadOnlyException();
}
@Override
public void setFirstName(String firstName) {
throw new ModelReadOnlyException();
}
@Override
public void setLastName(String lastName) {
throw new ModelReadOnlyException();
}
@Override
public void setEmail(String email) {
throw new ModelReadOnlyException();
}
@Override
public void setEmailVerified(boolean verified) {
throw new ModelReadOnlyException();
}
@Override
public void deleteRoleMapping(RoleModel role) {
throw new ModelReadOnlyException();
}
@Override
public void setFederationLink(String link) {
throw new ModelReadOnlyException();
}
@Override
public void setServiceAccountClientLink(String clientInternalId) {
throw new ModelReadOnlyException();
}
@Override
public void setCreatedTimestamp(Long timestamp) {
throw new ModelReadOnlyException();
}
@Override
public void joinGroup(GroupModel group) {
throw new ModelReadOnlyException();
}
@Override
public void leaveGroup(GroupModel group) {
throw new ModelReadOnlyException();
}
@Override
public void grantRole(RoleModel role) {
throw new ModelReadOnlyException();
}
}

View file

@ -0,0 +1,379 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.storage.adapter;
import org.keycloak.common.util.MultivaluedHashMap;
import org.keycloak.common.util.Time;
import org.keycloak.models.ClientModel;
import org.keycloak.models.GroupModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.utils.DefaultRoles;
import org.keycloak.models.utils.RoleUtils;
import org.keycloak.storage.ReadOnlyException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedList;
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 InMemoryUserAdapter implements UserModel {
private String username;
private Long createdTimestamp = Time.currentTimeMillis();
private String firstName;
private String lastName;
private String email;
private boolean emailVerified;
private boolean enabled;
private String realmId;
private Set<String> roleIds = new HashSet<>();
private Set<String> groupIds = new HashSet<>();
private MultivaluedHashMap<String, String> attributes = new MultivaluedHashMap<>();
private Set<String> requiredActions = new HashSet<>();
private String federationLink;
private String serviceAccountClientLink;
private KeycloakSession session;
private RealmModel realm;
private String id;
private boolean readonly;
public InMemoryUserAdapter(KeycloakSession session, RealmModel realm, String id) {
this.session = session;
this.realm = realm;
this.id = id;
}
public void addDefaults() {
DefaultRoles.addDefaultRoles(realm, this);
for (GroupModel g : realm.getDefaultGroups()) {
joinGroup(g);
}
}
public void setReadonly(boolean flag) {
readonly = flag;
}
protected void checkReadonly() {
if (readonly) throw new ReadOnlyException("In memory user model is not writable");
}
@Override
public String getId() {
return id;
}
@Override
public String getUsername() {
return username;
}
@Override
public void setUsername(String username) {
checkReadonly();
this.username = username.toLowerCase();
}
@Override
public Long getCreatedTimestamp() {
return createdTimestamp;
}
@Override
public void setCreatedTimestamp(Long timestamp) {
checkReadonly();
this.createdTimestamp = timestamp;
}
@Override
public boolean isEnabled() {
return enabled;
}
@Override
public void setEnabled(boolean enabled) {
checkReadonly();
this.enabled = enabled;
}
@Override
public void setSingleAttribute(String name, String value) {
checkReadonly();
attributes.putSingle(name, value);
}
@Override
public void setAttribute(String name, List<String> values) {
checkReadonly();
attributes.put(name, values);
}
@Override
public void removeAttribute(String name) {
checkReadonly();
attributes.remove(name);
}
@Override
public String getFirstAttribute(String name) {
return attributes.getFirst(name);
}
@Override
public List<String> getAttribute(String name) {
List<String> value = attributes.get(name);
if (value == null) {
return new LinkedList<>();
}
return value;
}
@Override
public Map<String, List<String>> getAttributes() {
return attributes;
}
@Override
public Set<String> getRequiredActions() {
return requiredActions;
}
@Override
public void addRequiredAction(String action) {
checkReadonly();
requiredActions.add(action);
}
@Override
public void removeRequiredAction(String action) {
checkReadonly();
requiredActions.remove(action);
}
@Override
public void addRequiredAction(RequiredAction action) {
checkReadonly();
requiredActions.add(action.name());
}
@Override
public void removeRequiredAction(RequiredAction action) {
checkReadonly();
requiredActions.remove(action.name());
}
@Override
public String getFirstName() {
return firstName;
}
@Override
public void setFirstName(String firstName) {
checkReadonly();
this.firstName = firstName;
}
@Override
public String getLastName() {
return lastName;
}
@Override
public void setLastName(String lastName) {
checkReadonly();
this.lastName = lastName;
}
@Override
public String getEmail() {
return email;
}
@Override
public void setEmail(String email) {
checkReadonly();
if (email != null) email = email.toLowerCase();
this.email = email;
}
@Override
public boolean isEmailVerified() {
return emailVerified;
}
@Override
public void setEmailVerified(boolean verified) {
checkReadonly();
this.emailVerified = verified;
}
@Override
public Set<GroupModel> getGroups() {
if (groupIds.size() == 0) return Collections.EMPTY_SET;
Set<GroupModel> groups = new HashSet<>();
for (String id : groupIds) {
groups.add(realm.getGroupById(id));
}
return groups;
}
@Override
public void joinGroup(GroupModel group) {
checkReadonly();
groupIds.add(group.getId());
}
@Override
public void leaveGroup(GroupModel group) {
checkReadonly();
groupIds.remove(group.getId());
}
@Override
public boolean isMemberOf(GroupModel group) {
if (groupIds == null) return false;
if (groupIds.contains(group.getId())) return true;
Set<GroupModel> groups = getGroups();
return RoleUtils.isMember(groups, group);
}
@Override
public String getFederationLink() {
return federationLink;
}
@Override
public void setFederationLink(String link) {
checkReadonly();
this.federationLink = link;
}
@Override
public String getServiceAccountClientLink() {
return serviceAccountClientLink;
}
@Override
public void setServiceAccountClientLink(String clientInternalId) {
checkReadonly();
this.serviceAccountClientLink = clientInternalId;
}
@Override
public Set<RoleModel> getRealmRoleMappings() {
Set<RoleModel> allRoles = getRoleMappings();
// Filter to retrieve just realm roles
Set<RoleModel> realmRoles = new HashSet<RoleModel>();
for (RoleModel role : allRoles) {
if (role.getContainer() instanceof RealmModel) {
realmRoles.add(role);
}
}
return realmRoles;
}
@Override
public Set<RoleModel> getClientRoleMappings(ClientModel app) {
Set<RoleModel> result = new HashSet<RoleModel>();
Set<RoleModel> roles = getRoleMappings();
for (RoleModel role : roles) {
if (app.equals(role.getContainer())) {
result.add(role);
}
}
return result;
}
@Override
public boolean hasRole(RoleModel role) {
Set<RoleModel> roles = getRoleMappings();
return RoleUtils.hasRole(roles, role)
|| RoleUtils.hasRoleFromGroup(getGroups(), role, true);
}
@Override
public void grantRole(RoleModel role) {
roleIds.add(role.getId());
}
@Override
public Set<RoleModel> getRoleMappings() {
if (roleIds.size() == 0) return Collections.EMPTY_SET;
Set<RoleModel> roles = new HashSet<>();
for (String id : roleIds) {
roles.add(realm.getRoleById(id));
}
return roles;
}
@Override
public void deleteRoleMapping(RoleModel role) {
roleIds.remove(role.getId());
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || !(o instanceof UserModel)) return false;
UserModel that = (UserModel) o;
return that.getId().equals(getId());
}
@Override
public int hashCode() {
return getId().hashCode();
}
}

View file

@ -69,6 +69,22 @@ public interface UserProvider extends Provider,
UserModel addUser(RealmModel realm, String id, String username, boolean addDefaultRoles, boolean addDefaultRequiredActions);
void preRemove(RealmModel realm);
/**
* Removes any imported users from a specific User Storage Provider.
*
* @param realm
* @param storageProviderId
*/
void removeImportedUsers(RealmModel realm, String storageProviderId);
/**
* Set federation link to null to imported users of a specific User Storage Provider
*
* @param realm
* @param storageProviderId
*/
void unlinkUsers(RealmModel realm, String storageProviderId);
void preRemove(RealmModel realm, RoleModel role);
void preRemove(RealmModel realm, GroupModel group);

View file

@ -23,6 +23,9 @@ package org.keycloak.storage;
* @version $Revision: 1 $
*/
public class ReadOnlyException extends RuntimeException {
public ReadOnlyException() {
}
public ReadOnlyException(String message) {
super(message);
}

View file

@ -24,13 +24,11 @@ import org.keycloak.component.ComponentModel;
import org.keycloak.events.admin.OperationType;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.provider.ProviderFactory;
import org.keycloak.services.ServicesLogger;
import org.keycloak.services.managers.UserStorageSyncManager;
import org.keycloak.storage.UserStorageProvider;
import org.keycloak.storage.UserStorageProviderModel;
import org.keycloak.storage.ldap.LDAPStorageProvider;
import org.keycloak.storage.ldap.LDAPStorageProviderFactory;
import org.keycloak.storage.ldap.mappers.LDAPStorageMapper;
import org.keycloak.storage.user.SynchronizationResult;
@ -94,7 +92,7 @@ public class UserStorageProviderResource {
@NoCache
@Produces(MediaType.APPLICATION_JSON)
public SynchronizationResult syncUsers(@PathParam("id") String id,
@QueryParam("action") String action) {
@QueryParam("action") String action) {
auth.requireManage();
ComponentModel model = realm.getComponent(id);
@ -129,6 +127,53 @@ public class UserStorageProviderResource {
return syncResult;
}
/**
* Remove imported users
*
*
* @param id
* @return
*/
@POST
@Path("{id}/remove-imported-users")
@NoCache
public void removeImportedUsers(@PathParam("id") String id) {
auth.requireManage();
ComponentModel model = realm.getComponent(id);
if (model == null) {
throw new NotFoundException("Could not find component");
}
if (!model.getProviderType().equals(UserStorageProvider.class.getName())) {
throw new NotFoundException("found, but not a UserStorageProvider");
}
session.users().removeImportedUsers(realm, id);
}
/**
* Unlink imported users from a storage provider
*
*
* @param id
* @return
*/
@POST
@Path("{id}/unlink-users")
@NoCache
public void unlinkUsers(@PathParam("id") String id) {
auth.requireManage();
ComponentModel model = realm.getComponent(id);
if (model == null) {
throw new NotFoundException("Could not find component");
}
if (!model.getProviderType().equals(UserStorageProvider.class.getName())) {
throw new NotFoundException("found, but not a UserStorageProvider");
}
session.users().unlinkUsers(realm, id);
}
/**
* Trigger sync of mapper data related to ldap mapper (roles, groups, ...)
*

View file

@ -614,6 +614,16 @@ public class UserStorageManager implements UserProvider, OnUserCache, OnCreateCo
}
@Override
public void removeImportedUsers(RealmModel realm, String storageProviderId) {
localStorage().removeImportedUsers(realm, storageProviderId);
}
@Override
public void unlinkUsers(RealmModel realm, String storageProviderId) {
localStorage().unlinkUsers(realm, storageProviderId);
}
@Override
public void onCache(RealmModel realm, CachedUserModel user, UserModel delegate) {
if (StorageId.isLocalStorage(user)) {
@ -652,4 +662,6 @@ public class UserStorageManager implements UserProvider, OnUserCache, OnCreateCo
}
}
}

View file

@ -99,6 +99,26 @@ public class AdapterTest {
@Rule
public AdapterTestStrategy testStrategy = new AdapterTestStrategy("http://localhost:8081/auth", "http://localhost:8081", keycloakRule);
//@Test
public void testUi() throws Exception {
Thread.sleep(1000000000);
}
public static class MySuper {
}
public static class Base extends MySuper {
public Class superClass() {
return super.getClass();
}
}
@Test
public void testBase() {
System.out.println(new Base().superClass().getName());
}
@Test
public void testLoginSSOAndLogout() throws Exception {
testStrategy.testLoginSSOMax();

View file

@ -77,9 +77,9 @@ public class LDAPMultipleAttributesTest {
});
private static ComponentModel ldapModel = null;
public static UserStorageProviderModel ldapModel = null;
private static KeycloakRule keycloakRule = new KeycloakRule(new KeycloakRule.KeycloakSetup() {
public static KeycloakRule keycloakRule = new KeycloakRule(new KeycloakRule.KeycloakSetup() {
@Override
public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
@ -94,7 +94,7 @@ public class LDAPMultipleAttributesTest {
model.setPriority(0);
model.setProviderId(LDAPStorageProviderFactory.PROVIDER_NAME);
model.setConfig(ldapConfig);
ldapModel = appRealm.addComponentModel(model);
ldapModel = new UserStorageProviderModel(appRealm.addComponentModel(model));
LDAPTestUtils.addZipCodeLDAPMapper(appRealm, ldapModel);
LDAPTestUtils.addUserAttributeMapper(appRealm, ldapModel, "streetMapper", "street", LDAPConstants.STREET);
@ -148,15 +148,27 @@ public class LDAPMultipleAttributesTest {
@WebResource
protected LoginPage loginPage;
protected void checkUserAndImportMode(KeycloakSession session, RealmModel realm, String username, String expectedFirstName, String expectedLastName, String expectedEmail, String expectedPostalCode) {
LDAPTestUtils.assertUserImported(session.userLocalStorage(), realm, "jbrown", "James", "Brown", "jbrown@keycloak.org", "88441");
}
protected void checkImportMode(KeycloakSession session, RealmModel realm, UserModel user) {
Assert.assertNotNull(session.userLocalStorage().getUserById(user.getId(), realm));
}
@Test
public void testModel() {
KeycloakSession session = keycloakRule.startSession();
try {
session.userCache().clear();
RealmModel appRealm = session.realms().getRealmByName("test");
LDAPTestUtils.assertUserImported(session.users(), appRealm, "jbrown", "James", "Brown", "jbrown@keycloak.org", "88441");
checkUserAndImportMode(session, appRealm, "jbrown", "James", "Brown", "jbrown@keycloak.org", "88441");
UserModel user = session.users().getUserByUsername("bwilson", appRealm);
checkImportMode(session, appRealm, user);
Assert.assertEquals("bwilson@keycloak.org", user.getEmail());
Assert.assertEquals("Bruce", user.getFirstName());

View file

@ -19,6 +19,9 @@ package org.keycloak.testsuite.federation.storage.ldap;
import org.jboss.logging.Logger;
import org.junit.Assert;
import org.junit.Assume;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.ClassRule;
import org.junit.FixMethodOrder;
import org.junit.Rule;
@ -27,9 +30,12 @@ import org.junit.rules.RuleChain;
import org.junit.rules.TestRule;
import org.junit.runners.MethodSorters;
import org.keycloak.OAuth2Constants;
import org.keycloak.admin.client.Keycloak;
import org.keycloak.common.Profile;
import org.keycloak.common.util.MultivaluedHashMap;
import org.keycloak.component.ComponentModel;
import org.keycloak.credential.CredentialModel;
import org.keycloak.models.Constants;
import org.keycloak.storage.UserStorageProvider;
import org.keycloak.storage.UserStorageProviderModel;
import org.keycloak.storage.ldap.LDAPConfig;
@ -67,7 +73,10 @@ import org.openqa.selenium.WebDriver;
import java.util.List;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.MASTER;
import static org.junit.Assert.assertEquals;
import static org.keycloak.models.AdminRoles.ADMIN;
import static org.keycloak.testsuite.Constants.AUTH_SERVER_ROOT;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
@ -174,6 +183,67 @@ public class LDAPProvidersIntegrationTest {
}
private Keycloak adminClient;
@Before
public void onBefore() {
adminClient = Keycloak.getInstance(AUTH_SERVER_ROOT, MASTER, ADMIN, ADMIN, Constants.ADMIN_CLI_CLIENT_ID);
}
@Test
public void testRemoveImportedUsers() {
KeycloakSession session = keycloakRule.startSession();
try {
RealmManager manager = new RealmManager(session);
RealmModel appRealm = manager.getRealm("test");
UserModel user = session.users().getUserByUsername("johnkeycloak", appRealm);
Assert.assertEquals(ldapModel.getId(), user.getFederationLink());
} finally {
keycloakRule.stopSession(session, true);
}
adminClient.realm("test").userStorage().removeImportedUsers(ldapModel.getId());
session = keycloakRule.startSession();
try {
RealmManager manager = new RealmManager(session);
RealmModel appRealm = manager.getRealm("test");
UserModel user = session.userLocalStorage().getUserByUsername("johnkeycloak", appRealm);
Assert.assertNull(user);
} finally {
keycloakRule.stopSession(session, true);
}
}
// test name prefixed with zz to make sure it runs last as we are unlinking imported users
@Test
public void zzTestUnlinkUsers() {
KeycloakSession session = keycloakRule.startSession();
try {
RealmManager manager = new RealmManager(session);
RealmModel appRealm = manager.getRealm("test");
UserModel user = session.users().getUserByUsername("johnkeycloak", appRealm);
Assert.assertEquals(ldapModel.getId(), user.getFederationLink());
} finally {
keycloakRule.stopSession(session, true);
}
adminClient.realm("test").userStorage().unlink(ldapModel.getId());
session = keycloakRule.startSession();
try {
RealmManager manager = new RealmManager(session);
RealmModel appRealm = manager.getRealm("test");
UserModel user = session.users().getUserByUsername("johnkeycloak", appRealm);
Assert.assertNotNull(user);
Assert.assertNull(user.getFederationLink());
} finally {
keycloakRule.stopSession(session, true);
}
}
@Test
public void caseInSensitiveImport() {
KeycloakSession session = keycloakRule.startSession();

View file

@ -150,6 +150,14 @@ public class LDAPTestUtils {
Assert.assertEquals(expectedPostalCode, user.getFirstAttribute("postal_code"));
}
public static void assertLoaded(UserModel user, String username, String expectedFirstName, String expectedLastName, String expectedEmail, String expectedPostalCode) {
Assert.assertNotNull(user);
Assert.assertEquals(expectedFirstName, user.getFirstName());
Assert.assertEquals(expectedLastName, user.getLastName());
Assert.assertEquals(expectedEmail, user.getEmail());
Assert.assertEquals(expectedPostalCode, user.getFirstAttribute("postal_code"));
}
// CRUD model mappers

View file

@ -0,0 +1,327 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.testsuite.federation.storage.ldap.noimport;
import org.junit.Assert;
import org.junit.ClassRule;
import org.junit.FixMethodOrder;
import org.junit.Test;
import org.junit.rules.RuleChain;
import org.junit.rules.TestRule;
import org.junit.runners.MethodSorters;
import org.keycloak.common.util.MultivaluedHashMap;
import org.keycloak.component.ComponentModel;
import org.keycloak.models.GroupModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.LDAPConstants;
import org.keycloak.models.ModelException;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.services.managers.RealmManager;
import org.keycloak.storage.UserStorageProvider;
import org.keycloak.storage.UserStorageProviderModel;
import org.keycloak.storage.ldap.LDAPConfig;
import org.keycloak.storage.ldap.LDAPStorageProvider;
import org.keycloak.storage.ldap.LDAPStorageProviderFactory;
import org.keycloak.storage.ldap.LDAPUtils;
import org.keycloak.storage.ldap.idm.model.LDAPDn;
import org.keycloak.storage.ldap.idm.model.LDAPObject;
import org.keycloak.storage.ldap.mappers.membership.LDAPGroupMapperMode;
import org.keycloak.storage.ldap.mappers.membership.MembershipType;
import org.keycloak.storage.ldap.mappers.membership.group.GroupLDAPStorageMapper;
import org.keycloak.storage.ldap.mappers.membership.group.GroupLDAPStorageMapperFactory;
import org.keycloak.storage.ldap.mappers.membership.group.GroupMapperConfig;
import org.keycloak.testsuite.federation.storage.ldap.LDAPTestUtils;
import org.keycloak.testsuite.rule.KeycloakRule;
import org.keycloak.testsuite.rule.LDAPRule;
import java.util.List;
import java.util.Set;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
public class LDAPGroupMapperNoImportTest {
private static LDAPRule ldapRule = new LDAPRule();
private static ComponentModel ldapModel = null;
private static String descriptionAttrName = null;
static class GroupTestKeycloakSetup extends KeycloakRule.KeycloakSetup {
private final LDAPRule ldapRule;
ComponentModel ldapModel = null;
String descriptionAttrName = null;
public GroupTestKeycloakSetup(LDAPRule ldapRule) {
this.ldapRule = ldapRule;
}
@Override
public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
LDAPTestUtils.addLocalUser(manager.getSession(), appRealm, "mary", "mary@test.com", "password-app");
LDAPTestUtils.addLocalUser(manager.getSession(), appRealm, "john", "john@test.com", "password-app");
MultivaluedHashMap<String,String> ldapConfig = LDAPTestUtils.getLdapRuleConfig(ldapRule);
ldapConfig.putSingle(LDAPConstants.SYNC_REGISTRATIONS, "true");
ldapConfig.putSingle(LDAPConstants.EDIT_MODE, UserStorageProvider.EditMode.WRITABLE.toString());
UserStorageProviderModel model = new UserStorageProviderModel();
model.setLastSync(0);
model.setChangedSyncPeriod(-1);
model.setFullSyncPeriod(-1);
model.setName("test-ldap");
model.setPriority(0);
model.setProviderId(LDAPStorageProviderFactory.PROVIDER_NAME);
model.setConfig(ldapConfig);
model.setImportEnabled(false);
ldapModel = appRealm.addComponentModel(model);
LDAPStorageProvider ldapFedProvider = LDAPTestUtils.getLdapProvider(session, ldapModel);
descriptionAttrName = ldapFedProvider.getLdapIdentityStore().getConfig().isActiveDirectory() ? "displayName" : "description";
// Add group mapper
LDAPTestUtils.addOrUpdateGroupMapper(appRealm, ldapModel, LDAPGroupMapperMode.LDAP_ONLY, descriptionAttrName);
// Remove all LDAP groups
LDAPTestUtils.removeAllLDAPGroups(session, appRealm, ldapModel, "groupsMapper");
// Add some groups for testing
LDAPObject group1 = LDAPTestUtils.createLDAPGroup(manager.getSession(), appRealm, ldapModel, "group1", descriptionAttrName, "group1 - description");
LDAPObject group11 = LDAPTestUtils.createLDAPGroup(manager.getSession(), appRealm, ldapModel, "group11");
LDAPObject group12 = LDAPTestUtils.createLDAPGroup(manager.getSession(), appRealm, ldapModel, "group12", descriptionAttrName, "group12 - description");
LDAPUtils.addMember(ldapFedProvider, MembershipType.DN, LDAPConstants.MEMBER, "not-used", group1, group11, false);
LDAPUtils.addMember(ldapFedProvider, MembershipType.DN, LDAPConstants.MEMBER, "not-used", group1, group12, true);
// Sync LDAP groups to Keycloak DB
ComponentModel mapperModel = LDAPTestUtils.getSubcomponentByName(appRealm, ldapModel, "groupsMapper");
new GroupLDAPStorageMapperFactory().create(session, mapperModel).syncDataFromFederationProviderToKeycloak(appRealm);
// Delete all LDAP users
LDAPTestUtils.removeAllLDAPUsers(ldapFedProvider, appRealm);
// Add some LDAP users for testing
LDAPObject john = LDAPTestUtils.addLDAPUser(ldapFedProvider, appRealm, "johnkeycloak", "John", "Doe", "john@email.org", null, "1234");
LDAPTestUtils.updateLDAPPassword(ldapFedProvider, john, "Password1");
LDAPObject mary = LDAPTestUtils.addLDAPUser(ldapFedProvider, appRealm, "marykeycloak", "Mary", "Kelly", "mary@email.org", null, "5678");
LDAPTestUtils.updateLDAPPassword(ldapFedProvider, mary, "Password1");
LDAPObject rob = LDAPTestUtils.addLDAPUser(ldapFedProvider, appRealm, "robkeycloak", "Rob", "Brown", "rob@email.org", null, "8910");
LDAPTestUtils.updateLDAPPassword(ldapFedProvider, rob, "Password1");
LDAPObject james = LDAPTestUtils.addLDAPUser(ldapFedProvider, appRealm, "jameskeycloak", "James", "Brown", "james@email.org", null, "8910");
LDAPTestUtils.updateLDAPPassword(ldapFedProvider, james, "Password1");
postSetup(appRealm, ldapFedProvider);
}
void postSetup(RealmModel appRealm, LDAPStorageProvider ldapProvider) {
LDAPGroupMapperNoImportTest.ldapModel = this.ldapModel;
LDAPGroupMapperNoImportTest.descriptionAttrName = this.descriptionAttrName;
}
}
private static KeycloakRule keycloakRule = new KeycloakRule(new GroupTestKeycloakSetup(ldapRule));
@ClassRule
public static TestRule chain = RuleChain
.outerRule(ldapRule)
.around(keycloakRule);
@Test
public void test01ReadGroupMappings() {
KeycloakSession session = keycloakRule.startSession();
try {
RealmModel appRealm = session.realms().getRealmByName("test");
ComponentModel mapperModel = LDAPTestUtils.getSubcomponentByName(appRealm, ldapModel, "groupsMapper");
LDAPTestUtils.updateGroupMapperConfigOptions(mapperModel, GroupMapperConfig.MODE, LDAPGroupMapperMode.LDAP_ONLY.toString());
appRealm.updateComponent(mapperModel);
LDAPStorageProvider ldapProvider = LDAPTestUtils.getLdapProvider(session, ldapModel);
GroupLDAPStorageMapper groupMapper = LDAPTestUtils.getGroupMapper(mapperModel, ldapProvider, appRealm);
LDAPObject maryLdap = ldapProvider.loadLDAPUserByUsername(appRealm, "marykeycloak");
groupMapper.addGroupMappingInLDAP(appRealm, "group1", maryLdap);
groupMapper.addGroupMappingInLDAP(appRealm, "group11", maryLdap);
} finally {
keycloakRule.stopSession(session, true);
}
session = keycloakRule.startSession();
try {
session.userCache().clear();
System.out.println("starting test01ReadGroupMappings");
RealmModel appRealm = session.realms().getRealmByName("test");
GroupModel group1 = KeycloakModelUtils.findGroupByPath(appRealm, "/group1");
Assert.assertNotNull(group1);
GroupModel group11 = KeycloakModelUtils.findGroupByPath(appRealm, "/group1/group11");
Assert.assertNotNull(group11);
GroupModel group12 = KeycloakModelUtils.findGroupByPath(appRealm, "/group1/group12");
Assert.assertNotNull(group12);
UserModel mary = session.users().getUserByUsername("marykeycloak", appRealm);
// make sure we are in no-import mode
Assert.assertNull(session.userLocalStorage().getUserByUsername("marykeycloak", appRealm));
// Assert that mary has both LDAP and DB mapped groups
Set<GroupModel> maryGroups = mary.getGroups();
Assert.assertEquals(2, maryGroups.size());
Assert.assertTrue(maryGroups.contains(group1));
Assert.assertTrue(maryGroups.contains(group11));
// Check through userProvider
List<UserModel> group1Members = session.users().getGroupMembers(appRealm, group1, 0, 10);
List<UserModel> group11Members = session.users().getGroupMembers(appRealm, group11, 0, 10);
Assert.assertEquals(1, group1Members.size());
Assert.assertEquals("marykeycloak", group1Members.get(0).getUsername());
Assert.assertEquals(1, group11Members.size());
Assert.assertEquals("marykeycloak", group11Members.get(0).getUsername());
// Delete role mappings directly in LDAP
LDAPStorageProvider ldapProvider = LDAPTestUtils.getLdapProvider(session, ldapModel);
ComponentModel mapperModel = LDAPTestUtils.getSubcomponentByName(appRealm,ldapModel, "groupsMapper");
GroupLDAPStorageMapper groupMapper = LDAPTestUtils.getGroupMapper(mapperModel, ldapProvider, appRealm);
LDAPObject maryLdap = ldapProvider.loadLDAPUserByUsername(appRealm, "marykeycloak");
deleteGroupMappingsInLDAP(groupMapper, maryLdap, "group1");
deleteGroupMappingsInLDAP(groupMapper, maryLdap, "group11");
} finally {
keycloakRule.stopSession(session, true);
}
}
@Test
public void test02WriteGroupMappings() {
KeycloakSession session = keycloakRule.startSession();
try {
RealmModel appRealm = session.realms().getRealmByName("test");
ComponentModel mapperModel = LDAPTestUtils.getSubcomponentByName(appRealm, ldapModel, "groupsMapper");
LDAPTestUtils.updateGroupMapperConfigOptions(mapperModel, GroupMapperConfig.MODE, LDAPGroupMapperMode.LDAP_ONLY.toString());
appRealm.updateComponent(mapperModel);
UserModel john = session.users().getUserByUsername("johnkeycloak", appRealm);
UserModel mary = session.users().getUserByUsername("marykeycloak", appRealm);
// make sure we are in no-import mode
Assert.assertNull(session.userLocalStorage().getUserByUsername("johnkeycloak", appRealm));
Assert.assertNull(session.userLocalStorage().getUserByUsername("marykeycloak", appRealm));
// 1 - Grant some groups in LDAP
// This group should already exists as it was imported from LDAP
GroupModel group1 = KeycloakModelUtils.findGroupByPath(appRealm, "/group1");
john.joinGroup(group1);
// This group should already exists as it was imported from LDAP
GroupModel group11 = KeycloakModelUtils.findGroupByPath(appRealm, "/group1/group11");
mary.joinGroup(group11);
// This group should already exists as it was imported from LDAP
GroupModel group12 = KeycloakModelUtils.findGroupByPath(appRealm, "/group1/group12");
john.joinGroup(group12);
mary.joinGroup(group12);
} finally {
keycloakRule.stopSession(session, true);
}
session = keycloakRule.startSession();
try {
session.userCache().clear(); // clear cache to make sure we're reloading from LDAP.
RealmModel appRealm = session.realms().getRealmByName("test");
GroupModel group1 = KeycloakModelUtils.findGroupByPath(appRealm, "/group1");
GroupModel group11 = KeycloakModelUtils.findGroupByPath(appRealm, "/group1/group11");
GroupModel group12 = KeycloakModelUtils.findGroupByPath(appRealm, "/group1/group12");
UserModel john = session.users().getUserByUsername("johnkeycloak", appRealm);
UserModel mary = session.users().getUserByUsername("marykeycloak", appRealm);
// make sure we are in no-import mode
Assert.assertNull(session.userLocalStorage().getUserByUsername("johnkeycloak", appRealm));
Assert.assertNull(session.userLocalStorage().getUserByUsername("marykeycloak", appRealm));
// 3 - Check that group mappings are in LDAP and hence available through federation
Set<GroupModel> johnGroups = john.getGroups();
Assert.assertEquals(2, johnGroups.size());
Assert.assertTrue(johnGroups.contains(group1));
Assert.assertFalse(johnGroups.contains(group11));
Assert.assertTrue(johnGroups.contains(group12));
// 4 - Check through userProvider
List<UserModel> group1Members = session.users().getGroupMembers(appRealm, group1, 0, 10);
List<UserModel> group11Members = session.users().getGroupMembers(appRealm, group11, 0, 10);
List<UserModel> group12Members = session.users().getGroupMembers(appRealm, group12, 0, 10);
Assert.assertEquals(1, group1Members.size());
Assert.assertEquals("johnkeycloak", group1Members.get(0).getUsername());
Assert.assertEquals(1, group11Members.size());
Assert.assertEquals("marykeycloak", group11Members.get(0).getUsername());
Assert.assertEquals(2, group12Members.size());
// 4 - Delete some group mappings and check they are deleted
john.leaveGroup(group1);
john.leaveGroup(group12);
mary.leaveGroup(group1);
mary.leaveGroup(group11);
mary.leaveGroup(group12);
} finally {
keycloakRule.stopSession(session, true);
}
session = keycloakRule.startSession();
try {
session.userCache().clear(); // clear cache to make sure we're reloading from LDAP.
RealmModel appRealm = session.realms().getRealmByName("test");
UserModel john = session.users().getUserByUsername("johnkeycloak", appRealm);
UserModel mary = session.users().getUserByUsername("marykeycloak", appRealm);
// make sure we are in no-import mode
Assert.assertNull(session.userLocalStorage().getUserByUsername("johnkeycloak", appRealm));
Assert.assertNull(session.userLocalStorage().getUserByUsername("marykeycloak", appRealm));
session.userCache().clear(); // clear cache to make sure we're reloading from LDAP.
Set<GroupModel> johnGroups = john.getGroups();
Assert.assertEquals(0, johnGroups.size());
Set<GroupModel> maryGroups = mary.getGroups();
Assert.assertEquals(0, maryGroups.size());
} finally {
keycloakRule.stopSession(session, true);
}
}
private void deleteGroupMappingsInLDAP(GroupLDAPStorageMapper groupMapper, LDAPObject ldapUser, String groupName) {
LDAPObject ldapGroup = groupMapper.loadLDAPGroupByName(groupName);
groupMapper.deleteGroupMappingInLDAP(ldapUser, ldapGroup);
}
}

View file

@ -0,0 +1,112 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.testsuite.federation.storage.ldap.noimport;
import org.junit.Assert;
import org.junit.Before;
import org.junit.ClassRule;
import org.junit.FixMethodOrder;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.RuleChain;
import org.junit.rules.TestRule;
import org.junit.runners.MethodSorters;
import org.keycloak.OAuth2Constants;
import org.keycloak.common.util.MultivaluedHashMap;
import org.keycloak.component.ComponentModel;
import org.keycloak.models.ClientModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.LDAPConstants;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.protocol.oidc.OIDCLoginProtocolService;
import org.keycloak.protocol.oidc.mappers.UserAttributeMapper;
import org.keycloak.services.managers.RealmManager;
import org.keycloak.storage.UserStorageProvider;
import org.keycloak.storage.UserStorageProviderModel;
import org.keycloak.storage.ldap.LDAPStorageProvider;
import org.keycloak.storage.ldap.LDAPStorageProviderFactory;
import org.keycloak.storage.ldap.idm.model.LDAPObject;
import org.keycloak.testsuite.OAuthClient;
import org.keycloak.testsuite.federation.storage.ldap.LDAPExampleServlet;
import org.keycloak.testsuite.federation.storage.ldap.LDAPMultipleAttributesTest;
import org.keycloak.testsuite.federation.storage.ldap.LDAPTestUtils;
import org.keycloak.testsuite.pages.LoginPage;
import org.keycloak.testsuite.rule.KeycloakRule;
import org.keycloak.testsuite.rule.LDAPRule;
import org.keycloak.testsuite.rule.WebResource;
import org.keycloak.testsuite.rule.WebRule;
import org.openqa.selenium.WebDriver;
import javax.ws.rs.core.UriBuilder;
import java.net.URL;
import java.util.Arrays;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
public class LDAPMultipleAttributesNoImportTest extends LDAPMultipleAttributesTest {
@Override
protected void checkUserAndImportMode(KeycloakSession session, RealmModel realm, String username, String expectedFirstName, String expectedLastName, String expectedEmail, String expectedPostalCode) {
UserModel user = session.users().getUserByUsername(username, realm);
Assert.assertNull(session.userLocalStorage().getUserByUsername(username, realm));
LDAPTestUtils.assertLoaded(user, username, expectedFirstName, expectedLastName, expectedEmail, expectedPostalCode);
}
@Override
protected void checkImportMode(KeycloakSession session, RealmModel realm, UserModel user) {
Assert.assertNull(session.userLocalStorage().getUserById(user.getId(), realm));
}
@Before
public void setNoImportMode() throws Exception {
KeycloakSession session = keycloakRule.startSession();
try {
session.userCache().clear();
RealmModel appRealm = session.realms().getRealmByName("test");
ldapModel.setImportEnabled(false);
appRealm.updateComponent(ldapModel);
} finally {
keycloakRule.stopSession(session, true);
}
}
@Test
public void testModel() {
super.testModel();
}
@Test
public void ldapPortalEndToEndTest() {
super.ldapPortalEndToEndTest();
}
}

View file

@ -0,0 +1,849 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.testsuite.federation.storage.ldap.noimport;
import org.jboss.logging.Logger;
import org.junit.Assert;
import org.junit.ClassRule;
import org.junit.FixMethodOrder;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.RuleChain;
import org.junit.rules.TestRule;
import org.junit.runners.MethodSorters;
import org.keycloak.OAuth2Constants;
import org.keycloak.common.util.MultivaluedHashMap;
import org.keycloak.component.ComponentModel;
import org.keycloak.credential.CredentialModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.LDAPConstants;
import org.keycloak.models.ModelException;
import org.keycloak.models.ModelReadOnlyException;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleModel;
import org.keycloak.models.UserCredentialModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.representations.AccessToken;
import org.keycloak.services.managers.RealmManager;
import org.keycloak.storage.StorageId;
import org.keycloak.storage.UserStorageProvider;
import org.keycloak.storage.UserStorageProviderModel;
import org.keycloak.storage.ldap.LDAPConfig;
import org.keycloak.storage.ldap.LDAPStorageProvider;
import org.keycloak.storage.ldap.LDAPStorageProviderFactory;
import org.keycloak.storage.ldap.idm.model.LDAPObject;
import org.keycloak.storage.ldap.mappers.FullNameLDAPStorageMapper;
import org.keycloak.storage.ldap.mappers.FullNameLDAPStorageMapperFactory;
import org.keycloak.storage.ldap.mappers.HardcodedLDAPRoleStorageMapper;
import org.keycloak.storage.ldap.mappers.HardcodedLDAPRoleStorageMapperFactory;
import org.keycloak.storage.ldap.mappers.LDAPStorageMapper;
import org.keycloak.storage.ldap.mappers.UserAttributeLDAPStorageMapper;
import org.keycloak.testsuite.OAuthClient;
import org.keycloak.testsuite.federation.storage.ldap.LDAPTestUtils;
import org.keycloak.testsuite.pages.AccountPasswordPage;
import org.keycloak.testsuite.pages.AccountUpdateProfilePage;
import org.keycloak.testsuite.pages.AppPage;
import org.keycloak.testsuite.pages.LoginPage;
import org.keycloak.testsuite.pages.RegisterPage;
import org.keycloak.testsuite.rule.KeycloakRule;
import org.keycloak.testsuite.rule.LDAPRule;
import org.keycloak.testsuite.rule.WebResource;
import org.keycloak.testsuite.rule.WebRule;
import org.openqa.selenium.WebDriver;
import java.util.List;
import static org.junit.Assert.assertEquals;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
public class LDAPProvidersIntegrationNoImportTest {
private static final Logger log = Logger.getLogger(LDAPProvidersIntegrationNoImportTest.class);
private static LDAPRule ldapRule = new LDAPRule();
private static ComponentModel ldapModel = null;
private static KeycloakRule keycloakRule = new KeycloakRule(new KeycloakRule.KeycloakSetup() {
@Override
public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
LDAPTestUtils.addLocalUser(manager.getSession(), appRealm, "marykeycloak", "mary@test.com", "password-app");
MultivaluedHashMap<String,String> ldapConfig = LDAPTestUtils.getLdapRuleConfig(ldapRule);
ldapConfig.putSingle(LDAPConstants.SYNC_REGISTRATIONS, "true");
ldapConfig.putSingle(LDAPConstants.EDIT_MODE, UserStorageProvider.EditMode.WRITABLE.toString());
UserStorageProviderModel model = new UserStorageProviderModel();
model.setLastSync(0);
model.setChangedSyncPeriod(-1);
model.setFullSyncPeriod(-1);
model.setName("test-ldap");
model.setPriority(0);
model.setProviderId(LDAPStorageProviderFactory.PROVIDER_NAME);
model.setConfig(ldapConfig);
model.setImportEnabled(false);
ldapModel = appRealm.addComponentModel(model);
LDAPTestUtils.addZipCodeLDAPMapper(appRealm, ldapModel);
// Delete all LDAP users and add some new for testing
LDAPStorageProvider ldapFedProvider = LDAPTestUtils.getLdapProvider(session, ldapModel);
LDAPTestUtils.removeAllLDAPUsers(ldapFedProvider, appRealm);
LDAPObject john = LDAPTestUtils.addLDAPUser(ldapFedProvider, appRealm, "johnkeycloak", "John", "Doe", "john@email.org", null, "1234");
LDAPTestUtils.updateLDAPPassword(ldapFedProvider, john, "Password1");
LDAPObject existing = LDAPTestUtils.addLDAPUser(ldapFedProvider, appRealm, "existing", "Existing", "Foo", "existing@email.org", null, "5678");
appRealm.getClientByClientId("test-app").setDirectAccessGrantsEnabled(true);
}
});
@ClassRule
public static TestRule chain = RuleChain
.outerRule(ldapRule)
.around(keycloakRule);
@Rule
public WebRule webRule = new WebRule(this);
@WebResource
protected OAuthClient oauth;
@WebResource
protected WebDriver driver;
@WebResource
protected AppPage appPage;
@WebResource
protected RegisterPage registerPage;
@WebResource
protected LoginPage loginPage;
@WebResource
protected AccountUpdateProfilePage profilePage;
@WebResource
protected AccountPasswordPage changePasswordPage;
// @Test
// @Ignore
// public void runit() throws Exception {
// Thread.sleep(10000000);
//
// }
/**
* KEYCLOAK-3986
*
*/
@Test
public void testSyncRegistrationOff() {
KeycloakSession session = keycloakRule.startSession();
try {
RealmManager manager = new RealmManager(session);
RealmModel appRealm = manager.getRealm("test");
UserStorageProviderModel newModel = new UserStorageProviderModel(ldapModel);
newModel.getConfig().putSingle(LDAPConstants.SYNC_REGISTRATIONS, "false");
appRealm.updateComponent(newModel);
UserModel newUser1 = session.users().addUser(appRealm, "newUser1");
Assert.assertTrue(StorageId.isLocalStorage(newUser1));
} finally {
keycloakRule.stopSession(session, false);
}
}
@Test
public void caseInSensitiveImport() {
KeycloakSession session = keycloakRule.startSession();
try {
RealmManager manager = new RealmManager(session);
RealmModel appRealm = manager.getRealm("test");
LDAPStorageProvider ldapFedProvider = LDAPTestUtils.getLdapProvider(session, ldapModel);
LDAPObject jbrown2 = LDAPTestUtils.addLDAPUser(ldapFedProvider, appRealm, "JBrown2", "John", "Brown2", "jbrown2@email.org", null, "1234");
LDAPTestUtils.updateLDAPPassword(ldapFedProvider, jbrown2, "Password1");
LDAPObject jbrown3 = LDAPTestUtils.addLDAPUser(ldapFedProvider, appRealm, "jbrown3", "John", "Brown3", "JBrown3@email.org", null, "1234");
LDAPTestUtils.updateLDAPPassword(ldapFedProvider, jbrown3, "Password1");
} finally {
keycloakRule.stopSession(session, true);
}
loginSuccessAndLogout("jbrown2", "Password1");
loginSuccessAndLogout("JBrown2", "Password1");
loginSuccessAndLogout("jbrown2@email.org", "Password1");
loginSuccessAndLogout("JBrown2@email.org", "Password1");
loginSuccessAndLogout("jbrown3", "Password1");
loginSuccessAndLogout("JBrown3", "Password1");
loginSuccessAndLogout("jbrown3@email.org", "Password1");
loginSuccessAndLogout("JBrown3@email.org", "Password1");
}
private void loginSuccessAndLogout(String username, String password) {
loginPage.open();
loginPage.login(username, password);
Assert.assertEquals(AppPage.RequestType.AUTH_RESPONSE, appPage.getRequestType());
Assert.assertNotNull(oauth.getCurrentQuery().get(OAuth2Constants.CODE));
oauth.openLogout();
}
@Test
public void caseInsensitiveSearch() {
KeycloakSession session = keycloakRule.startSession();
try {
RealmManager manager = new RealmManager(session);
RealmModel appRealm = manager.getRealm("test");
LDAPStorageProvider ldapFedProvider = LDAPTestUtils.getLdapProvider(session, ldapModel);
LDAPObject jbrown4 = LDAPTestUtils.addLDAPUser(ldapFedProvider, appRealm, "JBrown4", "John", "Brown4", "jbrown4@email.org", null, "1234");
LDAPTestUtils.updateLDAPPassword(ldapFedProvider, jbrown4, "Password1");
LDAPObject jbrown5 = LDAPTestUtils.addLDAPUser(ldapFedProvider, appRealm, "jbrown5", "John", "Brown5", "JBrown5@Email.org", null, "1234");
LDAPTestUtils.updateLDAPPassword(ldapFedProvider, jbrown5, "Password1");
} finally {
keycloakRule.stopSession(session, true);
}
session = keycloakRule.startSession();
try {
RealmManager manager = new RealmManager(session);
RealmModel appRealm = manager.getRealm("test");
// search by username
List<UserModel> users = session.users().searchForUser("JBROwn4", appRealm);
Assert.assertEquals(1, users.size());
UserModel user4 = users.get(0);
Assert.assertEquals("jbrown4", user4.getUsername());
Assert.assertEquals("jbrown4@email.org", user4.getEmail());
// search by email
users = session.users().searchForUser("JBROwn5@eMAil.org", appRealm);
Assert.assertEquals(1, users.size());
UserModel user5 = users.get(0);
Assert.assertEquals("jbrown5", user5.getUsername());
Assert.assertEquals("jbrown5@email.org", user5.getEmail());
} finally {
keycloakRule.stopSession(session, true);
}
}
@Test
public void deleteFederationLink() {
loginLdap();
{
KeycloakSession session = keycloakRule.startSession();
try {
RealmManager manager = new RealmManager(session);
RealmModel appRealm = manager.getRealm("test");
appRealm.removeComponent(ldapModel);
Assert.assertEquals(0, appRealm.getComponents(appRealm.getId(), UserStorageProvider.class.getName()).size());
} finally {
keycloakRule.stopSession(session, true);
}
}
loginPage.open();
loginPage.login("johnkeycloak", "Password1");
loginPage.assertCurrent();
Assert.assertEquals("Invalid username or password.", loginPage.getError());
{
KeycloakSession session = keycloakRule.startSession();
try {
RealmManager manager = new RealmManager(session);
RealmModel appRealm = manager.getRealm("test");
ldapModel.setId(null);
ldapModel = appRealm.addComponentModel(ldapModel);
LDAPTestUtils.addZipCodeLDAPMapper(appRealm, ldapModel);
} finally {
keycloakRule.stopSession(session, true);
}
}
loginLdap();
}
@Test
public void loginClassic() {
loginPage.open();
loginPage.login("marykeycloak", "password-app");
Assert.assertEquals(AppPage.RequestType.AUTH_RESPONSE, appPage.getRequestType());
Assert.assertNotNull(oauth.getCurrentQuery().get(OAuth2Constants.CODE));
}
@Test
public void loginLdap() {
loginPage.open();
loginPage.login("johnkeycloak", "Password1");
Assert.assertEquals(AppPage.RequestType.AUTH_RESPONSE, appPage.getRequestType());
Assert.assertNotNull(oauth.getCurrentQuery().get(OAuth2Constants.CODE));
profilePage.open();
Assert.assertEquals("John", profilePage.getFirstName());
Assert.assertEquals("Doe", profilePage.getLastName());
Assert.assertEquals("john@email.org", profilePage.getEmail());
}
@Test
public void loginLdapWithDirectGrant() throws Exception {
OAuthClient.AccessTokenResponse response = oauth.doGrantAccessTokenRequest("password", "johnkeycloak", "Password1");
assertEquals(200, response.getStatusCode());
AccessToken accessToken = oauth.verifyToken(response.getAccessToken());
response = oauth.doGrantAccessTokenRequest("password", "johnkeycloak", "");
assertEquals(401, response.getStatusCode());
}
@Test
public void loginLdapWithEmail() {
loginPage.open();
loginPage.login("john@email.org", "Password1");
Assert.assertEquals(AppPage.RequestType.AUTH_RESPONSE, appPage.getRequestType());
Assert.assertNotNull(oauth.getCurrentQuery().get(OAuth2Constants.CODE));
}
@Test
public void loginLdapWithoutPassword() {
loginPage.open();
loginPage.login("john@email.org", "");
Assert.assertEquals("Invalid username or password.", loginPage.getError());
}
@Test
public void passwordChangeLdap() throws Exception {
changePasswordPage.open();
loginPage.login("johnkeycloak", "Password1");
changePasswordPage.changePassword("Password1", "New-password1", "New-password1");
Assert.assertEquals("Your password has been updated.", profilePage.getSuccess());
changePasswordPage.logout();
loginPage.open();
loginPage.login("johnkeycloak", "Bad-password1");
Assert.assertEquals("Invalid username or password.", loginPage.getError());
loginPage.open();
loginPage.login("johnkeycloak", "New-password1");
Assert.assertEquals(AppPage.RequestType.AUTH_RESPONSE, appPage.getRequestType());
// Change password back to previous value
changePasswordPage.open();
changePasswordPage.changePassword("New-password1", "Password1", "Password1");
Assert.assertEquals("Your password has been updated.", profilePage.getSuccess());
}
@Test
public void registerExistingLdapUser() {
loginPage.open();
loginPage.clickRegister();
registerPage.assertCurrent();
// check existing username
registerPage.register("firstName", "lastName", "email@mail.cz", "existing", "Password1", "Password1");
registerPage.assertCurrent();
Assert.assertEquals("Username already exists.", registerPage.getError());
// Check existing email
registerPage.register("firstName", "lastName", "existing@email.org", "nonExisting", "Password1", "Password1");
registerPage.assertCurrent();
Assert.assertEquals("Email already exists.", registerPage.getError());
}
@Test
public void registerUserLdapSuccess() {
loginPage.open();
loginPage.clickRegister();
registerPage.assertCurrent();
registerPage.register("firstName", "lastName", "email2@check.cz", "registerUserSuccess2", "Password1", "Password1");
Assert.assertEquals(AppPage.RequestType.AUTH_RESPONSE, appPage.getRequestType());
KeycloakSession session = keycloakRule.startSession();
try {
RealmModel appRealm = session.realms().getRealmByName("test");
UserModel user = session.users().getUserByUsername("registerUserSuccess2", appRealm);
Assert.assertNotNull(user);
Assert.assertFalse(StorageId.isLocalStorage(user));
Assert.assertEquals(StorageId.providerId(user.getId()), ldapModel.getId());
Assert.assertEquals("registerusersuccess2", user.getUsername());
Assert.assertEquals("firstName", user.getFirstName());
Assert.assertEquals("lastName", user.getLastName());
Assert.assertTrue(user.isEnabled());
} finally {
keycloakRule.stopSession(session, false);
}
}
@Test
public void testCaseSensitiveAttributeName() {
KeycloakSession session = keycloakRule.startSession();
try {
RealmModel appRealm = new RealmManager(session).getRealmByName("test");
LDAPStorageProvider ldapFedProvider = LDAPTestUtils.getLdapProvider(session, ldapModel);
LDAPObject johnZip = LDAPTestUtils.addLDAPUser(ldapFedProvider, appRealm, "johnzip", "John", "Zip", "johnzip@email.org", null, "12398");
// Remove default zipcode mapper and add the mapper for "POstalCode" to test case sensitivity
ComponentModel currentZipMapper = LDAPTestUtils.getSubcomponentByName(appRealm, ldapModel, "zipCodeMapper");
appRealm.removeComponent(currentZipMapper);
LDAPTestUtils.addUserAttributeMapper(appRealm, ldapModel, "zipCodeMapper-cs", "postal_code", "POstalCode");
// Fetch user from LDAP and check that postalCode is filled
UserModel user = session.users().getUserByUsername("johnzip", appRealm);
String postalCode = user.getFirstAttribute("postal_code");
Assert.assertEquals("12398", postalCode);
} finally {
keycloakRule.stopSession(session, false);
}
}
@Test
public void testCommaInUsername() {
KeycloakSession session = keycloakRule.startSession();
boolean skip = false;
try {
RealmModel appRealm = new RealmManager(session).getRealmByName("test");
LDAPStorageProvider ldapFedProvider = LDAPTestUtils.getLdapProvider(session, ldapModel);
// Workaround as comma is not allowed in sAMAccountName on active directory. So we will skip the test for this configuration
LDAPConfig config = ldapFedProvider.getLdapIdentityStore().getConfig();
if (config.isActiveDirectory() && config.getUsernameLdapAttribute().equals(LDAPConstants.SAM_ACCOUNT_NAME)) {
skip = true;
}
if (!skip) {
LDAPObject johnComma = LDAPTestUtils.addLDAPUser(ldapFedProvider, appRealm, "john,comma", "John", "Comma", "johncomma@email.org", null, "12387");
LDAPTestUtils.updateLDAPPassword(ldapFedProvider, johnComma, "Password1");
LDAPObject johnPlus = LDAPTestUtils.addLDAPUser(ldapFedProvider, appRealm, "john+plus,comma", "John", "Plus", "johnplus@email.org", null, "12387");
LDAPTestUtils.updateLDAPPassword(ldapFedProvider, johnPlus, "Password1");
}
} finally {
keycloakRule.stopSession(session, false);
}
if (!skip) {
// Try to import the user with comma in username into Keycloak
loginSuccessAndLogout("john,comma", "Password1");
loginSuccessAndLogout("john+plus,comma", "Password1");
}
}
//@Test // don't think we should support this, bburke
public void testDirectLDAPUpdate() {
KeycloakSession session = keycloakRule.startSession();
try {
RealmModel appRealm = new RealmManager(session).getRealmByName("test");
LDAPStorageProvider ldapFedProvider = LDAPTestUtils.getLdapProvider(session, ldapModel);
LDAPObject johnDirect = LDAPTestUtils.addLDAPUser(ldapFedProvider, appRealm, "johndirect", "John", "Direct", "johndirect@email.org", null, "12399");
// Fetch user from LDAP and check that postalCode is filled
UserModel user = session.users().getUserByUsername("johndirect", appRealm);
String postalCode = user.getFirstAttribute("postal_code");
Assert.assertEquals("12399", postalCode);
// Directly update user in LDAP
johnDirect.setSingleAttribute(LDAPConstants.POSTAL_CODE, "12400");
johnDirect.setSingleAttribute(LDAPConstants.SN, "DirectLDAPUpdated");
ldapFedProvider.getLdapIdentityStore().update(johnDirect);
} finally {
keycloakRule.stopSession(session, true);
}
session = keycloakRule.startSession();
try {
RealmModel appRealm = new RealmManager(session).getRealmByName("test");
UserModel user = session.users().getUserByUsername("johndirect", appRealm);
// Verify that postalCode is still the same as we read it's value from Keycloak DB
user = session.users().getUserByUsername("johndirect", appRealm);
String postalCode = user.getFirstAttribute("postal_code");
Assert.assertEquals("12399", postalCode);
// Check user.getAttributes()
postalCode = user.getAttributes().get("postal_code").get(0);
Assert.assertEquals("12399", postalCode);
// LastName is new as lastName mapper will read the value from LDAP
String lastName = user.getLastName();
Assert.assertEquals("DirectLDAPUpdated", lastName);
} finally {
keycloakRule.stopSession(session, true);
}
session = keycloakRule.startSession();
try {
RealmModel appRealm = new RealmManager(session).getRealmByName("test");
// Update postalCode mapper to always read the value from LDAP
ComponentModel zipMapper = LDAPTestUtils.getSubcomponentByName(appRealm, ldapModel, "zipCodeMapper");
zipMapper.getConfig().putSingle(UserAttributeLDAPStorageMapper.ALWAYS_READ_VALUE_FROM_LDAP, "true");
appRealm.updateComponent(zipMapper);
// Update lastName mapper to read the value from Keycloak DB
ComponentModel lastNameMapper = LDAPTestUtils.getSubcomponentByName(appRealm, ldapModel, "last name");
lastNameMapper.getConfig().putSingle(UserAttributeLDAPStorageMapper.ALWAYS_READ_VALUE_FROM_LDAP, "false");
appRealm.updateComponent(lastNameMapper);
// Verify that postalCode is read from LDAP now
UserModel user = session.users().getUserByUsername("johndirect", appRealm);
String postalCode = user.getFirstAttribute("postal_code");
Assert.assertEquals("12400", postalCode);
// Check user.getAttributes()
postalCode = user.getAttributes().get("postal_code").get(0);
Assert.assertEquals("12400", postalCode);
Assert.assertFalse(user.getAttributes().containsKey(UserModel.LAST_NAME));
// lastName is read from Keycloak DB now
String lastName = user.getLastName();
Assert.assertEquals("Direct", lastName);
} finally {
keycloakRule.stopSession(session, false);
}
}
// TODO: Rather separate test for fullNameMapper to better test all the possibilities
@Test
public void testFullNameMapper() {
KeycloakSession session = keycloakRule.startSession();
ComponentModel firstNameMapper = null;
try {
RealmModel appRealm = new RealmManager(session).getRealmByName("test");
// assert that user "fullnameUser" is not in local DB
Assert.assertNull(session.users().getUserByUsername("fullname", appRealm));
// Add the user with some fullName into LDAP directly. Ensure that fullName is saved into "cn" attribute in LDAP (currently mapped to model firstName)
LDAPStorageProvider ldapFedProvider = LDAPTestUtils.getLdapProvider(session, ldapModel);
LDAPTestUtils.addLDAPUser(ldapFedProvider, appRealm, "fullname", "James Dee", "Dee", "fullname@email.org", null, "4578");
// add fullname mapper to the provider and remove "firstNameMapper". For this test, we will simply map full name to the LDAP attribute, which was before firstName ( "givenName" on active directory, "cn" on other LDAP servers)
firstNameMapper = LDAPTestUtils.getSubcomponentByName(appRealm, ldapModel, "first name");
String ldapFirstNameAttributeName = firstNameMapper.getConfig().getFirst(UserAttributeLDAPStorageMapper.LDAP_ATTRIBUTE);
appRealm.removeComponent(firstNameMapper);
ComponentModel fullNameMapperModel = KeycloakModelUtils.createComponentModel("full name", ldapModel.getId(), FullNameLDAPStorageMapperFactory.PROVIDER_ID, LDAPStorageMapper.class.getName(),
FullNameLDAPStorageMapper.LDAP_FULL_NAME_ATTRIBUTE, ldapFirstNameAttributeName,
FullNameLDAPStorageMapper.READ_ONLY, "false");
appRealm.addComponentModel(fullNameMapperModel);
} finally {
keycloakRule.stopSession(session, true);
}
session = keycloakRule.startSession();
try {
RealmModel appRealm = new RealmManager(session).getRealmByName("test");
// Assert user is successfully imported in Keycloak DB now with correct firstName and lastName
LDAPTestUtils.assertUserImported(session.users(), appRealm, "fullname", "James", "Dee", "fullname@email.org", "4578");
} finally {
keycloakRule.stopSession(session, true);
}
// Assert changing user in Keycloak will change him in LDAP too...
session = keycloakRule.startSession();
try {
RealmModel appRealm = new RealmManager(session).getRealmByName("test");
UserModel fullnameUser = session.users().getUserByUsername("fullname", appRealm);
fullnameUser.setFirstName("James2");
fullnameUser.setLastName("Dee2");
} finally {
keycloakRule.stopSession(session, true);
}
// Assert changed user available in Keycloak
session = keycloakRule.startSession();
try {
RealmModel appRealm = new RealmManager(session).getRealmByName("test");
// Assert user is successfully imported in Keycloak DB now with correct firstName and lastName
LDAPTestUtils.assertUserImported(session.users(), appRealm, "fullname", "James2", "Dee2", "fullname@email.org", "4578");
// Remove "fullnameUser" to assert he is removed from LDAP. Revert mappers to previous state
UserModel fullnameUser = session.users().getUserByUsername("fullname", appRealm);
session.users().removeUser(appRealm, fullnameUser);
// Revert mappers
ComponentModel fullNameMapperModel = LDAPTestUtils.getSubcomponentByName(appRealm, ldapModel, "full name");
appRealm.removeComponent(fullNameMapperModel);
firstNameMapper.setId(null);
appRealm.addComponentModel(firstNameMapper);
} finally {
keycloakRule.stopSession(session, true);
}
}
@Test
public void testHardcodedRoleMapper() {
KeycloakSession session = keycloakRule.startSession();
ComponentModel firstNameMapper = null;
try {
RealmModel appRealm = new RealmManager(session).getRealmByName("test");
RoleModel hardcodedRole = appRealm.addRole("hardcoded-role");
// assert that user "johnkeycloak" doesn't have hardcoded role
UserModel john = session.users().getUserByUsername("johnkeycloak", appRealm);
Assert.assertFalse(john.hasRole(hardcodedRole));
ComponentModel hardcodedMapperModel = KeycloakModelUtils.createComponentModel("hardcoded role", ldapModel.getId(), HardcodedLDAPRoleStorageMapperFactory.PROVIDER_ID, LDAPStorageMapper.class.getName(),
HardcodedLDAPRoleStorageMapper.ROLE, "hardcoded-role");
appRealm.addComponentModel(hardcodedMapperModel);
} finally {
keycloakRule.stopSession(session, true);
}
session = keycloakRule.startSession();
try {
RealmModel appRealm = new RealmManager(session).getRealmByName("test");
RoleModel hardcodedRole = appRealm.getRole("hardcoded-role");
// Assert user is successfully imported in Keycloak DB now with correct firstName and lastName
UserModel john = session.users().getUserByUsername("johnkeycloak", appRealm);
Assert.assertTrue(john.hasRole(hardcodedRole));
// Can't remove user from hardcoded role
try {
john.deleteRoleMapping(hardcodedRole);
Assert.fail("Didn't expected to remove role mapping");
} catch (ModelException expected) {
}
// Revert mappers
ComponentModel hardcodedMapperModel = LDAPTestUtils.getSubcomponentByName(appRealm, ldapModel, "hardcoded role");
appRealm.removeComponent(hardcodedMapperModel);
} finally {
keycloakRule.stopSession(session, true);
}
}
@Test
public void testImportExistingUserFromLDAP() throws Exception {
// Add LDAP user with same email like existing model user
keycloakRule.update(new KeycloakRule.KeycloakSetup() {
@Override
public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
LDAPStorageProvider ldapFedProvider = LDAPTestUtils.getLdapProvider(session, ldapModel);
LDAPTestUtils.addLDAPUser(ldapFedProvider, appRealm, "marykeycloak", "Mary1", "Kelly1", "mary1@email.org", null, "123");
LDAPTestUtils.addLDAPUser(ldapFedProvider, appRealm, "mary-duplicatemail", "Mary2", "Kelly2", "mary@test.com", null, "123");
LDAPObject marynoemail = LDAPTestUtils.addLDAPUser(ldapFedProvider, appRealm, "marynoemail", "Mary1", "Kelly1", null, null, "123");
LDAPTestUtils.updateLDAPPassword(ldapFedProvider, marynoemail, "Password1");
}
});
// Try to import the duplicated LDAP user into Keycloak
loginPage.open();
loginPage.login("mary-duplicatemail", "password");
Assert.assertEquals("Email already exists.", loginPage.getError());
loginPage.login("mary1@email.org", "password");
Assert.assertEquals("Username already exists.", loginPage.getError());
loginSuccessAndLogout("marynoemail", "Password1");
}
@Test
public void testReadonly() {
KeycloakSession session = keycloakRule.startSession();
try {
RealmModel appRealm = session.realms().getRealmByName("test");
UserStorageProviderModel model = new UserStorageProviderModel(ldapModel);
model.getConfig().putSingle(LDAPConstants.EDIT_MODE, UserStorageProvider.EditMode.READ_ONLY.toString());
appRealm.updateComponent(model);
UserModel user = session.users().getUserByUsername("johnkeycloak", appRealm);
Assert.assertNotNull(user);
try {
user.setEmail("error@error.com");
Assert.fail("should fail");
} catch (ModelReadOnlyException e) {
}
try {
user.setLastName("Berk");
Assert.fail("should fail");
} catch (ModelReadOnlyException e) {
}
try {
user.setFirstName("Bilbo");
Assert.fail("should fail");
} catch (ModelReadOnlyException e) {
}
try {
UserCredentialModel cred = UserCredentialModel.password("PoopyPoop1", true);
session.userCredentialManager().updateCredential(appRealm, user, cred);
Assert.fail("should fail");
} catch (ModelReadOnlyException e) {
}
Assert.assertTrue(session.users().removeUser(appRealm, user));
} finally {
keycloakRule.stopSession(session, false);
}
session = keycloakRule.startSession();
try {
RealmModel appRealm = session.realms().getRealmByName("test");
Assert.assertEquals(UserStorageProvider.EditMode.WRITABLE.toString(), appRealm.getComponent(ldapModel.getId()).getConfig().getFirst(LDAPConstants.EDIT_MODE));
} finally {
keycloakRule.stopSession(session, false);
}
}
@Test
public void testRemoveFederatedUser() {
/*
{
KeycloakSession session = keycloakRule.startSession();
RealmModel appRealm = session.realms().getRealmByName("test");
UserModel user = session.users().getUserByUsername("registerUserSuccess2", appRealm);
keycloakRule.stopSession(session, true);
if (user == null) {
registerUserLdapSuccess();
}
}
*/
KeycloakSession session = keycloakRule.startSession();
try {
RealmModel appRealm = session.realms().getRealmByName("test");
UserModel user = session.users().getUserByUsername("registerUserSuccess2", appRealm);
Assert.assertNotNull(user);
Assert.assertFalse(StorageId.isLocalStorage(user));
Assert.assertEquals(StorageId.providerId(user.getId()), ldapModel.getId());
Assert.assertTrue(session.users().removeUser(appRealm, user));
Assert.assertNull(session.users().getUserByUsername("registerUserSuccess2", appRealm));
} finally {
keycloakRule.stopSession(session, true);
}
}
@Test
public void testSearch() {
KeycloakSession session = keycloakRule.startSession();
try {
RealmModel appRealm = session.realms().getRealmByName("test");
LDAPStorageProvider ldapProvider = LDAPTestUtils.getLdapProvider(session, ldapModel);
LDAPTestUtils.addLDAPUser(ldapProvider, appRealm, "username1", "John1", "Doel1", "user1@email.org", null, "121");
LDAPTestUtils.addLDAPUser(ldapProvider, appRealm, "username2", "John2", "Doel2", "user2@email.org", null, "122");
LDAPTestUtils.addLDAPUser(ldapProvider, appRealm, "username3", "John3", "Doel3", "user3@email.org", null, "123");
LDAPTestUtils.addLDAPUser(ldapProvider, appRealm, "username4", "John4", "Doel4", "user4@email.org", null, "124");
// Users are not at local store at this moment
Assert.assertNull(session.userLocalStorage().getUserByUsername("username1", appRealm));
Assert.assertNull(session.userLocalStorage().getUserByUsername("username2", appRealm));
Assert.assertNull(session.userLocalStorage().getUserByUsername("username3", appRealm));
Assert.assertNull(session.userLocalStorage().getUserByUsername("username4", appRealm));
UserModel user;
// search by username
user = session.users().searchForUser("username1", appRealm).get(0);
LDAPTestUtils.assertLoaded(user, "username1", "John1", "Doel1", "user1@email.org", "121");
// search by email
user = session.users().searchForUser("user2@email.org", appRealm).get(0);
LDAPTestUtils.assertLoaded(user, "username2", "John2", "Doel2", "user2@email.org", "122");
// search by lastName
user = session.users().searchForUser("Doel3", appRealm).get(0);
LDAPTestUtils.assertLoaded(user, "username3", "John3", "Doel3", "user3@email.org", "123");
// search by firstName + lastName
user = session.users().searchForUser("John4 Doel4", appRealm).get(0);
LDAPTestUtils.assertLoaded(user, "username4", "John4", "Doel4", "user4@email.org", "124");
} finally {
keycloakRule.stopSession(session, true);
}
}
@Test
public void testSearchWithCustomLDAPFilter() {
// Add custom filter for searching users
KeycloakSession session = keycloakRule.startSession();
try {
RealmModel appRealm = session.realms().getRealmByName("test");
ldapModel.getConfig().putSingle(LDAPConstants.CUSTOM_USER_SEARCH_FILTER, "(|(mail=user5@email.org)(mail=user6@email.org))");
appRealm.updateComponent(ldapModel);
} finally {
keycloakRule.stopSession(session, true);
}
session = keycloakRule.startSession();
try {
LDAPStorageProvider ldapProvider = LDAPTestUtils.getLdapProvider(session, ldapModel);
RealmModel appRealm = session.realms().getRealmByName("test");
LDAPTestUtils.addLDAPUser(ldapProvider, appRealm, "username5", "John5", "Doel5", "user5@email.org", null, "125");
LDAPTestUtils.addLDAPUser(ldapProvider, appRealm, "username6", "John6", "Doel6", "user6@email.org", null, "126");
LDAPTestUtils.addLDAPUser(ldapProvider, appRealm, "username7", "John7", "Doel7", "user7@email.org", null, "127");
UserModel user;
// search by email
user = session.users().searchForUser("user5@email.org", appRealm).get(0);
LDAPTestUtils.assertLoaded(user, "username5", "John5", "Doel5", "user5@email.org", "125");
user = session.users().searchForUser("John6 Doel6", appRealm).get(0);
LDAPTestUtils.assertLoaded(user, "username6", "John6", "Doel6", "user6@email.org", "126");
Assert.assertTrue(session.users().searchForUser("user7@email.org", appRealm).isEmpty());
// Remove custom filter
ldapModel.getConfig().remove(LDAPConstants.CUSTOM_USER_SEARCH_FILTER);
appRealm.updateComponent(ldapModel);
} finally {
keycloakRule.stopSession(session, true);
}
}
}

View file

@ -0,0 +1,323 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.testsuite.federation.storage.ldap.noimport;
import org.junit.Assert;
import org.junit.ClassRule;
import org.junit.FixMethodOrder;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.RuleChain;
import org.junit.rules.TestRule;
import org.junit.runners.MethodSorters;
import org.keycloak.common.util.MultivaluedHashMap;
import org.keycloak.component.ComponentModel;
import org.keycloak.models.AccountRoles;
import org.keycloak.models.ClientModel;
import org.keycloak.models.Constants;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.LDAPConstants;
import org.keycloak.models.ModelException;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleModel;
import org.keycloak.models.UserModel;
import org.keycloak.services.managers.RealmManager;
import org.keycloak.storage.UserStorageProvider;
import org.keycloak.storage.UserStorageProviderModel;
import org.keycloak.storage.ldap.LDAPStorageProvider;
import org.keycloak.storage.ldap.LDAPStorageProviderFactory;
import org.keycloak.storage.ldap.idm.model.LDAPObject;
import org.keycloak.storage.ldap.mappers.membership.LDAPGroupMapperMode;
import org.keycloak.storage.ldap.mappers.membership.role.RoleLDAPStorageMapper;
import org.keycloak.testsuite.OAuthClient;
import org.keycloak.testsuite.federation.storage.ldap.LDAPTestUtils;
import org.keycloak.testsuite.pages.AppPage;
import org.keycloak.testsuite.pages.LoginPage;
import org.keycloak.testsuite.rule.KeycloakRule;
import org.keycloak.testsuite.rule.LDAPRule;
import org.keycloak.testsuite.rule.WebResource;
import org.keycloak.testsuite.rule.WebRule;
import org.openqa.selenium.WebDriver;
import java.util.Set;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
public class LDAPRoleMappingsNoImportTest {
private static LDAPRule ldapRule = new LDAPRule();
private static ComponentModel ldapModel = null;
private static KeycloakRule keycloakRule = new KeycloakRule(new KeycloakRule.KeycloakSetup() {
@Override
public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
LDAPTestUtils.addLocalUser(manager.getSession(), appRealm, "mary", "mary@test.com", "password-app");
MultivaluedHashMap<String,String> ldapConfig = LDAPTestUtils.getLdapRuleConfig(ldapRule);
ldapConfig.putSingle(LDAPConstants.SYNC_REGISTRATIONS, "true");
ldapConfig.putSingle(LDAPConstants.EDIT_MODE, UserStorageProvider.EditMode.WRITABLE.toString());
UserStorageProviderModel model = new UserStorageProviderModel();
model.setLastSync(0);
model.setChangedSyncPeriod(-1);
model.setFullSyncPeriod(-1);
model.setName("test-ldap");
model.setPriority(0);
model.setProviderId(LDAPStorageProviderFactory.PROVIDER_NAME);
model.setConfig(ldapConfig);
model.setImportEnabled(false);
ldapModel = appRealm.addComponentModel(model);
// Delete all LDAP users
LDAPStorageProvider ldapFedProvider = LDAPTestUtils.getLdapProvider(session, ldapModel);
LDAPTestUtils.removeAllLDAPUsers(ldapFedProvider, appRealm);
// Add sample application
ClientModel finance = appRealm.addClient("finance");
// Delete all LDAP roles
LDAPTestUtils.addOrUpdateRoleLDAPMappers(appRealm, ldapModel, LDAPGroupMapperMode.LDAP_ONLY);
LDAPTestUtils.removeAllLDAPRoles(manager.getSession(), appRealm, ldapModel, "realmRolesMapper");
LDAPTestUtils.removeAllLDAPRoles(manager.getSession(), appRealm, ldapModel, "financeRolesMapper");
// Add some users for testing
LDAPObject john = LDAPTestUtils.addLDAPUser(ldapFedProvider, appRealm, "johnkeycloak", "John", "Doe", "john@email.org", null, "1234");
LDAPTestUtils.updateLDAPPassword(ldapFedProvider, john, "Password1");
LDAPObject mary = LDAPTestUtils.addLDAPUser(ldapFedProvider, appRealm, "marykeycloak", "Mary", "Kelly", "mary@email.org", null, "5678");
LDAPTestUtils.updateLDAPPassword(ldapFedProvider, mary, "Password1");
LDAPObject rob = LDAPTestUtils.addLDAPUser(ldapFedProvider, appRealm, "robkeycloak", "Rob", "Brown", "rob@email.org", null, "8910");
LDAPTestUtils.updateLDAPPassword(ldapFedProvider, rob, "Password1");
// Add some roles for testing
LDAPTestUtils.createLDAPRole(manager.getSession(), appRealm, ldapModel, "realmRolesMapper", "realmRole1");
LDAPTestUtils.createLDAPRole(manager.getSession(), appRealm, ldapModel, "realmRolesMapper", "realmRole2");
LDAPTestUtils.createLDAPRole(manager.getSession(), appRealm, ldapModel, "financeRolesMapper", "financeRole1");
// Sync LDAP roles to Keycloak DB
LDAPTestUtils.syncRolesFromLDAP(appRealm, ldapFedProvider, ldapModel);
}
});
@ClassRule
public static TestRule chain = RuleChain
.outerRule(ldapRule)
.around(keycloakRule);
@Rule
public WebRule webRule = new WebRule(this);
@WebResource
protected OAuthClient oauth;
@WebResource
protected WebDriver driver;
@WebResource
protected AppPage appPage;
@WebResource
protected LoginPage loginPage;
@Test
public void test01ReadMappings() {
KeycloakSession session = keycloakRule.startSession();
try {
RealmModel appRealm = session.realms().getRealmByName("test");
LDAPTestUtils.addOrUpdateRoleLDAPMappers(appRealm, ldapModel, LDAPGroupMapperMode.LDAP_ONLY);
ComponentModel roleMapperModel = LDAPTestUtils.getSubcomponentByName(appRealm, ldapModel, "realmRolesMapper");
LDAPStorageProvider ldapProvider = LDAPTestUtils.getLdapProvider(session, ldapModel);
RoleLDAPStorageMapper roleMapper = LDAPTestUtils.getRoleMapper(roleMapperModel, ldapProvider, appRealm);
LDAPObject maryLdap = ldapProvider.loadLDAPUserByUsername(appRealm, "marykeycloak");
roleMapper.addRoleMappingInLDAP("realmRole1", maryLdap);
roleMapper.addRoleMappingInLDAP("realmRole2", maryLdap);
} finally {
keycloakRule.stopSession(session, true);
}
session = keycloakRule.startSession();
try {
session.userCache().clear();
RealmModel appRealm = session.realms().getRealmByName("test");
UserModel mary = session.users().getUserByUsername("marykeycloak", appRealm);
// make sure we are in no-import mode!
Assert.assertNull(session.userLocalStorage().getUserByUsername("marykeycloak", appRealm));
// This role should already exists as it was imported from LDAP
RoleModel realmRole1 = appRealm.getRole("realmRole1");
// This role should already exists as it was imported from LDAP
RoleModel realmRole2 = appRealm.getRole("realmRole2");
Set<RoleModel> maryRoles = mary.getRealmRoleMappings();
Assert.assertTrue(maryRoles.contains(realmRole1));
Assert.assertTrue(maryRoles.contains(realmRole2));
// Add some role mappings directly into LDAP
ComponentModel roleMapperModel = LDAPTestUtils.getSubcomponentByName(appRealm, ldapModel, "realmRolesMapper");
LDAPStorageProvider ldapProvider = LDAPTestUtils.getLdapProvider(session, ldapModel);
RoleLDAPStorageMapper roleMapper = LDAPTestUtils.getRoleMapper(roleMapperModel, ldapProvider, appRealm);
LDAPObject maryLdap = ldapProvider.loadLDAPUserByUsername(appRealm, "marykeycloak");
deleteRoleMappingsInLDAP(roleMapper, maryLdap, "realmRole1");
deleteRoleMappingsInLDAP(roleMapper, maryLdap, "realmRole2");
} finally {
keycloakRule.stopSession(session, true);
}
session = keycloakRule.startSession();
try {
session.userCache().clear();
RealmModel appRealm = session.realms().getRealmByName("test");
UserModel mary = session.users().getUserByUsername("marykeycloak", appRealm);
// This role should already exists as it was imported from LDAP
RoleModel realmRole1 = appRealm.getRole("realmRole1");
// This role should already exists as it was imported from LDAP
RoleModel realmRole2 = appRealm.getRole("realmRole2");
Set<RoleModel> maryRoles = mary.getRealmRoleMappings();
Assert.assertFalse(maryRoles.contains(realmRole1));
Assert.assertFalse(maryRoles.contains(realmRole2));
} finally {
keycloakRule.stopSession(session, true);
}
}
@Test
public void test02WriteMappings() {
KeycloakSession session = keycloakRule.startSession();
try {
session.userCache().clear();
RealmModel appRealm = session.realms().getRealmByName("test");
LDAPTestUtils.addOrUpdateRoleLDAPMappers(appRealm, ldapModel, LDAPGroupMapperMode.LDAP_ONLY);
UserModel john = session.users().getUserByUsername("johnkeycloak", appRealm);
UserModel mary = session.users().getUserByUsername("marykeycloak", appRealm);
// make sure we are in no-import mode
Assert.assertNull(session.userLocalStorage().getUserByUsername("johnkeycloak", appRealm));
Assert.assertNull(session.userLocalStorage().getUserByUsername("marykeycloak", appRealm));
// 1 - Grant some roles in LDAP
// This role should already exists as it was imported from LDAP
RoleModel realmRole1 = appRealm.getRole("realmRole1");
john.grantRole(realmRole1);
// This role should already exists as it was imported from LDAP
RoleModel realmRole2 = appRealm.getRole("realmRole2");
mary.grantRole(realmRole2);
// This role may already exists from previous test (was imported from LDAP), but may not
RoleModel realmRole3 = appRealm.getRole("realmRole3");
if (realmRole3 == null) {
realmRole3 = appRealm.addRole("realmRole3");
}
john.grantRole(realmRole3);
mary.grantRole(realmRole3);
ClientModel accountApp = appRealm.getClientByClientId(Constants.ACCOUNT_MANAGEMENT_CLIENT_ID);
ClientModel financeApp = appRealm.getClientByClientId("finance");
RoleModel manageAccountRole = accountApp.getRole(AccountRoles.MANAGE_ACCOUNT);
RoleModel financeRole1 = financeApp.getRole("financeRole1");
john.grantRole(financeRole1);
session.userCache().clear();
} finally {
keycloakRule.stopSession(session, true);
}
session = keycloakRule.startSession();
try {
session.userCache().clear();
RealmModel appRealm = session.realms().getRealmByName("test");
UserModel john = session.users().getUserByUsername("johnkeycloak", appRealm);
UserModel mary = session.users().getUserByUsername("marykeycloak", appRealm);
// make sure we are in no-import mode
Assert.assertNull(session.userLocalStorage().getUserByUsername("johnkeycloak", appRealm));
Assert.assertNull(session.userLocalStorage().getUserByUsername("marykeycloak", appRealm));
RoleModel realmRole1 = appRealm.getRole("realmRole1");
RoleModel realmRole2 = appRealm.getRole("realmRole2");
RoleModel realmRole3 = appRealm.getRole("realmRole3");
ClientModel accountApp = appRealm.getClientByClientId(Constants.ACCOUNT_MANAGEMENT_CLIENT_ID);
ClientModel financeApp = appRealm.getClientByClientId("finance");
RoleModel manageAccountRole = accountApp.getRole(AccountRoles.MANAGE_ACCOUNT);
RoleModel financeRole1 = financeApp.getRole("financeRole1");
// 3 - Check that role mappings are in LDAP and hence available through federation
Set<RoleModel> johnRoles = john.getRoleMappings();
Assert.assertTrue(johnRoles.contains(realmRole1));
Assert.assertFalse(johnRoles.contains(realmRole2));
Assert.assertTrue(johnRoles.contains(realmRole3));
Assert.assertTrue(johnRoles.contains(financeRole1));
Assert.assertTrue(johnRoles.contains(manageAccountRole));
Set<RoleModel> johnRealmRoles = john.getRealmRoleMappings();
Assert.assertEquals(2, johnRealmRoles.size());
Assert.assertTrue(johnRealmRoles.contains(realmRole1));
Assert.assertTrue(johnRealmRoles.contains(realmRole3));
// account roles are not mapped in LDAP. Those are in Keycloak DB
Set<RoleModel> johnAccountRoles = john.getClientRoleMappings(accountApp);
Assert.assertTrue(johnAccountRoles.contains(manageAccountRole));
Set<RoleModel> johnFinanceRoles = john.getClientRoleMappings(financeApp);
Assert.assertEquals(1, johnFinanceRoles.size());
Assert.assertTrue(johnFinanceRoles.contains(financeRole1));
// 4 - Delete some role mappings and check they are deleted
john.deleteRoleMapping(realmRole3);
john.deleteRoleMapping(realmRole1);
john.deleteRoleMapping(financeRole1);
johnRoles = john.getRoleMappings();
Assert.assertFalse(johnRoles.contains(realmRole1));
Assert.assertFalse(johnRoles.contains(realmRole2));
Assert.assertFalse(johnRoles.contains(realmRole3));
Assert.assertFalse(johnRoles.contains(financeRole1));
// Cleanup
mary.deleteRoleMapping(realmRole2);
mary.deleteRoleMapping(realmRole3);
session.userCache().clear();
} finally {
keycloakRule.stopSession(session, false);
}
}
private void deleteRoleMappingsInLDAP(RoleLDAPStorageMapper roleMapper, LDAPObject ldapUser, String roleName) {
LDAPObject ldapRole1 = roleMapper.loadLDAPRoleByName(roleName);
roleMapper.deleteRoleMappingInLDAP(ldapUser, ldapRole1);
}
}

View file

@ -689,6 +689,8 @@ changed-users-sync-period=Changed Users Sync Period
changed-users-sync-period.tooltip=Period for synchronization of changed or newly created provider users in seconds
synchronize-changed-users=Synchronize changed users
synchronize-all-users=Synchronize all users
remove-imported-users=Remove imported
unlink-users=Unlink users
kerberos-realm=Kerberos Realm
kerberos-realm.tooltip=Name of kerberos realm. For example FOO.ORG
server-principal=Server Principal
@ -706,6 +708,8 @@ update-profile-first-login=Update Profile First Login
update-profile-first-login.tooltip=Update profile on first login
sync-registrations=Sync Registrations
ldap.sync-registrations.tooltip=Should newly created users be created within LDAP store? Priority effects which provider is chose to sync the new user.
import-enabled=Import Users
ldap.import-enabled.tooltip=If true, LDAP users will be imported into Keycloak DB and synced via the configured sync policies.
vendor=Vendor
ldap.vendor.tooltip=LDAP vendor (provider)
username-ldap-attribute=Username LDAP attribute

View file

@ -670,7 +670,7 @@ module.controller('UserFederationCtrl', function($scope, $location, $route, real
});
module.controller('GenericUserStorageCtrl', function($scope, $location, Notifications, $route, Dialog, realm,
serverInfo, instance, providerId, Components, UserStorageSync) {
serverInfo, instance, providerId, Components, UserStorageOperations) {
console.log('GenericUserStorageCtrl');
console.log('providerId: ' + providerId);
$scope.create = !instance.providerId;
@ -869,7 +869,7 @@ module.controller('GenericUserStorageCtrl', function($scope, $location, Notifica
}
function triggerSync(action) {
UserStorageSync.save({ action: action, realm: $scope.realm.realm, componentId: $scope.instance.id }, {}, function(syncResult) {
UserStorageOperations.sync.save({ action: action, realm: $scope.realm.realm, componentId: $scope.instance.id }, {}, function(syncResult) {
$route.reload();
Notifications.success("Sync of users finished successfully. " + syncResult.status);
}, function() {
@ -877,6 +877,24 @@ module.controller('GenericUserStorageCtrl', function($scope, $location, Notifica
Notifications.error("Error during sync of users");
});
}
$scope.removeImportedUsers = function() {
UserStorageOperations.removeImportedUsers.save({ realm: $scope.realm.realm, componentId: $scope.instance.id }, {}, function(syncResult) {
$route.reload();
Notifications.success("Remove imported users finished successfully. ");
}, function() {
$route.reload();
Notifications.error("Error during remove");
});
};
$scope.unlinkUsers = function() {
UserStorageOperations.unlinkUsers.save({ realm: $scope.realm.realm, componentId: $scope.instance.id }, {}, function(syncResult) {
$route.reload();
Notifications.success("Unlink of users finished successfully. ");
}, function() {
$route.reload();
Notifications.error("Error during unlink");
});
};
});
@ -971,7 +989,7 @@ module.controller('UserGroupMembershipCtrl', function($scope, $route, realm, gro
});
module.controller('LDAPUserStorageCtrl', function($scope, $location, Notifications, $route, Dialog, realm,
serverInfo, instance, Components, UserStorageSync, RealmLDAPConnectionTester) {
serverInfo, instance, Components, UserStorageOperations, RealmLDAPConnectionTester) {
console.log('LDAPUserStorageCtrl');
var providerId = 'ldap';
console.log('providerId: ' + providerId);
@ -1046,6 +1064,7 @@ module.controller('LDAPUserStorageCtrl', function($scope, $location, Notificatio
instance.config['evictionMinute'] = [''];
instance.config['maxLifespan'] = [''];
instance.config['batchSizeForSync'] = [DEFAULT_BATCH_SIZE];
//instance.config['importEnabled'] = ['true'];
if (providerFactory.properties) {
@ -1098,6 +1117,9 @@ module.controller('LDAPUserStorageCtrl', function($scope, $location, Notificatio
if (!instance.config['priority']) {
instance.config['priority'] = ['0'];
}
if (!instance.config['importEnabled']) {
instance.config['importEnabled'] = ['true'];
}
if (providerFactory.properties) {
@ -1244,8 +1266,9 @@ module.controller('LDAPUserStorageCtrl', function($scope, $location, Notificatio
triggerSync('triggerChangedUsersSync');
}
function triggerSync(action) {
UserStorageSync.save({ action: action, realm: $scope.realm.realm, componentId: $scope.instance.id }, {}, function(syncResult) {
UserStorageOperations.sync.save({ action: action, realm: $scope.realm.realm, componentId: $scope.instance.id }, {}, function(syncResult) {
$route.reload();
Notifications.success("Sync of users finished successfully. " + syncResult.status);
}, function() {
@ -1253,7 +1276,24 @@ module.controller('LDAPUserStorageCtrl', function($scope, $location, Notificatio
Notifications.error("Error during sync of users");
});
}
$scope.removeImportedUsers = function() {
UserStorageOperations.removeImportedUsers.save({ realm: $scope.realm.realm, componentId: $scope.instance.id }, {}, function(syncResult) {
$route.reload();
Notifications.success("Remove imported users finished successfully. ");
}, function() {
$route.reload();
Notifications.error("Error during remove");
});
};
$scope.unlinkUsers = function() {
UserStorageOperations.unlinkUsers.save({ realm: $scope.realm.realm, componentId: $scope.instance.id }, {}, function(syncResult) {
$route.reload();
Notifications.success("Unlink of users finished successfully. ");
}, function() {
$route.reload();
Notifications.error("Error during unlink");
});
};
var initConnectionTest = function(testAction, ldapConfig) {
return {
action: testAction,

View file

@ -1687,13 +1687,24 @@ module.factory('Components', function($resource, ComponentUtils) {
});
});
module.factory('UserStorageSync', function($resource) {
return $resource(authUrl + '/admin/realms/:realm/user-storage/:componentId/sync', {
module.factory('UserStorageOperations', function($resource) {
var object = {}
object.sync = $resource(authUrl + '/admin/realms/:realm/user-storage/:componentId/sync', {
realm : '@realm',
componentId : '@componentId'
});
object.removeImportedUsers = $resource(authUrl + '/admin/realms/:realm/user-storage/:componentId/remove-imported-users', {
realm : '@realm',
componentId : '@componentId'
});
object.unlinkUsers = $resource(authUrl + '/admin/realms/:realm/user-storage/:componentId/unlink-users', {
realm : '@realm',
componentId : '@componentId'
});
return object;
});
module.factory('ClientRegistrationPolicyProviders', function($resource) {
return $resource(authUrl + '/admin/realms/:realm/client-registration-policy/providers', {
realm : '@realm',

View file

@ -229,6 +229,8 @@
<button kc-reset data-ng-disabled="!changed">{{:: 'cancel' | translate}}</button>
<button class="btn btn-primary" data-ng-click="triggerChangedUsersSync()" data-ng-hide="changed || !showSync">{{:: 'synchronize-changed-users' | translate}}</button>
<button class="btn btn-primary" data-ng-click="triggerFullSync()" data-ng-hide="changed || !showSync">{{:: 'synchronize-all-users' | translate}}</button>
<button class="btn btn-primary" data-ng-click="removeImportedUsers()" data-ng-hide="changed">{{:: 'remove-imported-users' | translate}}</button>
<button class="btn btn-primary" data-ng-click="unlinkUsers()" data-ng-hide="changed">{{:: 'unlink-users' | translate}}</button>
</div>
</div>
</form>

View file

@ -33,6 +33,13 @@
</div>
<kc-tooltip>{{:: 'priority.tooltip' | translate}}</kc-tooltip>
</div>
<div class="form-group clearfix block">
<label class="col-md-2 control-label" for="importEnabled">{{:: 'import-enabled' | translate}}</label>
<div class="col-md-6">
<input ng-model="instance.config['importEnabled'][0]" name="importEnabled" id="importEnabled" onoffswitchvalue on-text="{{:: 'onText' | translate}}" off-text="{{:: 'offText' | translate}}" />
</div>
<kc-tooltip>{{:: 'ldap.import-enabled.tooltip' | translate}}</kc-tooltip>
</div>
<div class="form-group">
<label class="col-md-2 control-label" for="editMode">{{:: 'edit-mode' | translate}}</label>
@ -455,6 +462,8 @@
<button kc-reset data-ng-disabled="!changed">{{:: 'cancel' | translate}}</button>
<button class="btn btn-primary" data-ng-click="triggerChangedUsersSync()" data-ng-hide="changed">{{:: 'synchronize-changed-users' | translate}}</button>
<button class="btn btn-primary" data-ng-click="triggerFullSync()" data-ng-hide="changed">{{:: 'synchronize-all-users' | translate}}</button>
<button class="btn btn-primary" data-ng-click="removeImportedUsers()" data-ng-hide="changed">{{:: 'remove-imported-users' | translate}}</button>
<button class="btn btn-primary" data-ng-click="unlinkUsers()" data-ng-hide="changed">{{:: 'unlink-users' | translate}}</button>
</div>
</div>
</form>