KEYCLOAK-1728 NPE during LDAP sync when some LDAP user doesn't have username

This commit is contained in:
mposolda 2015-08-04 12:30:37 +02:00
parent 3b2e679aed
commit a0197bc9da
8 changed files with 108 additions and 19 deletions

View file

@ -258,16 +258,14 @@ public class LDAPFederationProvider implements UserFederationProvider {
protected UserModel importUserFromLDAP(KeycloakSession session, RealmModel realm, LDAPObject ldapUser) { protected UserModel importUserFromLDAP(KeycloakSession session, RealmModel realm, LDAPObject ldapUser) {
String ldapUsername = LDAPUtils.getUsername(ldapUser, ldapIdentityStore.getConfig()); String ldapUsername = LDAPUtils.getUsername(ldapUser, ldapIdentityStore.getConfig());
if (ldapUsername == null) {
throw new ModelException("User returned from LDAP has null username! Check configuration of your LDAP mappings. Mapped username LDAP attribute: " +
ldapIdentityStore.getConfig().getUsernameLdapAttribute() + ", attributes from LDAP: " + ldapUser.getAttributes());
}
UserModel imported = session.userStorage().addUser(realm, ldapUsername); UserModel imported = session.userStorage().addUser(realm, ldapUsername);
imported.setEnabled(true); imported.setEnabled(true);
Set<UserFederationMapperModel> federationMappers = realm.getUserFederationMappersByFederationProvider(getModel().getId()); Set<UserFederationMapperModel> federationMappers = realm.getUserFederationMappersByFederationProvider(getModel().getId());
for (UserFederationMapperModel mapperModel : federationMappers) { for (UserFederationMapperModel mapperModel : federationMappers) {
if (logger.isTraceEnabled()) {
logger.tracef("Using mapper %s during import user from LDAP", mapperModel);
}
LDAPFederationMapper ldapMapper = getMapper(mapperModel); LDAPFederationMapper ldapMapper = getMapper(mapperModel);
ldapMapper.onImportUserFromLDAP(mapperModel, this, ldapUser, imported, realm, true); ldapMapper.onImportUserFromLDAP(mapperModel, this, ldapUser, imported, realm, true);
} }

View file

@ -332,12 +332,19 @@ public class LDAPFederationProviderFactory extends UserFederationEventAwareProvi
public void run(KeycloakSession session) { public void run(KeycloakSession session) {
LDAPFederationProvider ldapFedProvider = getInstance(session, fedModel); LDAPFederationProvider ldapFedProvider = getInstance(session, fedModel);
RealmModel currentRealm = session.realms().getRealm(realmId); RealmModel currentRealm = session.realms().getRealm(realmId);
String username = LDAPUtils.getUsername(ldapUser, ldapFedProvider.getLdapIdentityStore().getConfig()); String username = null;
try {
username = LDAPUtils.getUsername(ldapUser, ldapFedProvider.getLdapIdentityStore().getConfig());
} catch (ModelException ignore) {
}
if (username != null) {
UserModel existing = session.userStorage().getUserByUsername(username, currentRealm); UserModel existing = session.userStorage().getUserByUsername(username, currentRealm);
if (existing != null) { if (existing != null) {
session.userStorage().removeUser(currentRealm, existing); session.userStorage().removeUser(currentRealm, existing);
} }
} }
}
}); });
} }

View file

@ -72,6 +72,13 @@ public class LDAPUtils {
public static String getUsername(LDAPObject ldapUser, LDAPConfig config) { public static String getUsername(LDAPObject ldapUser, LDAPConfig config) {
String usernameAttr = config.getUsernameLdapAttribute(); String usernameAttr = config.getUsernameLdapAttribute();
return ldapUser.getAttributeAsString(usernameAttr); String ldapUsername = ldapUser.getAttributeAsString(usernameAttr);
if (ldapUsername == null) {
throw new ModelException("User returned from LDAP has null username! Check configuration of your LDAP mappings. Mapped username LDAP attribute: " +
config.getUsernameLdapAttribute() + ", user DN: " + ldapUser.getDn() + ", attributes from LDAP: " + ldapUser.getAttributes());
}
return ldapUsername;
} }
} }

View file

@ -129,4 +129,9 @@ public class LDAPObject {
result = 31 * result + (getUuid() != null ? getUuid().hashCode() : 0); result = 31 * result + (getUuid() != null ? getUuid().hashCode() : 0);
return result; return result;
} }
@Override
public String toString() {
return "LDAP Object [ dn: " + dn + " , uuid: " + uuid + ", attributes: " + attributes + ", readOnly attribute names: " + readOnlyAttributeNames + " ]";
}
} }

View file

@ -81,8 +81,8 @@ public class LDAPIdentityStore implements IdentityStore {
this.operationManager.createSubContext(entryDN, ldapAttributes); this.operationManager.createSubContext(entryDN, ldapAttributes);
ldapObject.setUuid(getEntryIdentifier(ldapObject)); ldapObject.setUuid(getEntryIdentifier(ldapObject));
if (logger.isTraceEnabled()) { if (logger.isDebugEnabled()) {
logger.tracef("Type with identifier [%s] and dn [%s] successfully added to LDAP store.", ldapObject.getUuid(), entryDN); logger.debugf("Type with identifier [%s] and dn [%s] successfully added to LDAP store.", ldapObject.getUuid(), entryDN);
} }
} }
@ -94,8 +94,8 @@ public class LDAPIdentityStore implements IdentityStore {
String entryDn = ldapObject.getDn().toString(); String entryDn = ldapObject.getDn().toString();
this.operationManager.modifyAttributes(entryDn, attributes); this.operationManager.modifyAttributes(entryDn, attributes);
if (logger.isTraceEnabled()) { if (logger.isDebugEnabled()) {
logger.tracef("Type with identifier [%s] and DN [%s] successfully updated to LDAP store.", ldapObject.getUuid(), entryDn); logger.debugf("Type with identifier [%s] and DN [%s] successfully updated to LDAP store.", ldapObject.getUuid(), entryDn);
} }
} }
@ -103,8 +103,8 @@ public class LDAPIdentityStore implements IdentityStore {
public void remove(LDAPObject ldapObject) { public void remove(LDAPObject ldapObject) {
this.operationManager.removeEntry(ldapObject.getDn().toString()); this.operationManager.removeEntry(ldapObject.getDn().toString());
if (logger.isTraceEnabled()) { if (logger.isDebugEnabled()) {
logger.tracef("Type with identifier [%s] and DN [%s] successfully removed from LDAP store.", ldapObject.getUuid(), ldapObject.getDn().toString()); logger.debugf("Type with identifier [%s] and DN [%s] successfully removed from LDAP store.", ldapObject.getUuid(), ldapObject.getDn().toString());
} }
} }
@ -429,7 +429,7 @@ public class LDAPIdentityStore implements IdentityStore {
} }
if (logger.isTraceEnabled()) { if (logger.isTraceEnabled()) {
logger.tracef("Found ldap object [%s] and populated with the attributes [%s]. Read-only attributes are [%s]", ldapObject.getDn().toString(), ldapObject.getAttributes(), ldapObject.getReadOnlyAttributeNames()); logger.tracef("Found ldap object and populated with the attributes. LDAP Object: %s", ldapObject.toString());
} }
return ldapObject; return ldapObject;

View file

@ -76,4 +76,12 @@ public class UserFederationMapperModel implements Serializable {
public int hashCode() { public int hashCode() {
return id.hashCode(); return id.hashCode();
} }
@Override
public String toString() {
return new StringBuilder(" { name=" + name)
.append(", federationMapperType=" + federationMapperType)
.append(", config=" + config)
.append(" } ").toString();
}
} }

View file

@ -102,14 +102,14 @@ class FederationTestUtils {
addUserAttributeMapper(realm, providerModel, "zipCodeMapper", "postal_code", LDAPConstants.POSTAL_CODE); addUserAttributeMapper(realm, providerModel, "zipCodeMapper", "postal_code", LDAPConstants.POSTAL_CODE);
} }
public static void addUserAttributeMapper(RealmModel realm, UserFederationProviderModel providerModel, String mapperName, String userModelAttributeName, String ldapAttributeName) { public static UserFederationMapperModel addUserAttributeMapper(RealmModel realm, UserFederationProviderModel providerModel, String mapperName, String userModelAttributeName, String ldapAttributeName) {
UserFederationMapperModel mapperModel = KeycloakModelUtils.createUserFederationMapperModel(mapperName, providerModel.getId(), UserAttributeLDAPFederationMapperFactory.PROVIDER_ID, UserFederationMapperModel mapperModel = KeycloakModelUtils.createUserFederationMapperModel(mapperName, providerModel.getId(), UserAttributeLDAPFederationMapperFactory.PROVIDER_ID,
UserAttributeLDAPFederationMapper.USER_MODEL_ATTRIBUTE, userModelAttributeName, UserAttributeLDAPFederationMapper.USER_MODEL_ATTRIBUTE, userModelAttributeName,
UserAttributeLDAPFederationMapper.LDAP_ATTRIBUTE, ldapAttributeName, UserAttributeLDAPFederationMapper.LDAP_ATTRIBUTE, ldapAttributeName,
UserAttributeLDAPFederationMapper.READ_ONLY, "false", UserAttributeLDAPFederationMapper.READ_ONLY, "false",
UserAttributeLDAPFederationMapper.ALWAYS_READ_VALUE_FROM_LDAP, "false", UserAttributeLDAPFederationMapper.ALWAYS_READ_VALUE_FROM_LDAP, "false",
UserAttributeLDAPFederationMapper.IS_MANDATORY_IN_LDAP, "false"); UserAttributeLDAPFederationMapper.IS_MANDATORY_IN_LDAP, "false");
realm.addUserFederationMapper(mapperModel); return realm.addUserFederationMapper(mapperModel);
} }
public static void addOrUpdateRoleLDAPMappers(RealmModel realm, UserFederationProviderModel providerModel, RoleLDAPFederationMapper.Mode mode) { public static void addOrUpdateRoleLDAPMappers(RealmModel realm, UserFederationProviderModel providerModel, RoleLDAPFederationMapper.Mode mode) {

View file

@ -11,10 +11,12 @@ import org.keycloak.federation.ldap.LDAPConfig;
import org.keycloak.federation.ldap.LDAPFederationProvider; import org.keycloak.federation.ldap.LDAPFederationProvider;
import org.keycloak.federation.ldap.LDAPFederationProviderFactory; import org.keycloak.federation.ldap.LDAPFederationProviderFactory;
import org.keycloak.federation.ldap.idm.model.LDAPObject; import org.keycloak.federation.ldap.idm.model.LDAPObject;
import org.keycloak.federation.ldap.mappers.UserAttributeLDAPFederationMapper;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory; import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.models.LDAPConstants; import org.keycloak.models.LDAPConstants;
import org.keycloak.models.RealmModel; import org.keycloak.models.RealmModel;
import org.keycloak.models.UserFederationMapperModel;
import org.keycloak.models.UserFederationProvider; import org.keycloak.models.UserFederationProvider;
import org.keycloak.models.UserFederationProviderModel; import org.keycloak.models.UserFederationProviderModel;
import org.keycloak.models.UserFederationSyncResult; import org.keycloak.models.UserFederationSyncResult;
@ -287,6 +289,68 @@ public class SyncProvidersTest {
} }
} }
// KEYCLOAK-1728
@Test
public void test04MissingLDAPUsernameSync() {
KeycloakSession session = keycloakRule.startSession();
String origUsernameAttrName;
try {
RealmModel testRealm = session.realms().getRealm("test");
// Remove all users from model
for (UserModel user : session.userStorage().getUsers(testRealm, true)) {
session.userStorage().removeUser(testRealm, user);
}
UserFederationProviderModel providerModel = KeycloakModelUtils.findUserFederationProviderByDisplayName(ldapModel.getDisplayName(), testRealm);
// Add street mapper and add some user including street
UserFederationMapperModel streetMapper = FederationTestUtils.addUserAttributeMapper(testRealm, ldapModel, "streetMapper", "street", LDAPConstants.STREET);
LDAPFederationProvider ldapFedProvider = FederationTestUtils.getLdapProvider(session, ldapModel);
LDAPObject streetUser = FederationTestUtils.addLDAPUser(ldapFedProvider, testRealm, "user8", "User8FN", "User8LN", "user8@email.org", "user8street", "126");
// Change name of username attribute name to street
origUsernameAttrName = providerModel.getConfig().get(LDAPConstants.USERNAME_LDAP_ATTRIBUTE);
providerModel.getConfig().put(LDAPConstants.USERNAME_LDAP_ATTRIBUTE, "street");
// Need to change this due to ApacheDS pagination bug (For other LDAP servers, pagination works fine) TODO: Remove once ApacheDS upgraded and pagination is fixed
providerModel.getConfig().put(LDAPConstants.BATCH_SIZE_FOR_SYNC, "10");
testRealm.updateUserFederationProvider(providerModel);
} finally {
keycloakRule.stopSession(session, true);
}
// Just user8 synced. All others failed to sync
session = keycloakRule.startSession();
try {
RealmModel testRealm = session.realms().getRealm("test");
UserFederationProviderModel providerModel = KeycloakModelUtils.findUserFederationProviderByDisplayName(ldapModel.getDisplayName(), testRealm);
KeycloakSessionFactory sessionFactory = session.getKeycloakSessionFactory();
UserFederationSyncResult syncResult = new UsersSyncManager().syncAllUsers(sessionFactory, "test", providerModel);
Assert.assertEquals(1, syncResult.getAdded());
Assert.assertTrue(syncResult.getFailed() > 0);
} finally {
keycloakRule.stopSession(session, false);
}
session = keycloakRule.startSession();
try {
RealmModel testRealm = session.realms().getRealm("test");
// Revert config changes
UserFederationProviderModel providerModel = KeycloakModelUtils.findUserFederationProviderByDisplayName(ldapModel.getDisplayName(), testRealm);
providerModel.getConfig().put(LDAPConstants.USERNAME_LDAP_ATTRIBUTE, origUsernameAttrName);
testRealm.updateUserFederationProvider(providerModel);
UserFederationMapperModel streetMapper = testRealm.getUserFederationMapperByName(providerModel.getId(), "streetMapper");
testRealm.removeUserFederationMapper(streetMapper);
} finally {
keycloakRule.stopSession(session, true);
}
}
@Test @Test
public void testPeriodicSync() { public void testPeriodicSync() {
KeycloakSession session = keycloakRule.startSession(); KeycloakSession session = keycloakRule.startSession();