KEYCLOAK-2643 Added write-only property to LDAP full-name attribute mapper

This commit is contained in:
mposolda 2016-03-11 18:06:35 +01:00
parent 73c3534e7a
commit 85ccd64e01
12 changed files with 84 additions and 14 deletions

View file

@ -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);
}
}

View file

@ -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);
}
}

View file

@ -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

View file

@ -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");

View file

@ -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);
}

View file

@ -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);

View file

@ -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);

View file

@ -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

View file

@ -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

View file

@ -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());

View file

@ -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);

View file

@ -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