KEYCLOAK-2643 Added write-only property to LDAP full-name attribute mapper
This commit is contained in:
parent
73c3534e7a
commit
85ccd64e01
12 changed files with 84 additions and 14 deletions
|
@ -165,7 +165,8 @@ public class LDAPFederationProviderFactory extends UserFederationEventAwareProvi
|
|||
// For read-only LDAP, we map "cn" as full name
|
||||
mapperModel = KeycloakModelUtils.createUserFederationMapperModel("full name", newProviderModel.getId(), FullNameLDAPFederationMapperFactory.PROVIDER_ID,
|
||||
FullNameLDAPFederationMapper.LDAP_FULL_NAME_ATTRIBUTE, LDAPConstants.CN,
|
||||
UserAttributeLDAPFederationMapper.READ_ONLY, readOnly);
|
||||
FullNameLDAPFederationMapper.READ_ONLY, readOnly,
|
||||
FullNameLDAPFederationMapper.WRITE_ONLY, "false");
|
||||
realm.addUserFederationMapper(mapperModel);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -40,6 +40,8 @@ public class FullNameLDAPFederationMapper extends AbstractLDAPFederationMapper {
|
|||
|
||||
public static final String LDAP_FULL_NAME_ATTRIBUTE = "ldap.full.name.attribute";
|
||||
public static final String READ_ONLY = "read.only";
|
||||
public static final String WRITE_ONLY = "write.only";
|
||||
|
||||
|
||||
public FullNameLDAPFederationMapper(UserFederationMapperModel mapperModel, LDAPFederationProvider ldapProvider, RealmModel realm) {
|
||||
super(mapperModel, ldapProvider, realm);
|
||||
|
@ -47,6 +49,10 @@ public class FullNameLDAPFederationMapper extends AbstractLDAPFederationMapper {
|
|||
|
||||
@Override
|
||||
public void onImportUserFromLDAP(LDAPObject ldapUser, UserModel user, boolean isCreate) {
|
||||
if (isWriteOnly()) {
|
||||
return;
|
||||
}
|
||||
|
||||
String ldapFullNameAttrName = getLdapFullNameAttrName();
|
||||
String fullName = ldapUser.getAttributeAsString(ldapFullNameAttrName);
|
||||
if (fullName == null) {
|
||||
|
@ -117,6 +123,10 @@ public class FullNameLDAPFederationMapper extends AbstractLDAPFederationMapper {
|
|||
|
||||
@Override
|
||||
public void beforeLDAPQuery(LDAPQuery query) {
|
||||
if (isWriteOnly()) {
|
||||
return;
|
||||
}
|
||||
|
||||
String ldapFullNameAttrName = getLdapFullNameAttrName();
|
||||
query.addReturningLdapAttribute(ldapFullNameAttrName);
|
||||
|
||||
|
@ -178,4 +188,8 @@ public class FullNameLDAPFederationMapper extends AbstractLDAPFederationMapper {
|
|||
private boolean isReadOnly() {
|
||||
return parseBooleanParameter(mapperModel, READ_ONLY);
|
||||
}
|
||||
|
||||
private boolean isWriteOnly() {
|
||||
return parseBooleanParameter(mapperModel, WRITE_ONLY);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -43,12 +43,17 @@ public class FullNameLDAPFederationMapperFactory extends AbstractLDAPFederationM
|
|||
|
||||
static {
|
||||
ProviderConfigProperty userModelAttribute = createConfigProperty(FullNameLDAPFederationMapper.LDAP_FULL_NAME_ATTRIBUTE, "LDAP Full Name Attribute",
|
||||
"Name of LDAP attribute, which contains fullName of user. In most cases it will be 'cn' ", ProviderConfigProperty.STRING_TYPE, null);
|
||||
"Name of LDAP attribute, which contains fullName of user. Usually it will be 'cn' ", ProviderConfigProperty.STRING_TYPE, null);
|
||||
configProperties.add(userModelAttribute);
|
||||
|
||||
ProviderConfigProperty readOnly = createConfigProperty(UserAttributeLDAPFederationMapper.READ_ONLY, "Read Only",
|
||||
ProviderConfigProperty readOnly = createConfigProperty(FullNameLDAPFederationMapper.READ_ONLY, "Read Only",
|
||||
"For Read-only is data imported from LDAP to Keycloak DB, but it's not saved back to LDAP when user is updated in Keycloak.", ProviderConfigProperty.BOOLEAN_TYPE, null);
|
||||
configProperties.add(readOnly);
|
||||
|
||||
ProviderConfigProperty writeOnly = createConfigProperty(FullNameLDAPFederationMapper.WRITE_ONLY, "Write Only",
|
||||
"For Write-only is data propagated to LDAP when user is created or updated in Keycloak. But this mapper is not used to propagate data from LDAP back into Keycloak. " +
|
||||
"This setting is useful if you configured separate firstName and lastName attribute mappers and you want to use those to read attribute from LDAP into Keycloak", ProviderConfigProperty.BOOLEAN_TYPE, null);
|
||||
configProperties.add(writeOnly);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -78,8 +83,11 @@ public class FullNameLDAPFederationMapperFactory extends AbstractLDAPFederationM
|
|||
|
||||
defaultValues.put(FullNameLDAPFederationMapper.LDAP_FULL_NAME_ATTRIBUTE, LDAPConstants.CN);
|
||||
|
||||
String readOnly = config.getEditMode() == UserFederationProvider.EditMode.WRITABLE ? "false" : "true";
|
||||
defaultValues.put(UserAttributeLDAPFederationMapper.READ_ONLY, readOnly);
|
||||
boolean readOnly = config.getEditMode() != UserFederationProvider.EditMode.WRITABLE;
|
||||
defaultValues.put(FullNameLDAPFederationMapper.READ_ONLY, String.valueOf(readOnly));
|
||||
|
||||
String writeOnly = String.valueOf(!readOnly);
|
||||
defaultValues.put(FullNameLDAPFederationMapper.WRITE_ONLY, writeOnly);
|
||||
|
||||
return defaultValues;
|
||||
}
|
||||
|
@ -90,8 +98,21 @@ public class FullNameLDAPFederationMapperFactory extends AbstractLDAPFederationM
|
|||
}
|
||||
|
||||
@Override
|
||||
public void validateConfig(RealmModel realm, UserFederationMapperModel mapperModel) throws FederationConfigValidationException {
|
||||
public void validateConfig(RealmModel realm, UserFederationProviderModel fedProviderModel, UserFederationMapperModel mapperModel) throws FederationConfigValidationException {
|
||||
checkMandatoryConfigAttribute(FullNameLDAPFederationMapper.LDAP_FULL_NAME_ATTRIBUTE, "LDAP Full Name Attribute", mapperModel);
|
||||
|
||||
boolean readOnly = AbstractLDAPFederationMapper.parseBooleanParameter(mapperModel, FullNameLDAPFederationMapper.READ_ONLY);
|
||||
boolean writeOnly = AbstractLDAPFederationMapper.parseBooleanParameter(mapperModel, FullNameLDAPFederationMapper.WRITE_ONLY);
|
||||
|
||||
LDAPConfig cfg = new LDAPConfig(fedProviderModel.getConfig());
|
||||
UserFederationProvider.EditMode editMode = cfg.getEditMode();
|
||||
|
||||
if (writeOnly && cfg.getEditMode() != UserFederationProvider.EditMode.WRITABLE) {
|
||||
throw new FederationConfigValidationException("ldapErrorCantWriteOnlyForReadOnlyLdap");
|
||||
}
|
||||
if (writeOnly && readOnly) {
|
||||
throw new FederationConfigValidationException("ldapErrorCantWriteOnlyAndReadOnly");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -77,7 +77,7 @@ public class HardcodedLDAPRoleMapperFactory extends AbstractLDAPFederationMapper
|
|||
}
|
||||
|
||||
@Override
|
||||
public void validateConfig(RealmModel realm, UserFederationMapperModel mapperModel) throws FederationConfigValidationException {
|
||||
public void validateConfig(RealmModel realm, UserFederationProviderModel fedProviderModel, UserFederationMapperModel mapperModel) throws FederationConfigValidationException {
|
||||
String roleName = mapperModel.getConfig().get(HardcodedLDAPRoleMapper.ROLE);
|
||||
if (roleName == null) {
|
||||
throw new FederationConfigValidationException("Role can't be null");
|
||||
|
|
|
@ -101,7 +101,7 @@ public class UserAttributeLDAPFederationMapperFactory extends AbstractLDAPFedera
|
|||
}
|
||||
|
||||
@Override
|
||||
public void validateConfig(RealmModel realm, UserFederationMapperModel mapperModel) throws FederationConfigValidationException {
|
||||
public void validateConfig(RealmModel realm, UserFederationProviderModel fedProviderModel, UserFederationMapperModel mapperModel) throws FederationConfigValidationException {
|
||||
checkMandatoryConfigAttribute(UserAttributeLDAPFederationMapper.USER_MODEL_ATTRIBUTE, "User Model Attribute", mapperModel);
|
||||
checkMandatoryConfigAttribute(UserAttributeLDAPFederationMapper.LDAP_ATTRIBUTE, "LDAP Attribute", mapperModel);
|
||||
}
|
||||
|
|
|
@ -186,7 +186,7 @@ public class GroupLDAPFederationMapperFactory extends AbstractLDAPFederationMapp
|
|||
}
|
||||
|
||||
@Override
|
||||
public void validateConfig(RealmModel realm, UserFederationMapperModel mapperModel) throws FederationConfigValidationException {
|
||||
public void validateConfig(RealmModel realm, UserFederationProviderModel fedProviderModel, UserFederationMapperModel mapperModel) throws FederationConfigValidationException {
|
||||
checkMandatoryConfigAttribute(GroupMapperConfig.GROUPS_DN, "LDAP Groups DN", mapperModel);
|
||||
checkMandatoryConfigAttribute(GroupMapperConfig.MODE, "Mode", mapperModel);
|
||||
|
||||
|
|
|
@ -180,7 +180,7 @@ public class RoleLDAPFederationMapperFactory extends AbstractLDAPFederationMappe
|
|||
}
|
||||
|
||||
@Override
|
||||
public void validateConfig(RealmModel realm, UserFederationMapperModel mapperModel) throws FederationConfigValidationException {
|
||||
public void validateConfig(RealmModel realm, UserFederationProviderModel fedProviderModel, UserFederationMapperModel mapperModel) throws FederationConfigValidationException {
|
||||
checkMandatoryConfigAttribute(RoleMapperConfig.ROLES_DN, "LDAP Roles DN", mapperModel);
|
||||
checkMandatoryConfigAttribute(RoleMapperConfig.MODE, "Mode", mapperModel);
|
||||
|
||||
|
|
|
@ -75,7 +75,7 @@ public class MSADUserAccountControlMapperFactory extends AbstractLDAPFederationM
|
|||
}
|
||||
|
||||
@Override
|
||||
public void validateConfig(RealmModel realm, UserFederationMapperModel mapperModel) throws FederationConfigValidationException {
|
||||
public void validateConfig(RealmModel realm, UserFederationProviderModel fedProviderModel, UserFederationMapperModel mapperModel) throws FederationConfigValidationException {
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -53,10 +53,11 @@ public interface UserFederationMapperFactory extends ProviderFactory<UserFederat
|
|||
* Called when instance of mapperModel is created for this factory through admin endpoint
|
||||
*
|
||||
* @param realm
|
||||
* @param fedProviderModel
|
||||
* @param mapperModel
|
||||
* @throws FederationConfigValidationException if configuration provided in mapperModel is not valid
|
||||
*/
|
||||
void validateConfig(RealmModel realm, UserFederationMapperModel mapperModel) throws FederationConfigValidationException;
|
||||
void validateConfig(RealmModel realm, UserFederationProviderModel fedProviderModel, UserFederationMapperModel mapperModel) throws FederationConfigValidationException;
|
||||
|
||||
/**
|
||||
* Used to detect what are default values for ProviderConfigProperties specified during mapper creation
|
||||
|
|
|
@ -373,7 +373,7 @@ public class UserFederationProviderResource {
|
|||
private void validateModel(UserFederationMapperModel model) {
|
||||
try {
|
||||
UserFederationMapperFactory mapperFactory = (UserFederationMapperFactory) session.getKeycloakSessionFactory().getProviderFactory(UserFederationMapper.class, model.getFederationMapperType());
|
||||
mapperFactory.validateConfig(realm, model);
|
||||
mapperFactory.validateConfig(realm, federationProviderModel, model);
|
||||
} catch (FederationConfigValidationException ex) {
|
||||
logger.error(ex.getMessage());
|
||||
Properties messages = AdminRoot.getMessages(session, realm, auth.getAuth().getToken().getLocale());
|
||||
|
|
|
@ -29,6 +29,7 @@ import org.keycloak.OAuth2Constants;
|
|||
import org.keycloak.federation.ldap.LDAPConfig;
|
||||
import org.keycloak.federation.ldap.LDAPFederationProvider;
|
||||
import org.keycloak.federation.ldap.LDAPFederationProviderFactory;
|
||||
import org.keycloak.federation.ldap.LDAPUtils;
|
||||
import org.keycloak.federation.ldap.idm.model.LDAPObject;
|
||||
import org.keycloak.federation.ldap.mappers.FullNameLDAPFederationMapper;
|
||||
import org.keycloak.federation.ldap.mappers.FullNameLDAPFederationMapperFactory;
|
||||
|
@ -521,7 +522,7 @@ public class FederationProvidersIntegrationTest {
|
|||
|
||||
UserFederationMapperModel fullNameMapperModel = KeycloakModelUtils.createUserFederationMapperModel("full name", ldapModel.getId(), FullNameLDAPFederationMapperFactory.PROVIDER_ID,
|
||||
FullNameLDAPFederationMapper.LDAP_FULL_NAME_ATTRIBUTE, ldapFirstNameAttributeName,
|
||||
UserAttributeLDAPFederationMapper.READ_ONLY, "false");
|
||||
FullNameLDAPFederationMapper.READ_ONLY, "false");
|
||||
appRealm.addUserFederationMapper(fullNameMapperModel);
|
||||
} finally {
|
||||
keycloakRule.stopSession(session, true);
|
||||
|
@ -534,6 +535,36 @@ public class FederationProvidersIntegrationTest {
|
|||
// Assert user is successfully imported in Keycloak DB now with correct firstName and lastName
|
||||
FederationTestUtils.assertUserImported(session.users(), appRealm, "fullname", "James", "Dee", "fullname@email.org", "4578");
|
||||
|
||||
// change mapper to writeOnly
|
||||
UserFederationMapperModel fullNameMapperModel = appRealm.getUserFederationMapperByName(ldapModel.getId(), "full name");
|
||||
fullNameMapperModel.getConfig().put(FullNameLDAPFederationMapper.WRITE_ONLY, "true");
|
||||
appRealm.updateUserFederationMapper(fullNameMapperModel);
|
||||
} 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
|
||||
FederationTestUtils.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);
|
||||
|
|
|
@ -10,3 +10,5 @@ invalidPasswordHistoryMessage=Invalid password: must not be equal to any of last
|
|||
ldapErrorInvalidCustomFilter=Custom configured LDAP filter does not start with "(" or does not end with ")".
|
||||
ldapErrorMissingClientId=Client ID needs to be provided in config when Realm Roles Mapping is not used.
|
||||
ldapErrorCantPreserveGroupInheritanceWithUIDMembershipType=Not possible to preserve group inheritance and use UID membership type together.
|
||||
ldapErrorCantWriteOnlyForReadOnlyLdap=Can't set write only when LDAP provider mode is not WRITABLE
|
||||
ldapErrorCantWriteOnlyAndReadOnly=Can't set write-only and read-only together
|
||||
|
|
Loading…
Reference in a new issue