From 0d823bacd046864de77156d124e060a8c8f06a22 Mon Sep 17 00:00:00 2001 From: srosse <stephane.rosse@frentix.com> Date: Fri, 14 Aug 2020 11:52:59 +0200 Subject: [PATCH] OO-4794: adapt deletion process and add unit test for it --- .../basesecurity/BaseSecurityManager.java | 19 +----- .../manager/AuthenticationDAO.java | 43 +++++++++++++ .../java/org/olat/ldap/LDAPLoginManager.java | 2 +- .../ldap/manager/LDAPLoginManagerImpl.java | 61 +++++++++---------- .../org/olat/ldap/ui/LDAPAdminController.java | 2 +- .../manager/AuthenticationDAOTest.java | 24 ++++++++ .../ldap/manager/LDAPLoginManagerTest.java | 24 ++++++++ 7 files changed, 124 insertions(+), 51 deletions(-) diff --git a/src/main/java/org/olat/basesecurity/BaseSecurityManager.java b/src/main/java/org/olat/basesecurity/BaseSecurityManager.java index 65635d6948a..41566256ffd 100644 --- a/src/main/java/org/olat/basesecurity/BaseSecurityManager.java +++ b/src/main/java/org/olat/basesecurity/BaseSecurityManager.java @@ -767,24 +767,7 @@ public class BaseSecurityManager implements BaseSecurity, UserDataDeletable { @Override public Authentication findAuthentication(IdentityRef identity, String provider) { - if (identity==null) { - throw new IllegalArgumentException("identity must not be null"); - } - - StringBuilder sb = new StringBuilder(); - sb.append("select auth from ").append(AuthenticationImpl.class.getName()) - .append(" as auth where auth.identity.key=:identityKey and auth.provider=:provider"); - - List<Authentication> results = dbInstance.getCurrentEntityManager() - .createQuery(sb.toString(), Authentication.class) - .setParameter("identityKey", identity.getKey()) - .setParameter("provider", provider) - .getResultList(); - if (results == null || results.isEmpty()) return null; - if (results.size() > 1) { - throw new AssertException("Found more than one Authentication for a given subject and a given provider."); - } - return results.get(0); + return authenticationDao.getAuthentication(identity, provider); } @Override diff --git a/src/main/java/org/olat/basesecurity/manager/AuthenticationDAO.java b/src/main/java/org/olat/basesecurity/manager/AuthenticationDAO.java index 6c5a5e456cb..0dd9f9088e6 100644 --- a/src/main/java/org/olat/basesecurity/manager/AuthenticationDAO.java +++ b/src/main/java/org/olat/basesecurity/manager/AuthenticationDAO.java @@ -32,6 +32,7 @@ import org.olat.basesecurity.IdentityRef; import org.olat.core.commons.persistence.DB; import org.olat.core.id.Identity; import org.olat.core.logging.AssertException; +import org.olat.core.util.StringHelper; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @@ -126,6 +127,48 @@ public class AuthenticationDAO { .getResultList(); } + /** + * + * @param provider The authentication provider + * @return A list of identities (the user is not fetched) + */ + public List<Authentication> getAuthentications(String provider) { + StringBuilder sb = new StringBuilder(256); + sb.append("select auth from ").append(AuthenticationImpl.class.getName()).append(" as auth") + .append(" inner join fetch auth.identity as ident") + .append(" inner join fetch ident.user as identUser") + .append(" where auth.provider=:provider"); + return dbInstance.getCurrentEntityManager() + .createQuery(sb.toString(), Authentication.class) + .setParameter("provider", provider) + .getResultList(); + } + + public Authentication getAuthentication(IdentityRef identity, String provider) { + if (identity == null || !StringHelper.containsNonWhitespace(provider)) { + throw new IllegalArgumentException("identity must not be null"); + } + + StringBuilder sb = new StringBuilder(); + sb.append("select auth from ").append(AuthenticationImpl.class.getName()).append(" as auth") + .append(" inner join auth.identity as ident") + .append(" inner join ident.user as user") + .append(" where auth.identity.key=:identityKey and auth.provider=:provider"); + + List<Authentication> results = dbInstance.getCurrentEntityManager() + .createQuery(sb.toString(), Authentication.class) + .setParameter("identityKey", identity.getKey()) + .setParameter("provider", provider) + .getResultList(); + if (results == null || results.isEmpty()) { + return null; + } + if (results.size() > 1) { + throw new AssertException("Found more than one Authentication for a given subject and a given provider."); + } + return results.get(0); + } + public Authentication getAuthentication(String authUsername, String provider) { StringBuilder sb = new StringBuilder(256); sb.append("select auth from ").append(AuthenticationImpl.class.getName()).append(" as auth") diff --git a/src/main/java/org/olat/ldap/LDAPLoginManager.java b/src/main/java/org/olat/ldap/LDAPLoginManager.java index fb2fd84a067..a937094eb06 100644 --- a/src/main/java/org/olat/ldap/LDAPLoginManager.java +++ b/src/main/java/org/olat/ldap/LDAPLoginManager.java @@ -51,7 +51,7 @@ public interface LDAPLoginManager { public Map<String,String> prepareUserPropertyForSync(Attributes attributes, Identity identity); - public List<Identity> getIdentitysDeletedInLdap(LdapContext ctx); + public List<Identity> getIdentitiesDeletedInLdap(LdapContext ctx); public Identity findIdentityByLdapAuthentication(Attributes attrs, LDAPError errors); diff --git a/src/main/java/org/olat/ldap/manager/LDAPLoginManagerImpl.java b/src/main/java/org/olat/ldap/manager/LDAPLoginManagerImpl.java index 9657a972412..d2e893f920e 100644 --- a/src/main/java/org/olat/ldap/manager/LDAPLoginManagerImpl.java +++ b/src/main/java/org/olat/ldap/manager/LDAPLoginManagerImpl.java @@ -409,7 +409,9 @@ public class LDAPLoginManagerImpl implements LDAPLoginManager, AuthenticationPro */ @Override public boolean changePassword(Identity identity, String pwd, LDAPError errors) { - String uid = identity.getName(); + String uid = identity.getName();//TODO username + + String ldapUserPasswordAttribute = syncConfiguration.getLdapUserPasswordAttribute(); try { LdapContext ctx = bindSystem(); @@ -724,10 +726,12 @@ public class LDAPLoginManagerImpl implements LDAPLoginManager, AuthenticationPro LdapContext ctx = bindSystem(); if (ctx == null) { log.error("could not bind to ldap"); + return; } String ldapUserIDAttribute = syncConfiguration.getOlatPropertyToLdapAttribute(LDAPConstants.LDAP_USER_IDENTIFYER); - String filter = ldapDao.buildSearchUserFilter(ldapUserIDAttribute, identity.getName()); + Authentication authentication = authenticationDao.getAuthentication(identity, LDAPAuthenticationController.PROVIDER_LDAP); + String filter = ldapDao.buildSearchUserFilter(ldapUserIDAttribute, authentication.getAuthusername()); boolean withCoacheOfGroups = StringHelper.containsNonWhitespace(syncConfiguration.getCoachedGroupAttribute()); List<String> ldapBases = syncConfiguration.getLdapBases(); @@ -874,10 +878,11 @@ public class LDAPLoginManagerImpl implements LDAPLoginManager, AuthenticationPro * @throws NamingException */ @Override - public List<Identity> getIdentitysDeletedInLdap(LdapContext ctx) { + public List<Identity> getIdentitiesDeletedInLdap(LdapContext ctx) { if (ctx == null) { - return null; + return Collections.emptyList(); } + // Find all LDAP Users String userID = syncConfiguration.getOlatPropertyToLdapAttribute(LDAPConstants.LDAP_USER_IDENTIFYER); String userFilter = syncConfiguration.getLdapUserFilter(); @@ -897,18 +902,18 @@ public class LDAPLoginManagerImpl implements LDAPLoginManager, AuthenticationPro }, (userFilter == null ? "" : userFilter), new String[] { userID }, ctx); if (ldapList.isEmpty()) { - log.warn("No users in LDAP found, can't create deletionList!!"); + log.warn("No users in LDAP found, can't create the deletion list."); return Collections.emptyList(); } - + List<Identity> identityListToDelete = new ArrayList<>(); - List<Identity> olatListIdentity = authenticationDao.getIdentitiesWithAuthentication(LDAPAuthenticationController.PROVIDER_LDAP); - for (Identity ida:olatListIdentity) { - // compare usernames with lowercase - if (!ldapList.contains(ida.getName().toLowerCase())) { - identityListToDelete.add(ida); + List<Authentication> ldapAuthentications = authenticationDao.getAuthentications(LDAPAuthenticationController.PROVIDER_LDAP); + for (Authentication ldapAuthentication:ldapAuthentications) { + if (!ldapList.contains(ldapAuthentication.getAuthusername().toLowerCase())) { + identityListToDelete.add(ldapAuthentication.getIdentity()); } } + dbInstance.commitAndCloseSession(); return identityListToDelete; } @@ -1167,7 +1172,7 @@ public class LDAPLoginManagerImpl implements LDAPLoginManager, AuthenticationPro break; } default: { - log.error("LDAP Role synchronization not supported for: " + role); + log.error("LDAP Role synchronization not supported for: {}", role); } } } @@ -1175,11 +1180,12 @@ public class LDAPLoginManagerImpl implements LDAPLoginManager, AuthenticationPro private void doBatchSyncDeletedUsers(LdapContext ctx, String sinceSentence) { // create User to Delete List - List<Identity> deletedUserList = getIdentitysDeletedInLdap(ctx); + List<Identity> deletedUserList = getIdentitiesDeletedInLdap(ctx); // delete old users if (deletedUserList == null || deletedUserList.isEmpty()) { - log.info("LDAP batch sync: no users to delete" + sinceSentence); + log.info("LDAP batch sync: no users to delete {}", sinceSentence); } else { + int deletedUserListSize = deletedUserList.size(); if (ldapLoginModule.isDeleteRemovedLDAPUsersOnSync()) { // check if more not more than the defined percentages of // users managed in LDAP should be deleted @@ -1189,31 +1195,24 @@ public class LDAPLoginManagerImpl implements LDAPLoginManager, AuthenticationPro if (olatListIdentity.isEmpty()) log.info("No users managed by LDAP, can't delete users"); else { - int prozente = (int) (((float)deletedUserList.size() / (float) olatListIdentity.size())*100); - if (prozente >= ldapLoginModule.getDeleteRemovedLDAPUsersPercentage()) { - log.info("LDAP batch sync: more than " - + ldapLoginModule.getDeleteRemovedLDAPUsersPercentage() - + "% of LDAP managed users should be deleted. Please use Admin Deletion Job. Or increase deleteRemovedLDAPUsersPercentage. " - + prozente - + "% tried to delete."); + int prozente = (int) (((float)deletedUserListSize / (float) olatListIdentity.size()) * 100.0); + int cutValue = ldapLoginModule.getDeleteRemovedLDAPUsersPercentage(); + if (prozente >= cutValue) { + log.info("LDAP batch sync: more than {}% of LDAP managed users should be deleted. Please use Admin Deletion Job. Or increase deleteRemovedLDAPUsersPercentage. {}% tried to delete.", cutValue, prozente); } else { // delete users deleteIdentities(deletedUserList, null); - log.info("LDAP batch sync: " + deletedUserList.size() + " users deleted" + sinceSentence); + log.info("LDAP batch sync: {} users deleted {}", deletedUserListSize, sinceSentence); } } } else { // Do nothing, only log users to logfile - StringBuilder users = new StringBuilder(); + StringBuilder users = new StringBuilder(deletedUserListSize * 42); for (Identity toBeDeleted : deletedUserList) { - users.append(toBeDeleted.getName()).append(','); + users.append(toBeDeleted.getKey()).append(','); } - log.info("LDAP batch sync: " - + deletedUserList.size() - + " users detected as to be deleted" - + sinceSentence - + ". Automatic deleting is disabled in LDAPLoginModule, delete these users manually::[" - + users.toString() + "]"); + log.info("LDAP batch sync: {} users detected as to be deleted {}. Automatic deleting is disabled in LDAPLoginModule, delete these users manually::[{}]", + deletedUserListSize, sinceSentence, users); } } dbInstance.commitAndCloseSession(); @@ -1558,7 +1557,7 @@ public class LDAPLoginManagerImpl implements LDAPLoginManager, AuthenticationPro } String ldapUserIDAttribute = syncConfiguration.getLdapUserLoginAttribute(); - Authentication authentication = securityManager.findAuthentication(ident, LDAPAuthenticationController.PROVIDER_LDAP); + Authentication authentication = authenticationDao.getAuthentication(ident, LDAPAuthenticationController.PROVIDER_LDAP); String filter = ldapDao.buildSearchUserFilter(ldapUserIDAttribute, authentication.getAuthusername()); List<Attributes> ldapUserAttrs = new ArrayList<>(); diff --git a/src/main/java/org/olat/ldap/ui/LDAPAdminController.java b/src/main/java/org/olat/ldap/ui/LDAPAdminController.java index 4c8cc68041f..b5df6f6f3bf 100644 --- a/src/main/java/org/olat/ldap/ui/LDAPAdminController.java +++ b/src/main/java/org/olat/ldap/ui/LDAPAdminController.java @@ -190,7 +190,7 @@ public class LDAPAdminController extends BasicController implements GenericEvent // get deleted users List<Identity> identitiesToDelete = null; try { - identitiesToDelete = ldapLoginManager.getIdentitysDeletedInLdap(ctx); + identitiesToDelete = ldapLoginManager.getIdentitiesDeletedInLdap(ctx); ctx.close(); } catch (NamingException e) { showError("delete.error.connection.close"); diff --git a/src/test/java/org/olat/basesecurity/manager/AuthenticationDAOTest.java b/src/test/java/org/olat/basesecurity/manager/AuthenticationDAOTest.java index 722c3b224e3..eb116d908e5 100644 --- a/src/test/java/org/olat/basesecurity/manager/AuthenticationDAOTest.java +++ b/src/test/java/org/olat/basesecurity/manager/AuthenticationDAOTest.java @@ -70,6 +70,17 @@ public class AuthenticationDAOTest extends OlatTestCase { Assert.assertEquals(newToken, updatedAuth.getCredential()); } + @Test + public void getAuthenticationIdentityProvider() { + IdentityWithLogin ident = JunitTestHelper.createAndPersistRndUser("authdao-1-"); + dbInstance.commitAndCloseSession(); + + //check if the new token was saved + Authentication authentication = authenticationDao.getAuthentication(ident.getIdentity(), "OLAT"); + Assert.assertEquals(ident.getIdentity(), authentication.getIdentity()); + Assert.assertEquals(ident.getLogin(), authentication.getAuthusername()); + } + @Test public void getIdentitiesWithAuthentication() { String token = UUID.randomUUID().toString(); @@ -142,6 +153,19 @@ public class AuthenticationDAOTest extends OlatTestCase { Assert.assertEquals(ident.getIdentity(), olatAuthentications.get(0).getIdentity()); } + @Test + public void getAuthenticationsByProvider() { + IdentityWithLogin ident = JunitTestHelper.createAndPersistRndUser("authdao-2-"); + dbInstance.commitAndCloseSession(); + Assert.assertNotNull(ident); + + List<Authentication> olatAuthentications = authenticationDao.getAuthentications("OLAT"); + Assert.assertNotNull(olatAuthentications); + for(Authentication authentication:olatAuthentications) { + Assert.assertEquals("OLAT", authentication.getProvider()); + } + } + @Test public void getAuthenticationsByAuthusername_providersList() { String token = UUID.randomUUID().toString(); diff --git a/src/test/java/org/olat/ldap/manager/LDAPLoginManagerTest.java b/src/test/java/org/olat/ldap/manager/LDAPLoginManagerTest.java index e107d4b668e..a8bbc666384 100644 --- a/src/test/java/org/olat/ldap/manager/LDAPLoginManagerTest.java +++ b/src/test/java/org/olat/ldap/manager/LDAPLoginManagerTest.java @@ -22,9 +22,11 @@ package org.olat.ldap.manager; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.UUID; import java.util.stream.Collectors; import javax.naming.directory.Attributes; +import javax.naming.ldap.LdapContext; import org.junit.Assert; import org.junit.Assume; @@ -47,6 +49,7 @@ import org.olat.ldap.LDAPLoginManager; import org.olat.ldap.LDAPLoginModule; import org.olat.ldap.LDAPSyncConfiguration; import org.olat.ldap.ui.LDAPAuthenticationController; +import org.olat.test.JunitTestHelper; import org.olat.test.OlatTestCase; import org.olat.user.UserManager; import org.springframework.beans.factory.annotation.Autowired; @@ -106,6 +109,27 @@ public class LDAPLoginManagerTest extends OlatTestCase { Assert.assertNotNull(identity); } + @Test + public void getIdentitiesDeletedInLdap() { + Assume.assumeTrue(ldapLoginModule.isLDAPEnabled()); + Identity orphan = JunitTestHelper.createAndPersistIdentityAsRndUser("ldap-orphan"); + securityManager.createAndPersistAuthentication(orphan, LDAPAuthenticationController.PROVIDER_LDAP, + UUID.randomUUID().toString(), null, null); + dbInstance.commitAndCloseSession(); + + LdapContext ctx = ldapManager.bindSystem(); + List<Identity> identities = ldapManager.getIdentitiesDeletedInLdap(ctx); + + Assert.assertNotNull(identities); + Assert.assertTrue(identities.contains(orphan)); + + // historic + Identity identity1 = userManager.findUniqueIdentityByEmail("hhuerlimann@openolat.com"); + Assert.assertFalse(identities.contains(identity1)); + Identity identity2 = userManager.findUniqueIdentityByEmail("ahentschel@openolat.com"); + Assert.assertFalse(identities.contains(identity2)); + } + @Test public void doSyncSingleUserWithLoginAttribute() throws LDAPException { Assume.assumeTrue(ldapLoginModule.isLDAPEnabled()); -- GitLab