mirror of
https://github.com/Alfresco/alfresco-community-repo.git
synced 2025-10-08 14:51:49 +00:00
128140 cpopa: MNT-14026: LDAP sync fails to update attribute's value deletion - Update to null, the node properties for which the corresponding LDAP attribute is missing. git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/BRANCHES/DEV/5.2.N/root@128144 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
1720 lines
67 KiB
Java
1720 lines
67 KiB
Java
/*
|
|
* #%L
|
|
* Alfresco Repository
|
|
* %%
|
|
* Copyright (C) 2005 - 2016 Alfresco Software Limited
|
|
* %%
|
|
* This file is part of the Alfresco software.
|
|
* If the software was purchased under a paid Alfresco license, the terms of
|
|
* the paid license agreement will prevail. Otherwise, the software is
|
|
* provided under the following open source license terms:
|
|
*
|
|
* Alfresco is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU Lesser General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* Alfresco is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public License
|
|
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
|
* #L%
|
|
*/
|
|
package org.alfresco.repo.security.sync;
|
|
|
|
import static org.mockito.Mockito.mock;
|
|
import static org.mockito.Mockito.when;
|
|
import static org.mockito.Mockito.any;
|
|
import junit.framework.TestCase;
|
|
|
|
import org.alfresco.error.AlfrescoRuntimeException;
|
|
import org.alfresco.model.ContentModel;
|
|
import org.alfresco.repo.management.subsystems.ActivateableBean;
|
|
import org.alfresco.repo.management.subsystems.ChildApplicationContextManager;
|
|
import org.alfresco.repo.security.authentication.AuthenticationContext;
|
|
import org.alfresco.repo.security.authentication.AuthenticationException;
|
|
import org.alfresco.repo.security.authentication.AuthenticationUtil;
|
|
import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork;
|
|
import org.alfresco.repo.security.authentication.ldap.LDAPInitialDirContextFactoryImpl;
|
|
import org.alfresco.repo.security.person.PersonServiceImpl;
|
|
import org.alfresco.repo.security.sync.ldap.AbstractDirectoryServiceUserAccountStatusInterpreter;
|
|
import org.alfresco.repo.security.sync.ldap.LDAPUserAccountStatusInterpreter;
|
|
import org.alfresco.repo.security.sync.ldap.LDAPUserRegistry;
|
|
import org.alfresco.repo.security.sync.ldap.LDAPUserRegistry.PersonCollection;
|
|
import org.alfresco.repo.security.sync.ldap_ad.LDAPADUserAccountStatusInterpreter;
|
|
import org.alfresco.repo.transaction.RetryingTransactionHelper;
|
|
import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
|
|
import org.alfresco.service.cmr.repository.NodeRef;
|
|
import org.alfresco.service.cmr.repository.NodeService;
|
|
import org.alfresco.service.cmr.repository.datatype.DefaultTypeConverter;
|
|
import org.alfresco.service.cmr.security.AuthorityService;
|
|
import org.alfresco.service.cmr.security.AuthorityType;
|
|
import org.alfresco.service.cmr.security.PersonService;
|
|
import org.alfresco.service.namespace.NamespaceService;
|
|
import org.alfresco.service.namespace.QName;
|
|
import org.alfresco.util.GUID;
|
|
import org.alfresco.util.PropertyMap;
|
|
import org.mockito.Mockito;
|
|
import org.springframework.context.ApplicationContext;
|
|
import org.springframework.context.ApplicationEvent;
|
|
import org.springframework.context.ApplicationListener;
|
|
import org.springframework.context.support.ClassPathXmlApplicationContext;
|
|
import org.springframework.context.support.StaticApplicationContext;
|
|
|
|
import java.io.Serializable;
|
|
import java.util.*;
|
|
|
|
import javax.naming.NamingEnumeration;
|
|
import javax.naming.NamingException;
|
|
import javax.naming.directory.BasicAttribute;
|
|
import javax.naming.directory.BasicAttributes;
|
|
import javax.naming.directory.InitialDirContext;
|
|
import javax.naming.directory.SearchControls;
|
|
import javax.naming.directory.SearchResult;
|
|
|
|
/**
|
|
* Tests the {@link ChainingUserRegistrySynchronizer} using a simulated {@link UserRegistry}.
|
|
*
|
|
* @author dward
|
|
*/
|
|
public class ChainingUserRegistrySynchronizerTest extends TestCase
|
|
{
|
|
|
|
/** The context locations, in reverse priority order. */
|
|
private static final String[] CONFIG_LOCATIONS =
|
|
{
|
|
"classpath:alfresco/application-context.xml", "classpath:sync-test-context.xml"
|
|
};
|
|
|
|
/** The Spring application context. */
|
|
private static ApplicationContext context = new ClassPathXmlApplicationContext(
|
|
ChainingUserRegistrySynchronizerTest.CONFIG_LOCATIONS);
|
|
|
|
/** The synchronizer we are testing. */
|
|
private UserRegistrySynchronizer synchronizer;
|
|
|
|
/** The application context manager. */
|
|
private MockApplicationContextManager applicationContextManager;
|
|
|
|
/** The namespace service. */
|
|
private NamespaceService namespaceService;
|
|
|
|
/** The person service. */
|
|
private PersonService personService;
|
|
|
|
/** The authority service. */
|
|
private AuthorityService authorityService;
|
|
|
|
/** The node service. */
|
|
private NodeService nodeService;
|
|
|
|
/** The authentication context. */
|
|
private AuthenticationContext authenticationContext;
|
|
|
|
/** The retrying transaction helper. */
|
|
private RetryingTransactionHelper retryingTransactionHelper;
|
|
|
|
/** The value given to the person service. */
|
|
private boolean homeFolderCreationEager;
|
|
|
|
/*
|
|
* (non-Javadoc)
|
|
* @see junit.framework.TestCase#setUp()
|
|
*/
|
|
@Override
|
|
protected void setUp() throws Exception
|
|
{
|
|
this.synchronizer = (UserRegistrySynchronizer) ChainingUserRegistrySynchronizerTest.context
|
|
.getBean("testUserRegistrySynchronizer");
|
|
this.applicationContextManager = (MockApplicationContextManager) ChainingUserRegistrySynchronizerTest.context
|
|
.getBean("testApplicationContextManager");
|
|
this.personService = (PersonService) ChainingUserRegistrySynchronizerTest.context.getBean("personService");
|
|
this.authorityService = (AuthorityService) ChainingUserRegistrySynchronizerTest.context
|
|
.getBean("authorityService");
|
|
this.nodeService = (NodeService) ChainingUserRegistrySynchronizerTest.context.getBean("nodeService");
|
|
|
|
this.authenticationContext = (AuthenticationContext) ChainingUserRegistrySynchronizerTest.context
|
|
.getBean("authenticationContext");
|
|
|
|
|
|
// this.authenticationContext.setSystemUserAsCurrentUser();
|
|
//AuthenticationUtil.setFullyAuthenticatedUser(AuthenticationUtil.getAdminUserName());
|
|
this.authenticationContext.setSystemUserAsCurrentUser();
|
|
|
|
this.retryingTransactionHelper = (RetryingTransactionHelper) ChainingUserRegistrySynchronizerTest.context
|
|
.getBean("retryingTransactionHelper");
|
|
setHomeFolderCreationEager(false); // the normal default if using LDAP
|
|
|
|
this.namespaceService = (NamespaceService) ChainingUserRegistrySynchronizerTest.context
|
|
.getBean("namespaceService");
|
|
}
|
|
|
|
/*
|
|
* (non-Javadoc)
|
|
* @see junit.framework.TestCase#tearDown()
|
|
*/
|
|
@Override
|
|
protected void tearDown() throws Exception
|
|
{
|
|
this.authenticationContext.clearCurrentSecurityContext();
|
|
setHomeFolderCreationEager(true); // the normal default if not using LDAP
|
|
}
|
|
|
|
/**
|
|
* Sets up the test users and groups in three zones, "Z0", "Z1" and "Z2", by doing a forced synchronize with a Mock
|
|
* user registry. Note that the zones have some overlapping entries. "Z0" is not used in subsequent synchronizations
|
|
* and is used to test that users and groups in zones that aren't in the authentication chain get 're-zoned'
|
|
* appropriately. The layout is as follows
|
|
*
|
|
* <pre>
|
|
* Z0
|
|
* G1
|
|
* U6
|
|
*
|
|
* Z1
|
|
* G2 - U1, G3 - U2, G4, G5
|
|
*
|
|
* Z2
|
|
* G2 - U1, U3, U4
|
|
* G6 - U3, U4, G7 - U5
|
|
* </pre>
|
|
*
|
|
* @throws Exception
|
|
* the exception
|
|
*/
|
|
private void setUpTestUsersAndGroups() throws Exception
|
|
{
|
|
this.applicationContextManager.setUserRegistries(new MockUserRegistry("Z0", new NodeDescription[]
|
|
{
|
|
newPerson("U6")
|
|
}, new NodeDescription[]
|
|
{
|
|
newGroup("G1")
|
|
}), new MockUserRegistry("Z1", new NodeDescription[]
|
|
{
|
|
newPerson("U1"), newPerson("U2"), newPerson("U7")
|
|
}, new NodeDescription[]
|
|
{
|
|
newGroup("G2", "U1", "G3"), newGroup("G3", "U2", "G4", "G5"), newGroup("G4"), newGroup("G5")
|
|
}), new MockUserRegistry("Z2", new NodeDescription[]
|
|
{
|
|
newPerson("U1"), newPerson("U3"), newPerson("U4"), newPerson("U5")
|
|
}, new NodeDescription[]
|
|
{
|
|
newGroup("G2", "U1", "U3", "U4"), newGroup("G6", "U3", "U4", "G7"), newGroup("G7", "U5")
|
|
}));
|
|
this.synchronizer.synchronize(true, true);
|
|
this.retryingTransactionHelper.doInTransaction(new RetryingTransactionCallback<Object>()
|
|
{
|
|
|
|
public Object execute() throws Throwable
|
|
{
|
|
assertExists("Z0", "U6");
|
|
assertExists("Z0", "G1");
|
|
assertExists("Z1", "U1");
|
|
assertExists("Z1", "U2");
|
|
assertExists("Z1", "G2", "U1", "G3");
|
|
assertExists("Z1", "G3", "U2", "G4", "G5");
|
|
assertExists("Z1", "G4");
|
|
assertExists("Z1", "G5");
|
|
assertExists("Z2", "U3");
|
|
assertExists("Z2", "U4");
|
|
assertExists("Z2", "U5");
|
|
assertExists("Z2", "G6", "U3", "U4", "G7");
|
|
assertExists("Z2", "G7", "U5");
|
|
return null;
|
|
}
|
|
}, false, true);
|
|
}
|
|
|
|
/**
|
|
* Tear down test users and groups.
|
|
*
|
|
* @throws Exception
|
|
* the exception
|
|
*/
|
|
public void tearDownTestUsersAndGroups() throws Exception
|
|
{
|
|
// Re-zone everything that may have gone astray
|
|
this.applicationContextManager.setUserRegistries(new MockUserRegistry("Z0", new NodeDescription[]
|
|
{
|
|
newPerson("U1"), newPerson("U2"), newPerson("U3"), newPerson("U4"), newPerson("U5"), newPerson("U6"),
|
|
newPerson("U7")
|
|
}, new NodeDescription[]
|
|
{
|
|
newGroup("G1"), newGroup("G2"), newGroup("G3"), newGroup("G4"), newGroup("G5"), newGroup("G6"),
|
|
newGroup("G7")
|
|
}), new MockUserRegistry("Z1", new NodeDescription[] {}, new NodeDescription[] {}), new MockUserRegistry("Z2",
|
|
new NodeDescription[] {}, new NodeDescription[] {}));
|
|
this.synchronizer.synchronize(true, true);
|
|
// Wipe out everything that was in Z0 - Z2
|
|
this.applicationContextManager.setUserRegistries(new MockUserRegistry("Z0", new NodeDescription[] {},
|
|
new NodeDescription[] {}), new MockUserRegistry("Z1", new NodeDescription[] {},
|
|
new NodeDescription[] {}), new MockUserRegistry("Z2", new NodeDescription[] {},
|
|
new NodeDescription[] {}));
|
|
this.synchronizer.synchronize(true, true);
|
|
this.retryingTransactionHelper.doInTransaction(new RetryingTransactionCallback<Object>()
|
|
{
|
|
|
|
public Object execute() throws Throwable
|
|
{
|
|
assertNotExists("U1");
|
|
assertNotExists("U2");
|
|
assertNotExists("U3");
|
|
assertNotExists("U4");
|
|
assertNotExists("U5");
|
|
assertNotExists("U6");
|
|
assertNotExists("U7");
|
|
assertNotExists("G1");
|
|
assertNotExists("G2");
|
|
assertNotExists("G3");
|
|
assertNotExists("G4");
|
|
assertNotExists("G5");
|
|
assertNotExists("G6");
|
|
assertNotExists("G7");
|
|
return null;
|
|
}
|
|
}, false, true);
|
|
}
|
|
|
|
public void setHomeFolderCreationEager(boolean homeFolderCreationEager)
|
|
{
|
|
this.homeFolderCreationEager = homeFolderCreationEager;
|
|
((PersonServiceImpl)personService).setHomeFolderCreationEager(homeFolderCreationEager);
|
|
}
|
|
|
|
/**
|
|
* Tests a differential update of the test users and groups. The layout is as follows
|
|
*
|
|
* <pre>
|
|
* Z1
|
|
* G2 - U1, G1 - U1, U6
|
|
* G3 - U2, G4, G5 - U6
|
|
*
|
|
* Z2
|
|
* G2 - U1, U3, U4, U6
|
|
* G6 - U3, U4, G7, G8 - U4, U8
|
|
* </pre>
|
|
*
|
|
* @throws Exception
|
|
* the exception
|
|
*/
|
|
public void testDifferentialUpdate() throws Exception
|
|
{
|
|
setUpTestUsersAndGroups();
|
|
this.applicationContextManager.removeZone("Z0");
|
|
this.applicationContextManager.updateZone("Z1", new NodeDescription[]
|
|
{
|
|
newPerson("U1", "changeofemail@alfresco.com"), newPerson("U6"), newPerson("U7")
|
|
}, new NodeDescription[]
|
|
{
|
|
newGroup("G1", "U1", "U6", "UDangling", "G2"), newGroup("G2", "U1", "GDangling", "G1"), // test cyclic G2
|
|
// <-> G1
|
|
newGroupWithDisplayName("G5", "Amazing Group", "U6", "U7", "G4")
|
|
});
|
|
this.applicationContextManager.updateZone("Z2", new NodeDescription[]
|
|
{
|
|
newPerson("U1", "shouldbeignored@alfresco.com"), newPerson("U5", "u5email@alfresco.com"), newPerson("U6"),
|
|
newPerson("U8")
|
|
}, new NodeDescription[]
|
|
{
|
|
newGroup("G2", "U1", "U3", "U4", "U6"), newGroup("G7"), newGroup("G8", "U4", "U8")
|
|
});
|
|
this.retryingTransactionHelper.doInTransaction(new RetryingTransactionCallback<Object>()
|
|
{
|
|
|
|
public Object execute() throws Throwable
|
|
{
|
|
// re: THOR-293 - note: use runAs else security context is cleared (=> current system user becomes null and personExists fails)
|
|
AuthenticationUtil.runAsSystem(new RunAsWork<Void>()
|
|
{
|
|
public Void doWork() throws Exception
|
|
{
|
|
// Split transactions to avoid MNT-9768
|
|
ChainingUserRegistrySynchronizerTest.this.synchronizer.synchronize(false, false);
|
|
return null;
|
|
}
|
|
});
|
|
|
|
// Stay in the same transaction
|
|
assertExists("Z1", "U1");
|
|
assertEmailEquals("U1", "changeofemail@alfresco.com");
|
|
assertExists("Z1", "U2");
|
|
assertExists("Z1", "U6");
|
|
assertExists("Z1", "U7");
|
|
assertExists("Z1", "G1", "U1", "U6");
|
|
assertExists("Z1", "G2", "U1", "G1");
|
|
assertExists("Z1", "G3", "U2", "G4", "G5");
|
|
assertExists("Z1", "G4");
|
|
assertExists("Z1", "G5", "U6", "U7", "G4");
|
|
assertGroupDisplayNameEquals("G5", "Amazing Group");
|
|
assertExists("Z2", "U3");
|
|
assertExists("Z2", "U4");
|
|
assertExists("Z2", "U5");
|
|
assertEmailEquals("U5", "u5email@alfresco.com");
|
|
assertExists("Z2", "U8");
|
|
assertExists("Z2", "G6", "U3", "U4", "G7");
|
|
assertExists("Z2", "G7");
|
|
assertExists("Z2", "G8", "U4", "U8");
|
|
return null;
|
|
}
|
|
});
|
|
tearDownTestUsersAndGroups();
|
|
}
|
|
|
|
/**
|
|
* Tests a forced update of the test users and groups. Also tests that groups and users that previously existed in
|
|
* Z2 get moved when they appear in Z1. Also tests that 'dangling references' to removed users (U4, U5) do not cause
|
|
* any problems. Also tests that case-sensitivity is not a problem when an occluded user is recreated with different
|
|
* case. The layout is as follows
|
|
*
|
|
* <pre>
|
|
* Z1
|
|
* G1 - U6
|
|
* G3 - U2, G5 - U6, G2 - G1
|
|
* G6 - u3
|
|
*
|
|
* Z2
|
|
* G2 - U1
|
|
* G6 - U3, G7
|
|
* G8 - U1, U8
|
|
* </pre>
|
|
*
|
|
* @throws Exception
|
|
* the exception
|
|
*/
|
|
public void testForcedUpdate() throws Exception
|
|
{
|
|
setUpTestUsersAndGroups();
|
|
this.applicationContextManager.setUserRegistries(new MockUserRegistry("Z1", new NodeDescription[]
|
|
{
|
|
newPerson("U2"), newPerson("u3"), newPerson("U6")
|
|
}, new NodeDescription[]
|
|
{
|
|
newGroup("G1", "U6", "G5"), newGroup("G2", "G1"), newGroup("G3", "U2", "G5"), newGroup("G5", "U6", "G2"), // cycle g1 -> g5 -> g2 -> g1
|
|
newGroup("G6", "u3")
|
|
}), new MockUserRegistry("Z2", new NodeDescription[]
|
|
{
|
|
newPerson("U1", "somenewemail@alfresco.com"), newPerson("U3"), newPerson("U6"), newPerson("U8"),
|
|
}, new NodeDescription[]
|
|
{
|
|
newGroup("G2", "U1", "U3", "U4", "U6"), newGroup("G6", "U3", "U4", "G7"),
|
|
newGroupWithDisplayName("G7", "Late Arrival", "U4", "U5"), newGroup("G8", "U1", "U8")
|
|
}));
|
|
this.synchronizer.synchronize(true, true);
|
|
this.retryingTransactionHelper.doInTransaction(new RetryingTransactionCallback<Object>()
|
|
{
|
|
|
|
public Object execute() throws Throwable
|
|
{
|
|
assertExists("Z1", "U2");
|
|
assertExists("Z1", "u3");
|
|
assertExists("Z1", "U6");
|
|
assertExists("Z1", "G1", "U6");
|
|
assertExists("Z1", "G2", "G1");
|
|
assertExists("Z1", "G3", "U2", "G5");
|
|
assertNotExists("G4");
|
|
assertExists("Z1", "G5", "U6", "G2");
|
|
assertExists("Z1", "G6", "u3");
|
|
assertExists("Z2", "U1");
|
|
assertEmailEquals("U1", "somenewemail@alfresco.com");
|
|
assertNotExists("U4");
|
|
assertNotExists("U5");
|
|
assertExists("Z2", "U8");
|
|
assertExists("Z2", "G7");
|
|
assertGroupDisplayNameEquals("G7", "Late Arrival");
|
|
assertExists("Z2", "G8", "U1", "U8");
|
|
return null;
|
|
}
|
|
}, false, true);
|
|
tearDownTestUsersAndGroups();
|
|
}
|
|
|
|
private class MockLDAPUserRegistry extends LDAPUserRegistry implements IMockUserRegistry
|
|
{
|
|
MockUserRegistry mockUserRegistry;
|
|
|
|
public MockLDAPUserRegistry(MockUserRegistry mockUserRegistry)
|
|
{
|
|
this.mockUserRegistry = mockUserRegistry;
|
|
}
|
|
|
|
@Override
|
|
public void setActive(boolean active)
|
|
{
|
|
mockUserRegistry.setActive(active);
|
|
}
|
|
|
|
@Override
|
|
public boolean isActive()
|
|
{
|
|
return mockUserRegistry.isActive();
|
|
}
|
|
|
|
@Override
|
|
public Set<QName> getPersonMappedProperties()
|
|
{
|
|
return mockUserRegistry.getPersonMappedProperties();
|
|
}
|
|
|
|
@Override
|
|
public Collection<NodeDescription> getPersons(Date modifiedSince)
|
|
{
|
|
Collection<NodeDescription> persons = mockUserRegistry.getPersons(modifiedSince);
|
|
return !persons.isEmpty() ? persons : super.getPersons(modifiedSince);
|
|
}
|
|
|
|
@Override
|
|
public Collection<String> getPersonNames()
|
|
{
|
|
return mockUserRegistry.getPersonNames();
|
|
}
|
|
|
|
@Override
|
|
public Collection<String> getGroupNames()
|
|
{
|
|
return mockUserRegistry.getGroupNames();
|
|
}
|
|
|
|
@Override
|
|
public Collection<NodeDescription> getGroups(Date modifiedSince)
|
|
{
|
|
return mockUserRegistry.getGroups(modifiedSince);
|
|
}
|
|
|
|
@Override
|
|
public String getZoneId()
|
|
{
|
|
return mockUserRegistry.getZoneId();
|
|
}
|
|
|
|
@Override
|
|
public void updateState(Collection<NodeDescription> persons, Collection<NodeDescription> groups)
|
|
{
|
|
mockUserRegistry.updateState(persons, groups);
|
|
}
|
|
}
|
|
|
|
private void testLDAPDisableUserAccount(AbstractDirectoryServiceUserAccountStatusInterpreter userAccountStatusInterpreter,
|
|
String enabledAccountPropertyValue, String disabledAccountPropertyValue) throws Exception
|
|
{
|
|
MockUserRegistry mockUserRegistry = new MockUserRegistry("ldap1", new NodeDescription[] {
|
|
newPersonWithUserAccountStatusProperty("EnabledUser", enabledAccountPropertyValue),
|
|
newPersonWithUserAccountStatusProperty("DisabledUser", disabledAccountPropertyValue) }, new NodeDescription[] {});
|
|
|
|
MockLDAPUserRegistry mockLDAPUserRegistry = new MockLDAPUserRegistry(mockUserRegistry);
|
|
mockLDAPUserRegistry.setUserAccountStatusInterpreter(userAccountStatusInterpreter);
|
|
|
|
this.applicationContextManager.setUserRegistries(mockLDAPUserRegistry);
|
|
|
|
ChainingUserRegistrySynchronizer chainingSynchronizer = (ChainingUserRegistrySynchronizer) this.synchronizer;
|
|
chainingSynchronizer.setExternalUserControl("true");
|
|
chainingSynchronizer.setExternalUserControlSubsystemName("ldap1");
|
|
|
|
this.synchronizer.synchronize(false, false);
|
|
|
|
this.retryingTransactionHelper.doInTransaction(new RetryingTransactionCallback<Object>()
|
|
{
|
|
public Object execute() throws Throwable
|
|
{
|
|
NodeRef enabledUserRef = ChainingUserRegistrySynchronizerTest.this.personService.getPerson("EnabledUser", false);
|
|
assertFalse(ChainingUserRegistrySynchronizerTest.this.nodeService.hasAspect(enabledUserRef, ContentModel.ASPECT_PERSON_DISABLED));
|
|
|
|
NodeRef disabledUserRef = ChainingUserRegistrySynchronizerTest.this.personService.getPerson("DisabledUser", false);
|
|
assertTrue(ChainingUserRegistrySynchronizerTest.this.nodeService.hasAspect(disabledUserRef, ContentModel.ASPECT_PERSON_DISABLED));
|
|
|
|
return null;
|
|
}
|
|
}, false, true);
|
|
}
|
|
|
|
public void testLDAPDisableUserAccountWithActiveDirectoryProperty() throws Exception
|
|
{
|
|
LDAPADUserAccountStatusInterpreter ldapadUserAccountStatusInterpreter = new LDAPADUserAccountStatusInterpreter();
|
|
|
|
// Active Directory enabled account: userAccountControl=512;
|
|
// disabled account: userAccountControl=514.
|
|
testLDAPDisableUserAccount(ldapadUserAccountStatusInterpreter, "512", "514");
|
|
}
|
|
|
|
public void testLDAPDisableUserAccountWithNetscapDSProperty() throws Exception
|
|
{
|
|
LDAPUserAccountStatusInterpreter ldapUserAccountStatusInterpreter = new LDAPUserAccountStatusInterpreter();
|
|
ldapUserAccountStatusInterpreter.setAcceptNullArgument(true);
|
|
|
|
// Netscape Directory Server derivatives (Oracle, Red Hat, 389 DS)
|
|
// disabled account property: nsAccountLock=true.
|
|
ldapUserAccountStatusInterpreter.setDisabledAccountPropertyValue("true");
|
|
|
|
testLDAPDisableUserAccount(ldapUserAccountStatusInterpreter, null, "true");
|
|
}
|
|
|
|
public void testLDAPDisableUserAccountWithOpenLDAPProperty() throws Exception
|
|
{
|
|
LDAPUserAccountStatusInterpreter ldapUserAccountStatusInterpreter = new LDAPUserAccountStatusInterpreter();
|
|
ldapUserAccountStatusInterpreter.setAcceptNullArgument(true);
|
|
|
|
// OpenLDAP disabled account: pwdAccountLockedTime=000001010000Z (part of PPolicy module)
|
|
ldapUserAccountStatusInterpreter.setDisabledAccountPropertyValue("000001010000Z");
|
|
|
|
testLDAPDisableUserAccount(ldapUserAccountStatusInterpreter, null, "000001010000Z");
|
|
}
|
|
|
|
/**
|
|
* Tests a forced update of the test users and groups with deletions disabled. No users or groups should be deleted,
|
|
* whether or not they move registry. Groups that would have been deleted should have no members and should only be
|
|
* in the default zone.
|
|
*
|
|
* @throws Exception
|
|
* the exception
|
|
*/
|
|
public void testForcedUpdateWithoutDeletions() throws Exception
|
|
{
|
|
UserRegistrySynchronizer synchronizer = (UserRegistrySynchronizer) ChainingUserRegistrySynchronizerTest.context
|
|
.getBean("testUserRegistrySynchronizerPreventDeletions");
|
|
setUpTestUsersAndGroups();
|
|
this.applicationContextManager.setUserRegistries(new MockUserRegistry("Z0", new NodeDescription[]
|
|
{
|
|
newPerson("U2"), newPerson("U3"), newPerson("U4"),
|
|
}, new NodeDescription[]
|
|
{
|
|
newGroup("G1"), newGroup("G2"),
|
|
}), new MockUserRegistry("Z1", new NodeDescription[]
|
|
{
|
|
newPerson("U5"), newPerson("u6"),
|
|
}, new NodeDescription[] {}), new MockUserRegistry("Z2", new NodeDescription[]
|
|
{
|
|
newPerson("U6"),
|
|
}, new NodeDescription[] {}));
|
|
synchronizer.synchronize(true, true);
|
|
this.retryingTransactionHelper.doInTransaction(new RetryingTransactionCallback<Object>()
|
|
{
|
|
|
|
public Object execute() throws Throwable
|
|
{
|
|
// MNT-13867 fix. User U6 already exists in zone "Z0". According ChainingUserRegistrySynchronizercurrent
|
|
// implementation when syncDelete==false person that exists in a different zone with higher
|
|
// precedence will be ignored
|
|
assertExists("Z0", "U6");
|
|
assertExists("Z1", "U1");
|
|
assertExists("Z1", "U7");
|
|
assertExists("Z1", "G5");
|
|
assertExists("Z2", "G6", "U3", "U4", "G7");
|
|
assertExists("Z1", "U5");
|
|
return null;
|
|
}
|
|
}, false, true);
|
|
tearDownTestUsersAndGroups();
|
|
}
|
|
|
|
/**
|
|
* Tests a forced update of the test users and groups where some of the users change their case and some groups
|
|
* appear with different case.
|
|
*/
|
|
public void testCaseChange() throws Exception
|
|
{
|
|
setUpTestUsersAndGroups();
|
|
|
|
final Map<String, NodeRef> personNodes = new TreeMap<String, NodeRef>();
|
|
this.retryingTransactionHelper.doInTransaction(new RetryingTransactionCallback<Object>()
|
|
{
|
|
public Object execute() throws Throwable
|
|
{
|
|
// Get hold of the original person nodes so we can compare them later
|
|
personNodes.put("u1", ChainingUserRegistrySynchronizerTest.this.personService.getPerson("U1", false));
|
|
personNodes.put("u2", ChainingUserRegistrySynchronizerTest.this.personService.getPerson("U2", false));
|
|
personNodes.put("u6", ChainingUserRegistrySynchronizerTest.this.personService.getPerson("U6", false));
|
|
return null;
|
|
}
|
|
}, false, true);
|
|
|
|
this.applicationContextManager.setUserRegistries(new MockUserRegistry("Z1", new NodeDescription[]
|
|
{
|
|
newPerson("u1"), newPerson("u2"), newPerson("u6"), newPerson("U7")
|
|
}, new NodeDescription[]
|
|
{
|
|
newGroup("g1", "u6"), newGroup("g2", "u1", "G3"), newGroup("G3", "u2", "g4", "g5"), newGroup("g4"),
|
|
newGroup("g5")
|
|
}), new MockUserRegistry("Z2", new NodeDescription[]
|
|
{
|
|
newPerson("U1"), newPerson("U3"), newPerson("U4"), newPerson("U5")
|
|
}, new NodeDescription[]
|
|
{
|
|
newGroup("G2", "U1", "U3", "U4"), newGroup("G6", "U3", "U4", "G7"), newGroup("G7", "U5")
|
|
}));
|
|
this.synchronizer.synchronize(true, true);
|
|
this.retryingTransactionHelper.doInTransaction(new RetryingTransactionCallback<Object>()
|
|
{
|
|
|
|
public Object execute() throws Throwable
|
|
{
|
|
assertExists("Z1", "u1");
|
|
assertExists("Z1", "u2");
|
|
assertExists("Z1", "u6");
|
|
assertExists("Z1", "g1", "u6");
|
|
assertExists("Z1", "g2", "u1", "G3");
|
|
assertExists("Z1", "G3", "u2", "g4", "g5");
|
|
assertExists("Z1", "g4");
|
|
assertExists("Z1", "g5");
|
|
assertExists("Z2", "U3");
|
|
assertExists("Z2", "U4");
|
|
assertExists("Z2", "U5");
|
|
assertExists("Z2", "G2", "U3", "U4");
|
|
assertExists("Z2", "G6", "U3", "U4", "G7");
|
|
assertExists("Z2", "G7", "U5");
|
|
|
|
// Make sure the original people have been preserved
|
|
assertEquals(personNodes.get("u1"), ChainingUserRegistrySynchronizerTest.this.personService.getPerson(
|
|
"U1", false));
|
|
assertEquals(personNodes.get("u2"), ChainingUserRegistrySynchronizerTest.this.personService.getPerson(
|
|
"U2", false));
|
|
assertEquals(personNodes.get("u6"), ChainingUserRegistrySynchronizerTest.this.personService.getPerson(
|
|
"U6", false));
|
|
return null;
|
|
}
|
|
}, false, true);
|
|
|
|
tearDownTestUsersAndGroups();
|
|
}
|
|
|
|
public void testDifferentialUpdateWithHomeFolderCreation() throws Exception
|
|
{
|
|
setHomeFolderCreationEager(!homeFolderCreationEager);
|
|
testDifferentialUpdate();
|
|
}
|
|
|
|
public void testForcedUpdateWithHomeFolderCreation() throws Exception
|
|
{
|
|
setHomeFolderCreationEager(!homeFolderCreationEager);
|
|
testDifferentialUpdate();
|
|
}
|
|
|
|
public void testCaseChangeWithHomeFolderCreation() throws Exception
|
|
{
|
|
setHomeFolderCreationEager(!homeFolderCreationEager);
|
|
testDifferentialUpdate();
|
|
}
|
|
|
|
/**
|
|
* Tests synchronization with a zone with a larger volume of authorities.
|
|
*
|
|
* @throws Exception
|
|
* the exception
|
|
*/
|
|
public void testVolume() throws Exception
|
|
{
|
|
List<NodeDescription> persons = new ArrayList<NodeDescription>(new RandomPersonCollection(100));
|
|
List<NodeDescription> groups = new ArrayList<NodeDescription>(new RandomGroupCollection(50, persons));
|
|
this.applicationContextManager.setUserRegistries(new MockUserRegistry("Z0", persons, groups));
|
|
this.synchronizer.synchronize(true, true);
|
|
tearDownTestUsersAndGroups();
|
|
}
|
|
|
|
/**
|
|
*
|
|
*/
|
|
public void testTestSynchronize() throws Exception
|
|
{
|
|
String TEST_REGISTRY_NAME = "Z0";
|
|
String TEST_REGISTRY_NAME_BAD = "XX";
|
|
List<NodeDescription> persons = new ArrayList<NodeDescription>(new RandomPersonCollection(100));
|
|
List<NodeDescription> groups = new ArrayList<NodeDescription>(new RandomGroupCollection(50, persons));
|
|
MockUserRegistry testRegistry = new MockUserRegistry(TEST_REGISTRY_NAME, persons, groups);
|
|
this.applicationContextManager.setUserRegistries(testRegistry);
|
|
Collection<String> instances = this.applicationContextManager.getInstanceIds();
|
|
try
|
|
{
|
|
// pre-conditions
|
|
assertTrue("pre-condition test registry isActive", testRegistry.isActive());
|
|
assertTrue("pre-condition test registry in application context", instances.contains(TEST_REGISTRY_NAME));
|
|
assertFalse("pre-condition test registry isActive", instances.contains(TEST_REGISTRY_NAME_BAD));
|
|
|
|
if(synchronizer instanceof ChainingUserRegistrySynchronizer)
|
|
{
|
|
/**
|
|
* positive test using mocked user registry
|
|
*/
|
|
ChainingUserRegistrySynchronizer chainingSynchronizer = (ChainingUserRegistrySynchronizer)synchronizer;
|
|
SynchronizeDiagnostic diagnostic = chainingSynchronizer.testSynchronize(TEST_REGISTRY_NAME);
|
|
assertTrue("diagnostic is active", diagnostic.isActive());
|
|
assertNotNull("diagnostic users are null", diagnostic.getUsers());
|
|
assertNotNull("diagnostic groups are null", diagnostic.getGroups());
|
|
|
|
/**
|
|
* test with active flag set to false
|
|
*/
|
|
testRegistry.setActive(false);
|
|
diagnostic = chainingSynchronizer.testSynchronize(TEST_REGISTRY_NAME);
|
|
assertFalse("diagnostic is still active", diagnostic.isActive());
|
|
assertNotNull("diagnostic users are null", diagnostic.getUsers());
|
|
assertNotNull("diagnostic groups are null", diagnostic.getGroups());
|
|
|
|
/**
|
|
* negative test - invalid user registry
|
|
*/
|
|
try
|
|
{
|
|
chainingSynchronizer.testSynchronize(TEST_REGISTRY_NAME_BAD);
|
|
fail("bad user registry not detected");
|
|
}
|
|
catch (AuthenticationException ae)
|
|
{
|
|
// expect to go here on invalid authenticator
|
|
}
|
|
}
|
|
else
|
|
{
|
|
fail("test not run - synchroniser is not a ChainingUserRegistrySynchronizer");
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
tearDownTestUsersAndGroups();
|
|
}
|
|
}
|
|
|
|
public void testSyncStatus() throws Exception
|
|
{
|
|
Date testStart = new Date();
|
|
|
|
try
|
|
{
|
|
List<NodeDescription> persons = new ArrayList<NodeDescription>(new RandomPersonCollection(3));
|
|
List<NodeDescription> groups = new ArrayList<NodeDescription>(new RandomGroupCollection(4, persons));
|
|
MockUserRegistry testRegistry = new MockUserRegistry("Z0", persons, groups);
|
|
this.applicationContextManager.setUserRegistries(testRegistry);
|
|
this.synchronizer.synchronize(true, true);
|
|
|
|
if(this.synchronizer instanceof ChainingUserRegistrySynchronizerStatus)
|
|
{
|
|
ChainingUserRegistrySynchronizerStatus status = (ChainingUserRegistrySynchronizerStatus)this.synchronizer;
|
|
// Header Status
|
|
assertTrue("end time not updated", status.getSyncEndTime().after(testStart));
|
|
assertTrue("start time not updated", status.getSyncStartTime().after(testStart));
|
|
assertEquals("sync status is not complete", "COMPLETE", status.getSynchronizationStatus());
|
|
assertNotNull("last run on server is null", status.getLastRunOnServer());
|
|
assertNull(status.getLastErrorMessage());
|
|
|
|
// Authenticator status
|
|
assertEquals("sync status is not complete", "COMPLETE", status.getSynchronizationStatus("Z0"));
|
|
//assertNull(status.getSynchronizationLastError("Z0"));
|
|
}
|
|
else
|
|
{
|
|
fail("test not run");
|
|
}
|
|
|
|
/**
|
|
* Negative test - make an user registry throw an exception
|
|
*/
|
|
testRegistry.setThrowError(true);
|
|
testStart = new Date();
|
|
try
|
|
{
|
|
this.synchronizer.synchronize(true, true);
|
|
fail("error not thrown");
|
|
}
|
|
catch (AlfrescoRuntimeException e)
|
|
{
|
|
// expect to go here
|
|
ChainingUserRegistrySynchronizerStatus status = (ChainingUserRegistrySynchronizerStatus)this.synchronizer;
|
|
// Header Status
|
|
assertTrue("end time not updated", status.getSyncEndTime().after(testStart));
|
|
assertTrue("start time not updated", status.getSyncStartTime().after(testStart));
|
|
assertEquals("sync status is not complete", "COMPLETE_ERROR", status.getSynchronizationStatus());
|
|
assertNotNull("last run on server is null", status.getLastRunOnServer());
|
|
assertNotNull(status.getLastErrorMessage());
|
|
|
|
// Authenticator status
|
|
assertEquals("sync status is not complete", "COMPLETE_ERROR", status.getSynchronizationStatus("Z0"));
|
|
assertNotNull(status.getSynchronizationLastError("Z0"));
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
tearDownTestUsersAndGroups();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* <p>Test that upon a first sync, the missing properties at the AD level are set as 'null' on the Alfresco Person object.</p>
|
|
* <p>MNT-14026: LDAP sync fails to update attribute's value deletion.</p>
|
|
*/
|
|
public void testSyncInexistentProperty() throws Exception
|
|
{
|
|
try
|
|
{
|
|
// Execute an LDAP sync where the AD server returns the attributes of a person without a certain property, in this case the 'mail'.
|
|
executeMockedLDAPSyncWithoutActiveDirectoryEmailProp();
|
|
|
|
Map<QName, Serializable> userProperties = this.nodeService.getProperties(this.personService.getPerson("U1"));
|
|
assertTrue("User must have the email property even though it's null", userProperties.containsKey(ContentModel.PROP_EMAIL));
|
|
assertTrue("User's email must be null on first sync.", userProperties.get(ContentModel.PROP_EMAIL) == null);
|
|
}
|
|
finally
|
|
{
|
|
tearDownTestUsersAndGroups();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* <p>Test that an attribute is also removed on the Alfresco side, when it's removed at the AD level.</p>
|
|
* <p>MNT-14026: LDAP sync fails to update attribute's value deletion.</p>
|
|
*/
|
|
public void testSyncDeletedProperty() throws Exception
|
|
{
|
|
try
|
|
{
|
|
// Execute an LDAP sync where the AD server returns the attributes of a person, including the 'mail' property.
|
|
executeMockedLDAPSyncWithActiveDirectoryEmailProp();
|
|
|
|
Map<QName, Serializable> userProperties = this.nodeService.getProperties(this.personService.getPerson("U1"));
|
|
assertTrue("User's email must be not null.", userProperties.get(ContentModel.PROP_EMAIL).equals("U1@alfresco.com"));
|
|
|
|
// Execute an LDAP sync where the AD server returns the attributes
|
|
// of a person without the 'mail' property, because it was deleted.
|
|
executeMockedLDAPSyncWithoutActiveDirectoryEmailProp();
|
|
|
|
userProperties = this.nodeService.getProperties(this.personService.getPerson("U1"));
|
|
assertTrue("User must have the email property even though it's null", userProperties.containsKey(ContentModel.PROP_EMAIL));
|
|
assertTrue("User's email must be null on a 2rd sync, since the email property was removed at the AD level.", userProperties.get(ContentModel.PROP_EMAIL) == null);
|
|
}
|
|
finally
|
|
{
|
|
tearDownTestUsersAndGroups();
|
|
}
|
|
}
|
|
|
|
private void executeMockedLDAPSyncWithActiveDirectoryEmailProp() throws Exception
|
|
{
|
|
executeMockedLDAPSync(true);
|
|
}
|
|
|
|
private void executeMockedLDAPSyncWithoutActiveDirectoryEmailProp() throws Exception
|
|
{
|
|
executeMockedLDAPSync(false);
|
|
}
|
|
|
|
private void executeMockedLDAPSync(boolean withEmail) throws NamingException, Exception
|
|
{
|
|
MockUserRegistry mockUserRegistry = new MockUserRegistry("Z0", new NodeDescription[] {}, new NodeDescription[] {});
|
|
|
|
MockLDAPUserRegistry mockLDAPUserRegistry = new MockLDAPUserRegistry(mockUserRegistry);
|
|
|
|
LDAPInitialDirContextFactoryImpl mockedLdapInitialDirContextFactory = getMockedLDAPSearchResult(withEmail);
|
|
|
|
mockLDAPUserRegistry.setLDAPInitialDirContextFactory(mockedLdapInitialDirContextFactory);
|
|
mockLDAPUserRegistry.setEnableProgressEstimation(false);
|
|
mockLDAPUserRegistry.setUserIdAttributeName("sAMAccountName");
|
|
Map<String, String> personAttributeMapping = getMockedLdapAttributeMapping();
|
|
mockLDAPUserRegistry.setPersonAttributeMapping(personAttributeMapping);
|
|
mockLDAPUserRegistry.setNamespaceService(this.namespaceService);
|
|
|
|
mockLDAPUserRegistry.afterPropertiesSet();
|
|
|
|
this.applicationContextManager.setUserRegistries(mockLDAPUserRegistry);
|
|
|
|
ChainingUserRegistrySynchronizer chainingSynchronizer = (ChainingUserRegistrySynchronizer) this.synchronizer;
|
|
|
|
chainingSynchronizer.synchronize(false, false);
|
|
}
|
|
|
|
private LDAPInitialDirContextFactoryImpl getMockedLDAPSearchResult(boolean withEmail) throws NamingException
|
|
{
|
|
@SuppressWarnings("unchecked")
|
|
NamingEnumeration<SearchResult> mockedNamingEnumeration = mock(NamingEnumeration.class);
|
|
when(mockedNamingEnumeration.hasMore()).thenReturn(true).thenReturn(false);
|
|
|
|
BasicAttributes attributes = new BasicAttributes();
|
|
attributes.put(new BasicAttribute("sAMAccountName", "U1"));
|
|
attributes.put(new BasicAttribute("givenName", "U1"));
|
|
if (withEmail)
|
|
{
|
|
attributes.put(new BasicAttribute("mail", "U1@alfresco.com"));
|
|
}
|
|
SearchResult mockedSearchResult = new SearchResult("CN:U1", null, attributes);
|
|
mockedSearchResult.setNameInNamespace("CN:U1");
|
|
|
|
when(mockedNamingEnumeration.next()).thenReturn(mockedSearchResult);
|
|
|
|
InitialDirContext mockedInitialDirContext = mock(InitialDirContext.class);
|
|
when(mockedInitialDirContext.search(any(String.class), any(String.class), any(SearchControls.class))).thenReturn(mockedNamingEnumeration);
|
|
|
|
LDAPInitialDirContextFactoryImpl mockedLdapInitialDirContextFactory = mock(LDAPInitialDirContextFactoryImpl.class);
|
|
when(mockedLdapInitialDirContextFactory.getDefaultIntialDirContext(0)).thenReturn(mockedInitialDirContext);
|
|
return mockedLdapInitialDirContextFactory;
|
|
}
|
|
|
|
private Map<String, String> getMockedLdapAttributeMapping()
|
|
{
|
|
Map<String, String> personAttributeMapping = new HashMap<>();
|
|
personAttributeMapping.put("cm:userName", "sAMAccountName");
|
|
personAttributeMapping.put("cm:firstName", "givenName");
|
|
personAttributeMapping.put("cm:email", "mail");
|
|
return personAttributeMapping;
|
|
}
|
|
|
|
/**
|
|
* Tests synchronization of group associations in a zone with a larger volume of authorities.
|
|
*
|
|
* @throws Exception
|
|
* the exception
|
|
*/
|
|
public void dontTestAssocs() throws Exception
|
|
{
|
|
List<NodeDescription> groups = this.retryingTransactionHelper.doInTransaction(
|
|
new RetryingTransactionCallback<List<NodeDescription>>()
|
|
{
|
|
|
|
public List<NodeDescription> execute() throws Throwable
|
|
{
|
|
return new ArrayList<NodeDescription>(new RandomGroupCollection(1000,
|
|
ChainingUserRegistrySynchronizerTest.this.authorityService.getAllAuthoritiesInZone(
|
|
AuthorityService.ZONE_AUTH_EXT_PREFIX + "Z0", null)));
|
|
}
|
|
}, true, true);
|
|
ChainingUserRegistrySynchronizerTest.this.applicationContextManager.setUserRegistries(new MockUserRegistry(
|
|
"Z0", Collections.<NodeDescription> emptyList(), groups));
|
|
ChainingUserRegistrySynchronizerTest.this.synchronizer.synchronize(true, true);
|
|
tearDownTestUsersAndGroups();
|
|
}
|
|
|
|
/**
|
|
* Constructs a description of a test group.
|
|
*
|
|
* @param name
|
|
* the name
|
|
* @param members
|
|
* the members
|
|
* @return the node description
|
|
*/
|
|
private NodeDescription newGroup(String name, String... members)
|
|
{
|
|
return newGroupWithDisplayName(name, name, members);
|
|
}
|
|
|
|
/**
|
|
* Constructs a description of a test group with a display name.
|
|
*
|
|
* @param name
|
|
* the name
|
|
* @param displayName
|
|
* the display name
|
|
* @param members
|
|
* the members
|
|
* @return the node description
|
|
*/
|
|
private NodeDescription newGroupWithDisplayName(String name, String displayName, String... members)
|
|
{
|
|
String longName = longName(name);
|
|
NodeDescription group = new NodeDescription(longName);
|
|
PropertyMap properties = group.getProperties();
|
|
properties.put(ContentModel.PROP_AUTHORITY_NAME, longName);
|
|
properties.put(ContentModel.PROP_AUTHORITY_DISPLAY_NAME, displayName);
|
|
if (members.length > 0)
|
|
{
|
|
Set<String> assocs = group.getChildAssociations();
|
|
for (String member : members)
|
|
{
|
|
assocs.add(longName(member));
|
|
}
|
|
}
|
|
group.setLastModified(new Date());
|
|
return group;
|
|
}
|
|
|
|
/**
|
|
* Constructs a description of a test person with default email (userName@alfresco.com)
|
|
*
|
|
* @param userName
|
|
* the user name
|
|
* @return the node description
|
|
*/
|
|
private NodeDescription newPerson(String userName)
|
|
{
|
|
return newPerson(userName, userName + "@alfresco.com");
|
|
}
|
|
|
|
/**
|
|
* Constructs a description of a test person with a given email.
|
|
*
|
|
* @param userName
|
|
* the user name
|
|
* @param email
|
|
* the email
|
|
* @return the node description
|
|
*/
|
|
private NodeDescription newPerson(String userName, String email)
|
|
{
|
|
NodeDescription person = new NodeDescription(userName);
|
|
PropertyMap properties = person.getProperties();
|
|
properties.put(ContentModel.PROP_USERNAME, userName);
|
|
properties.put(ContentModel.PROP_FIRSTNAME, userName + "F");
|
|
properties.put(ContentModel.PROP_LASTNAME, userName + "L");
|
|
properties.put(ContentModel.PROP_EMAIL, email);
|
|
person.setLastModified(new Date());
|
|
return person;
|
|
}
|
|
|
|
private NodeDescription newPersonWithUserAccountStatusProperty(String userName, String userAccountPropertyValue)
|
|
{
|
|
NodeDescription person = newPerson(userName, userName + "@somedomain.com");
|
|
|
|
person.getProperties().put(QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "userAccountStatusProperty"), userAccountPropertyValue);
|
|
person.setLastModified(new Date());
|
|
|
|
return person;
|
|
}
|
|
|
|
/**
|
|
* Perform all the necessary assertions to ensure that an authority and its members exist in the correct zone.
|
|
*
|
|
* @param zone
|
|
* the zone
|
|
* @param name
|
|
* the name
|
|
* @param members
|
|
* the members
|
|
*/
|
|
private void assertExists(String zone, String name, String... members)
|
|
{
|
|
String longName = longName(name);
|
|
// Check authority exists
|
|
assertTrue(this.authorityService.authorityExists(longName));
|
|
|
|
// Check in correct zone
|
|
if (zone == null)
|
|
{
|
|
Set<String> zones = new TreeSet<String>();
|
|
zones.add(AuthorityService.ZONE_APP_DEFAULT);
|
|
zones.add(AuthorityService.ZONE_AUTH_ALFRESCO);
|
|
assertEquals(zones, this.authorityService.getAuthorityZones(longName));
|
|
}
|
|
else
|
|
{
|
|
assertTrue(this.authorityService.getAuthorityZones(longName).contains(
|
|
AuthorityService.ZONE_AUTH_EXT_PREFIX + zone));
|
|
}
|
|
|
|
if (AuthorityType.getAuthorityType(longName).equals(AuthorityType.GROUP))
|
|
{
|
|
// Check groups have expected members
|
|
Set<String> memberSet = new HashSet<String>(members.length * 2);
|
|
for (String member : members)
|
|
{
|
|
memberSet.add(longName(member));
|
|
}
|
|
assertEquals(memberSet, this.authorityService.getContainedAuthorities(null, longName, true));
|
|
}
|
|
else
|
|
{
|
|
// Check users exist as persons
|
|
assertTrue(this.personService.personExists(name));
|
|
|
|
// Check case matches
|
|
assertEquals(this.personService.getUserIdentifier(name), name);
|
|
|
|
// Check the person exist.
|
|
NodeRef person = personService.getPerson(name, false);
|
|
assertNotNull("Person for "+name+" should exist", person);
|
|
|
|
// Check the home folder exists or not.
|
|
NodeRef homeFolder = DefaultTypeConverter.INSTANCE.convert(NodeRef.class, nodeService.getProperty(person, ContentModel.PROP_HOMEFOLDER));
|
|
if (homeFolderCreationEager)
|
|
{
|
|
assertNotNull("Home folder for "+name+" should exist", homeFolder);
|
|
}
|
|
else
|
|
{
|
|
assertNull("Home folder for "+name+" should not exist", homeFolder);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Perform all the necessary assertions to ensure that an authority does not exist.
|
|
*
|
|
* @param name
|
|
* the name
|
|
*/
|
|
private void assertNotExists(String name)
|
|
{
|
|
String longName = longName(name);
|
|
// Check authority does not exist
|
|
assertFalse(this.authorityService.authorityExists(longName));
|
|
|
|
// Check there is no zone
|
|
assertNull(this.authorityService.getAuthorityZones(longName));
|
|
if (!AuthorityType.getAuthorityType(longName).equals(AuthorityType.GROUP))
|
|
{
|
|
// Check person does not exist
|
|
assertFalse(this.personService.personExists(name));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Asserts that a person's email has the expected value.
|
|
*
|
|
* @param personName
|
|
* the person name
|
|
* @param email
|
|
* the email
|
|
*/
|
|
private void assertEmailEquals(String personName, String email)
|
|
{
|
|
NodeRef personRef = this.personService.getPerson(personName, false);
|
|
assertEquals(email, this.nodeService.getProperty(personRef, ContentModel.PROP_EMAIL));
|
|
}
|
|
|
|
/**
|
|
* Asserts that a group's display name has the expected value.
|
|
*
|
|
* @param name
|
|
* the person name
|
|
* @param displayName
|
|
* the display name
|
|
*/
|
|
private void assertGroupDisplayNameEquals(String name, String displayName)
|
|
{
|
|
assertEquals(displayName, this.authorityService.getAuthorityDisplayName(longName(name)));
|
|
}
|
|
|
|
/**
|
|
* Converts the given short name to a full authority name, assuming that those short names beginning with 'G'
|
|
* correspond to groups and all others correspond to users.
|
|
*
|
|
* @param shortName
|
|
* the short name
|
|
* @return the full authority name
|
|
*/
|
|
private String longName(String shortName)
|
|
{
|
|
return this.authorityService.getName(shortName.toLowerCase().startsWith("g") ? AuthorityType.GROUP
|
|
: AuthorityType.USER, shortName);
|
|
}
|
|
|
|
/**
|
|
* A Test {@link ApplicationListener} that checks SyncEndTime before the SynchronizeEndEvent and SynchronizeDirectoryEndEvent events.
|
|
*/
|
|
public static class TestSynchronizeEventListener implements ApplicationListener<ApplicationEvent>
|
|
{
|
|
private ChainingUserRegistrySynchronizer synchronizer;
|
|
|
|
public void setSynchronizer(ChainingUserRegistrySynchronizer synchronizer)
|
|
{
|
|
this.synchronizer = synchronizer;
|
|
}
|
|
|
|
@Override
|
|
public void onApplicationEvent(ApplicationEvent event)
|
|
{
|
|
if (SynchronizeEndEvent.class.isAssignableFrom(event.getClass())
|
|
|| SynchronizeDirectoryEndEvent.class.isAssignableFrom(event.getClass()))
|
|
{
|
|
assertEquals(null, this.synchronizer.getSyncEndTime());
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
public static interface IMockUserRegistry extends UserRegistry, ActivateableBean
|
|
{
|
|
/**
|
|
* Gets the zone id.
|
|
*
|
|
* @return the zoneId
|
|
*/
|
|
String getZoneId();
|
|
|
|
/**
|
|
* Modifies the state to match the arguments. Compares new with old and
|
|
* records new modification dates only for changes.
|
|
*
|
|
* @param persons
|
|
* the persons
|
|
* @param groups
|
|
* the groups
|
|
*/
|
|
void updateState(Collection<NodeDescription> persons, Collection<NodeDescription> groups);
|
|
}
|
|
|
|
/**
|
|
* A Mock {@link UserRegistry} that returns a fixed set of users and groups.
|
|
*/
|
|
public static class MockUserRegistry implements IMockUserRegistry
|
|
{
|
|
private boolean isActive = true;
|
|
|
|
private boolean throwError = false;
|
|
|
|
/** The zone id. */
|
|
private String zoneId;
|
|
|
|
/** The persons. */
|
|
private Collection<NodeDescription> persons;
|
|
|
|
/** The groups. */
|
|
private Collection<NodeDescription> groups;
|
|
|
|
/**
|
|
* Instantiates a new mock user registry.
|
|
*
|
|
* @param zoneId
|
|
* the zone id
|
|
* @param persons
|
|
* the persons
|
|
* @param groups
|
|
* the groups
|
|
*/
|
|
public MockUserRegistry(String zoneId, Collection<NodeDescription> persons, Collection<NodeDescription> groups)
|
|
{
|
|
this.zoneId = zoneId;
|
|
this.persons = persons;
|
|
this.groups = groups;
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
public void updateState(Collection<NodeDescription> persons, Collection<NodeDescription> groups)
|
|
{
|
|
List<NodeDescription> newPersons = new ArrayList<NodeDescription>(this.persons);
|
|
mergeNodeDescriptions(newPersons, persons, ContentModel.PROP_USERNAME, false);
|
|
this.persons = newPersons;
|
|
|
|
List<NodeDescription> newGroups = new ArrayList<NodeDescription>(this.groups);
|
|
mergeNodeDescriptions(newGroups, groups, ContentModel.PROP_AUTHORITY_NAME, true);
|
|
this.groups = newGroups;
|
|
}
|
|
|
|
/**
|
|
* Merges together an old and new list of node descriptions. Retains the old node with its old modification date
|
|
* if it is the same in the new list, otherwises uses the node from the new list.
|
|
*
|
|
* @param oldNodes
|
|
* the old node list
|
|
* @param newNodes
|
|
* the new node list
|
|
* @param idProp
|
|
* the name of the ID property
|
|
* @param caseSensitive
|
|
* are IDs case sensitive?
|
|
*/
|
|
private void mergeNodeDescriptions(List<NodeDescription> oldNodes, Collection<NodeDescription> newNodes,
|
|
QName idProp, boolean caseSensitive)
|
|
{
|
|
Map<String, NodeDescription> nodeMap = new LinkedHashMap<String, NodeDescription>(newNodes.size() * 2);
|
|
for (NodeDescription node : newNodes)
|
|
{
|
|
String id = (String) node.getProperties().get(idProp);
|
|
if (!caseSensitive)
|
|
{
|
|
id = id.toLowerCase();
|
|
}
|
|
nodeMap.put(id, node);
|
|
}
|
|
for (int i = 0; i < oldNodes.size(); i++)
|
|
{
|
|
NodeDescription oldNode = oldNodes.get(i);
|
|
String id = (String) oldNode.getProperties().get(idProp);
|
|
if (!caseSensitive)
|
|
{
|
|
id = id.toLowerCase();
|
|
}
|
|
NodeDescription newNode = nodeMap.remove(id);
|
|
if (newNode == null)
|
|
{
|
|
oldNodes.remove(i);
|
|
i--;
|
|
}
|
|
else if (!oldNode.getProperties().equals(newNode.getProperties())
|
|
|| !oldNode.getChildAssociations().equals(newNode.getChildAssociations()))
|
|
{
|
|
oldNodes.set(i, newNode);
|
|
}
|
|
}
|
|
oldNodes.addAll(nodeMap.values());
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param throwError boolean
|
|
*/
|
|
public void setThrowError(boolean throwError)
|
|
{
|
|
this.throwError = throwError;
|
|
}
|
|
|
|
/**
|
|
* Instantiates a new mock user registry.
|
|
*
|
|
* @param zoneId
|
|
* the zone id
|
|
* @param persons
|
|
* the persons
|
|
* @param groups
|
|
* the groups
|
|
*/
|
|
public MockUserRegistry(String zoneId, NodeDescription[] persons, NodeDescription[] groups)
|
|
{
|
|
this(zoneId, Arrays.asList(persons), Arrays.asList(groups));
|
|
}
|
|
|
|
@Override
|
|
public String getZoneId()
|
|
{
|
|
return this.zoneId;
|
|
}
|
|
|
|
/*
|
|
* (non-Javadoc)
|
|
* @see org.alfresco.repo.security.sync.UserRegistry#getGroupNames()
|
|
*/
|
|
public Collection<String> getGroupNames()
|
|
{
|
|
List<String> groupNames = new LinkedList<String>();
|
|
for (NodeDescription group : this.groups)
|
|
{
|
|
groupNames.add((String) group.getProperties().get(ContentModel.PROP_AUTHORITY_NAME));
|
|
}
|
|
return groupNames;
|
|
}
|
|
|
|
/*
|
|
* (non-Javadoc)
|
|
* @see org.alfresco.repo.security.sync.UserRegistry#getPersonNames()
|
|
*/
|
|
public Collection<String> getPersonNames()
|
|
{
|
|
List<String> personNames = new LinkedList<String>();
|
|
for (NodeDescription person : this.persons)
|
|
{
|
|
personNames.add((String) person.getProperties().get(ContentModel.PROP_USERNAME));
|
|
}
|
|
return personNames;
|
|
}
|
|
|
|
/*
|
|
* (non-Javadoc)
|
|
* @see org.alfresco.repo.security.sync.UserRegistry#getGroups(java.util.Date)
|
|
*/
|
|
public Collection<NodeDescription> getGroups(Date modifiedSince)
|
|
{
|
|
if(throwError)
|
|
{
|
|
throw new AlfrescoRuntimeException("test error");
|
|
}
|
|
return filterNodeDescriptions(this.groups, modifiedSince);
|
|
}
|
|
|
|
/**
|
|
* Filters the given list of node descriptions, retaining only those with a modification date greater than the
|
|
* given date.
|
|
*
|
|
* @param nodes
|
|
* the list of nodes
|
|
* @param modifiedSince
|
|
* the modified date
|
|
* @return the filter list of nodes
|
|
*/
|
|
private Collection<NodeDescription> filterNodeDescriptions(Collection<NodeDescription> nodes, Date modifiedSince)
|
|
{
|
|
if (modifiedSince == null)
|
|
{
|
|
return nodes;
|
|
}
|
|
List<NodeDescription> filteredNodes = new LinkedList<NodeDescription>();
|
|
for (NodeDescription node : nodes)
|
|
{
|
|
Date modified = node.getLastModified();
|
|
if (modifiedSince.compareTo(modified) < 0)
|
|
{
|
|
filteredNodes.add(node);
|
|
}
|
|
}
|
|
return filteredNodes;
|
|
}
|
|
|
|
/*
|
|
* (non-Javadoc)
|
|
* @see org.alfresco.repo.security.sync.UserRegistry#getPersons(java.util.Date)
|
|
*/
|
|
public Collection<NodeDescription> getPersons(Date modifiedSince)
|
|
{
|
|
return filterNodeDescriptions(this.persons, modifiedSince);
|
|
}
|
|
|
|
/*
|
|
* (non-Javadoc)
|
|
* @see org.alfresco.repo.security.sync.UserRegistry#getPersonMappedProperties()
|
|
*/
|
|
public Set<QName> getPersonMappedProperties()
|
|
{
|
|
return new HashSet<QName>(Arrays.asList(new QName[]
|
|
{
|
|
ContentModel.PROP_USERNAME, ContentModel.PROP_FIRSTNAME, ContentModel.PROP_LASTNAME,
|
|
ContentModel.PROP_EMAIL, ContentModel.PROP_ORGID, ContentModel.PROP_ORGANIZATION,
|
|
ContentModel.PROP_HOME_FOLDER_PROVIDER
|
|
}));
|
|
}
|
|
|
|
@Override
|
|
public boolean isActive()
|
|
{
|
|
return isActive;
|
|
}
|
|
|
|
public void setActive(boolean isActive)
|
|
{
|
|
this.isActive = isActive;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* An {@link ChildApplicationContextManager} for a chain of application contexts containing mock user registries.
|
|
*/
|
|
public static class MockApplicationContextManager implements ChildApplicationContextManager
|
|
{
|
|
|
|
/** The contexts. */
|
|
private Map<String, ApplicationContext> contexts = Collections.emptyMap();
|
|
|
|
/**
|
|
* Sets the user registries.
|
|
*
|
|
* @param registries
|
|
* the new user registries
|
|
*/
|
|
public void setUserRegistries(IMockUserRegistry... registries)
|
|
{
|
|
this.contexts = new LinkedHashMap<String, ApplicationContext>(registries.length * 2);
|
|
for (IMockUserRegistry registry : registries)
|
|
{
|
|
StaticApplicationContext context = new StaticApplicationContext();
|
|
context.getDefaultListableBeanFactory().registerSingleton("userRegistry", registry);
|
|
this.contexts.put(registry.getZoneId(), context);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Removes the application context for the given zone ID (simulating a change in the authentication chain).
|
|
*
|
|
* @param zoneId
|
|
* the zone id
|
|
*/
|
|
public void removeZone(String zoneId)
|
|
{
|
|
this.contexts.remove(zoneId);
|
|
}
|
|
|
|
/**
|
|
* Updates the state of the given zone ID, oopying in new modification dates only where changes have been made.
|
|
*
|
|
* @param zoneId
|
|
* the zone id
|
|
* @param persons
|
|
* the new list of persons
|
|
* @param groups
|
|
* the new list of groups
|
|
*/
|
|
public void updateZone(String zoneId, NodeDescription[] persons, NodeDescription[] groups)
|
|
{
|
|
ApplicationContext context = this.contexts.get(zoneId);
|
|
IMockUserRegistry registry = (IMockUserRegistry) context.getBean("userRegistry");
|
|
registry.updateState(Arrays.asList(persons), Arrays.asList(groups));
|
|
}
|
|
|
|
/*
|
|
* (non-Javadoc)
|
|
* @see
|
|
* org.alfresco.repo.management.subsystems.ChildApplicationContextManager#getApplicationContext(java.lang.String
|
|
* )
|
|
*/
|
|
public ApplicationContext getApplicationContext(String id)
|
|
{
|
|
return this.contexts.get(id);
|
|
}
|
|
|
|
/*
|
|
* (non-Javadoc)
|
|
* @see org.alfresco.repo.management.subsystems.ChildApplicationContextManager#getInstanceIds()
|
|
*/
|
|
public Collection<String> getInstanceIds()
|
|
{
|
|
return this.contexts.keySet();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* A collection whose iterator returns randomly generated persons.
|
|
*/
|
|
public class RandomPersonCollection extends AbstractCollection<NodeDescription>
|
|
{
|
|
|
|
/** The collection size. */
|
|
private final int size;
|
|
|
|
/**
|
|
* The Constructor.
|
|
*
|
|
* @param size
|
|
* the collection size
|
|
*/
|
|
public RandomPersonCollection(int size)
|
|
{
|
|
this.size = size;
|
|
}
|
|
|
|
/*
|
|
* (non-Javadoc)
|
|
* @see java.util.AbstractCollection#iterator()
|
|
*/
|
|
@Override
|
|
public Iterator<NodeDescription> iterator()
|
|
{
|
|
return new Iterator<NodeDescription>()
|
|
{
|
|
|
|
private int pos;
|
|
|
|
public boolean hasNext()
|
|
{
|
|
return this.pos < RandomPersonCollection.this.size;
|
|
}
|
|
|
|
public NodeDescription next()
|
|
{
|
|
this.pos++;
|
|
return newPerson("U" + GUID.generate());
|
|
}
|
|
|
|
public void remove()
|
|
{
|
|
throw new UnsupportedOperationException();
|
|
}
|
|
};
|
|
|
|
}
|
|
|
|
/*
|
|
* (non-Javadoc)
|
|
* @see java.util.AbstractCollection#size()
|
|
*/
|
|
@Override
|
|
public int size()
|
|
{
|
|
return this.size;
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
* A collection whose iterator returns randomly generated groups with random associations to a given list of
|
|
* persons.
|
|
*/
|
|
public class RandomGroupCollection extends AbstractCollection<NodeDescription>
|
|
{
|
|
/** Use a fixed seed to give this class deterministic behaviour */
|
|
private Random generator = new Random(1628876500L);
|
|
|
|
/** The collection size. */
|
|
private final int size;
|
|
|
|
/** The authorities. */
|
|
private final List<String> authorities;
|
|
|
|
/**
|
|
* The Constructor.
|
|
*
|
|
* @param size
|
|
* the collection size
|
|
* @param authorities
|
|
* the authorities
|
|
*/
|
|
public RandomGroupCollection(int size, Set<String> authorities)
|
|
{
|
|
this.size = size;
|
|
this.authorities = new ArrayList<String>(authorities);
|
|
}
|
|
|
|
/**
|
|
* The Constructor.
|
|
*
|
|
* @param size
|
|
* the collection size
|
|
* @param persons
|
|
* the persons
|
|
*/
|
|
public RandomGroupCollection(int size, Collection<NodeDescription> persons)
|
|
{
|
|
this.size = size;
|
|
this.authorities = new ArrayList<String>(persons.size());
|
|
for (NodeDescription nodeDescription : persons)
|
|
{
|
|
this.authorities.add((String) nodeDescription.getProperties().get(ContentModel.PROP_USERNAME));
|
|
}
|
|
}
|
|
|
|
/*
|
|
* (non-Javadoc)
|
|
* @see java.util.AbstractCollection#iterator()
|
|
*/
|
|
@Override
|
|
public Iterator<NodeDescription> iterator()
|
|
{
|
|
return new Iterator<NodeDescription>()
|
|
{
|
|
|
|
private int pos;
|
|
|
|
public boolean hasNext()
|
|
{
|
|
return this.pos < RandomGroupCollection.this.size;
|
|
}
|
|
|
|
public NodeDescription next()
|
|
{
|
|
this.pos++;
|
|
|
|
// Just for fun, make the last group one that includes ALL authorities!
|
|
String[] authorityNames = new String[this.pos == RandomGroupCollection.this.size ? RandomGroupCollection.this.size : 17];
|
|
for (int i = 0; i < authorityNames.length; i++)
|
|
{
|
|
// Choose an authority at random from the list of known authorities
|
|
int index = this.pos == RandomGroupCollection.this.size ? i : RandomGroupCollection.this.generator.nextInt(RandomGroupCollection.this.authorities
|
|
.size());
|
|
authorityNames[i] = ChainingUserRegistrySynchronizerTest.this.authorityService
|
|
.getShortName((String) RandomGroupCollection.this.authorities.get(index));
|
|
}
|
|
NodeDescription group = newGroup("G" + GUID.generate(), authorityNames);
|
|
// Make this group a candidate for adding to other groups
|
|
RandomGroupCollection.this.authorities.add((String) group.getProperties().get(
|
|
ContentModel.PROP_AUTHORITY_NAME));
|
|
return group;
|
|
}
|
|
|
|
public void remove()
|
|
{
|
|
throw new UnsupportedOperationException();
|
|
}
|
|
};
|
|
|
|
}
|
|
|
|
/*
|
|
* (non-Javadoc)
|
|
* @see java.util.AbstractCollection#size()
|
|
*/
|
|
@Override
|
|
public int size()
|
|
{
|
|
return this.size;
|
|
}
|
|
|
|
}
|
|
|
|
}
|