Added sync support to UserFederationMapper
This commit is contained in:
1 parent
358c273d39
commit
0d52e4e6c5
19 files changed
+294
-128
No files matched your search
+14
@@ -1,12 +1,26 @@
|
||||
package org.keycloak.federation.ldap.mappers;
|
||||
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.UserFederationMapperModel;
|
||||
import org.keycloak.models.UserFederationProvider;
|
||||
import org.keycloak.models.UserFederationSyncResult;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public abstract class AbstractLDAPFederationMapper implements LDAPFederationMapper {
|
||||
|
||||
@Override
|
||||
public UserFederationSyncResult syncDataFromFederationProviderToKeycloak(UserFederationMapperModel mapperModel, UserFederationProvider federationProvider, KeycloakSession session, RealmModel realm) {
|
||||
throw new IllegalStateException("Not supported");
|
||||
}
|
||||
|
||||
@Override
|
||||
public UserFederationSyncResult syncDataFromKeycloakToFederationProvider(UserFederationMapperModel mapperModel, UserFederationProvider federationProvider, KeycloakSession session, RealmModel realm) {
|
||||
throw new IllegalStateException("Not supported");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
|
||||
|
||||
+6
@@ -10,6 +10,7 @@ import org.keycloak.mappers.UserFederationMapperFactory;
|
||||
import org.keycloak.models.KeycloakSessionFactory;
|
||||
import org.keycloak.models.UserFederationMapperModel;
|
||||
import org.keycloak.provider.ProviderConfigProperty;
|
||||
import org.keycloak.representations.idm.UserFederationMapperSyncConfigRepresentation;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
@@ -35,6 +36,11 @@ public abstract class AbstractLDAPFederationMapperFactory implements UserFederat
|
||||
public void postInit(KeycloakSessionFactory factory) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public UserFederationMapperSyncConfigRepresentation getSyncConfig() {
|
||||
return new UserFederationMapperSyncConfigRepresentation(false, null, false, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
}
|
||||
|
||||
+79
-26
@@ -14,12 +14,15 @@ import org.keycloak.federation.ldap.idm.query.Condition;
|
||||
import org.keycloak.federation.ldap.idm.query.internal.LDAPQuery;
|
||||
import org.keycloak.federation.ldap.idm.query.internal.LDAPQueryConditionsBuilder;
|
||||
import org.keycloak.models.ClientModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.LDAPConstants;
|
||||
import org.keycloak.models.ModelException;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.RoleContainerModel;
|
||||
import org.keycloak.models.RoleModel;
|
||||
import org.keycloak.models.UserFederationMapperModel;
|
||||
import org.keycloak.models.UserFederationProvider;
|
||||
import org.keycloak.models.UserFederationSyncResult;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||
import org.keycloak.models.utils.UserModelDelegate;
|
||||
@@ -63,15 +66,8 @@ public class RoleLDAPFederationMapper extends AbstractLDAPFederationMapper {
|
||||
// Customized LDAP filter which is added to the whole LDAP query
|
||||
public static final String ROLES_LDAP_FILTER = "roles.ldap.filter";
|
||||
|
||||
|
||||
// List of IDs of UserFederationMapperModels where syncRolesFromLDAP was already called in this KeycloakSession. This is to improve performance
|
||||
// TODO: Rather address this with caching at LDAPIdentityStore level?
|
||||
private Set<String> rolesSyncedModels = new TreeSet<>();
|
||||
|
||||
@Override
|
||||
public void onImportUserFromLDAP(UserFederationMapperModel mapperModel, LDAPFederationProvider ldapProvider, LDAPObject ldapUser, UserModel user, RealmModel realm, boolean isCreate) {
|
||||
syncRolesFromLDAP(mapperModel, ldapProvider, realm);
|
||||
|
||||
Mode mode = getMode(mapperModel);
|
||||
|
||||
// For now, import LDAP role mappings just during create
|
||||
@@ -95,34 +91,91 @@ public class RoleLDAPFederationMapper extends AbstractLDAPFederationMapper {
|
||||
|
||||
@Override
|
||||
public void onRegisterUserToLDAP(UserFederationMapperModel mapperModel, LDAPFederationProvider ldapProvider, LDAPObject ldapUser, UserModel localUser, RealmModel realm) {
|
||||
syncRolesFromLDAP(mapperModel, ldapProvider, realm);
|
||||
}
|
||||
|
||||
// Sync roles from LDAP tree and create them in local Keycloak DB (if they don't exist here yet)
|
||||
protected void syncRolesFromLDAP(UserFederationMapperModel mapperModel, LDAPFederationProvider ldapProvider, RealmModel realm) {
|
||||
if (!rolesSyncedModels.contains(mapperModel.getId())) {
|
||||
logger.debugf("Syncing roles from LDAP into Keycloak DB. Mapper is [%s], LDAP provider is [%s]", mapperModel.getName(), ldapProvider.getModel().getDisplayName());
|
||||
|
||||
LDAPQuery ldapQuery = createRoleQuery(mapperModel, ldapProvider);
|
||||
// Sync roles from LDAP to Keycloak DB
|
||||
@Override
|
||||
public UserFederationSyncResult syncDataFromFederationProviderToKeycloak(UserFederationMapperModel mapperModel, UserFederationProvider federationProvider, KeycloakSession session, RealmModel realm) {
|
||||
LDAPFederationProvider ldapProvider = (LDAPFederationProvider) federationProvider;
|
||||
UserFederationSyncResult syncResult = new UserFederationSyncResult() {
|
||||
|
||||
// Send query
|
||||
List<LDAPObject> ldapRoles = ldapQuery.getResultList();
|
||||
|
||||
RoleContainerModel roleContainer = getTargetRoleContainer(mapperModel, realm);
|
||||
String rolesRdnAttr = getRoleNameLdapAttribute(mapperModel);
|
||||
for (LDAPObject ldapRole : ldapRoles) {
|
||||
String roleName = ldapRole.getAttributeAsString(rolesRdnAttr);
|
||||
|
||||
if (roleContainer.getRole(roleName) == null) {
|
||||
logger.debugf("Syncing role [%s] from LDAP to keycloak DB", roleName);
|
||||
roleContainer.addRole(roleName);
|
||||
}
|
||||
@Override
|
||||
public String getStatus() {
|
||||
return String.format("%d imported roles, %d roles already exists in Keycloak", getAdded(), getUpdated());
|
||||
}
|
||||
|
||||
rolesSyncedModels.add(mapperModel.getId());
|
||||
};
|
||||
|
||||
logger.debugf("Syncing roles from LDAP into Keycloak DB. Mapper is [%s], LDAP provider is [%s]", mapperModel.getName(), ldapProvider.getModel().getDisplayName());
|
||||
|
||||
// Send LDAP query
|
||||
LDAPQuery ldapQuery = createRoleQuery(mapperModel, ldapProvider);
|
||||
List<LDAPObject> ldapRoles = ldapQuery.getResultList();
|
||||
|
||||
RoleContainerModel roleContainer = getTargetRoleContainer(mapperModel, realm);
|
||||
String rolesRdnAttr = getRoleNameLdapAttribute(mapperModel);
|
||||
for (LDAPObject ldapRole : ldapRoles) {
|
||||
String roleName = ldapRole.getAttributeAsString(rolesRdnAttr);
|
||||
|
||||
if (roleContainer.getRole(roleName) == null) {
|
||||
logger.debugf("Syncing role [%s] from LDAP to keycloak DB", roleName);
|
||||
roleContainer.addRole(roleName);
|
||||
syncResult.increaseAdded();
|
||||
} else {
|
||||
syncResult.increaseUpdated();
|
||||
}
|
||||
}
|
||||
|
||||
return syncResult;
|
||||
}
|
||||
|
||||
|
||||
// Sync roles from Keycloak back to LDAP
|
||||
@Override
|
||||
public UserFederationSyncResult syncDataFromKeycloakToFederationProvider(UserFederationMapperModel mapperModel, UserFederationProvider federationProvider, KeycloakSession session, RealmModel realm) {
|
||||
LDAPFederationProvider ldapProvider = (LDAPFederationProvider) federationProvider;
|
||||
UserFederationSyncResult syncResult = new UserFederationSyncResult() {
|
||||
|
||||
@Override
|
||||
public String getStatus() {
|
||||
return String.format("%d roles imported to LDAP, %d roles already existed in LDAP", getAdded(), getUpdated());
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
logger.debugf("Syncing roles from Keycloak into LDAP. Mapper is [%s], LDAP provider is [%s]", mapperModel.getName(), ldapProvider.getModel().getDisplayName());
|
||||
|
||||
// Send LDAP query to see which roles exists there
|
||||
LDAPQuery ldapQuery = createRoleQuery(mapperModel, ldapProvider);
|
||||
List<LDAPObject> ldapRoles = ldapQuery.getResultList();
|
||||
|
||||
Set<String> ldapRoleNames = new HashSet<>();
|
||||
String rolesRdnAttr = getRoleNameLdapAttribute(mapperModel);
|
||||
for (LDAPObject ldapRole : ldapRoles) {
|
||||
String roleName = ldapRole.getAttributeAsString(rolesRdnAttr);
|
||||
ldapRoleNames.add(roleName);
|
||||
}
|
||||
|
||||
|
||||
RoleContainerModel roleContainer = getTargetRoleContainer(mapperModel, realm);
|
||||
Set<RoleModel> keycloakRoles = roleContainer.getRoles();
|
||||
|
||||
for (RoleModel keycloakRole : keycloakRoles) {
|
||||
String roleName = keycloakRole.getName();
|
||||
if (ldapRoleNames.contains(roleName)) {
|
||||
syncResult.increaseUpdated();
|
||||
} else {
|
||||
logger.debugf("Syncing role [%s] from Keycloak to LDAP", roleName);
|
||||
createLDAPRole(mapperModel, roleName, ldapProvider);
|
||||
syncResult.increaseAdded();
|
||||
}
|
||||
}
|
||||
|
||||
return syncResult;
|
||||
}
|
||||
|
||||
|
||||
public LDAPQuery createRoleQuery(UserFederationMapperModel mapperModel, LDAPFederationProvider ldapProvider) {
|
||||
LDAPQuery ldapQuery = new LDAPQuery(ldapProvider);
|
||||
|
||||
|
||||
+7
-46
@@ -5,21 +5,13 @@ import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.federation.ldap.LDAPFederationProvider;
|
||||
import org.keycloak.mappers.MapperConfigValidationException;
|
||||
import org.keycloak.mappers.UserFederationMapper;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.KeycloakSessionFactory;
|
||||
import org.keycloak.models.LDAPConstants;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.UserFederationMapperModel;
|
||||
import org.keycloak.models.UserFederationProvider;
|
||||
import org.keycloak.models.UserFederationProviderFactory;
|
||||
import org.keycloak.models.UserFederationProviderModel;
|
||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||
import org.keycloak.provider.ProviderConfigProperty;
|
||||
import org.keycloak.provider.ProviderEvent;
|
||||
import org.keycloak.provider.ProviderEventListener;
|
||||
import org.keycloak.representations.idm.UserFederationMapperSyncConfigRepresentation;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
@@ -58,15 +50,15 @@ public class RoleLDAPFederationMapperFactory extends AbstractLDAPFederationMappe
|
||||
membershipTypes.add(membershipType.toString());
|
||||
}
|
||||
ProviderConfigProperty membershipType = createConfigProperty(RoleLDAPFederationMapper.MEMBERSHIP_ATTRIBUTE_TYPE, "Membership Attribute Type",
|
||||
"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' ))",
|
||||
"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' .",
|
||||
ProviderConfigProperty.LIST_TYPE, membershipTypes);
|
||||
configProperties.add(membershipType);
|
||||
|
||||
|
||||
ProviderConfigProperty ldapFilter = createConfigProperty(RoleLDAPFederationMapper.ROLES_LDAP_FILTER,
|
||||
"LDAP Filter",
|
||||
"LDAP Filter adds additional custom filter to the whole query. Make sure that it starts with '(' and ends with ')'",
|
||||
"LDAP Filter adds additional custom filter to the whole query. Leave this empty if no additional filtering is needed. Otherwise make sure that filter starts with '(' and ends with ')'",
|
||||
ProviderConfigProperty.STRING_TYPE, null);
|
||||
configProperties.add(ldapFilter);
|
||||
|
||||
@@ -90,7 +82,7 @@ public class RoleLDAPFederationMapperFactory extends AbstractLDAPFederationMappe
|
||||
ProviderConfigProperty retriever = createConfigProperty(RoleLDAPFederationMapper.USER_ROLES_RETRIEVE_STRATEGY, "User Roles Retrieve Strategy",
|
||||
"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 extension."
|
||||
"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."
|
||||
,
|
||||
ProviderConfigProperty.LIST_TYPE, roleRetrievers);
|
||||
configProperties.add(retriever);
|
||||
@@ -131,40 +123,9 @@ public class RoleLDAPFederationMapperFactory extends AbstractLDAPFederationMappe
|
||||
return PROVIDER_ID;
|
||||
}
|
||||
|
||||
// Sync roles from LDAP to Keycloak DB during creation or update of mapperModel
|
||||
@Override
|
||||
public void postInit(KeycloakSessionFactory factory) {
|
||||
factory.register(new ProviderEventListener() {
|
||||
|
||||
@Override
|
||||
public void onEvent(ProviderEvent event) {
|
||||
if (event instanceof RealmModel.UserFederationMapperEvent) {
|
||||
RealmModel.UserFederationMapperEvent mapperEvent = (RealmModel.UserFederationMapperEvent)event;
|
||||
UserFederationMapperModel mapperModel = mapperEvent.getFederationMapper();
|
||||
RealmModel realm = mapperEvent.getRealm();
|
||||
KeycloakSession session = mapperEvent.getSession();
|
||||
|
||||
if (mapperModel.getFederationMapperType().equals(PROVIDER_ID)) {
|
||||
try {
|
||||
String federationProviderId = mapperModel.getFederationProviderId();
|
||||
UserFederationProviderModel providerModel = KeycloakModelUtils.findUserFederationProviderById(federationProviderId, realm);
|
||||
if (providerModel == null) {
|
||||
throw new IllegalStateException("Can't find federation provider with ID [" + federationProviderId + "] in realm " + realm.getName());
|
||||
}
|
||||
|
||||
UserFederationProviderFactory ldapFactory = (UserFederationProviderFactory) session.getKeycloakSessionFactory().getProviderFactory(UserFederationProvider.class, providerModel.getProviderName());
|
||||
LDAPFederationProvider ldapProvider = (LDAPFederationProvider) ldapFactory.getInstance(session, providerModel);
|
||||
|
||||
// Sync roles
|
||||
new RoleLDAPFederationMapper().syncRolesFromLDAP(mapperModel, ldapProvider, realm);
|
||||
} catch (Exception e) {
|
||||
logger.warn("Exception during initial sync of roles from LDAP.", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
public UserFederationMapperSyncConfigRepresentation getSyncConfig() {
|
||||
return new UserFederationMapperSyncConfigRepresentation(true, "sync-ldap-roles-to-keycloak", true, "sync-keycloak-roles-to-ldap");
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
Reference in new issue
Block a user