Merged 5.2.N (5.2.1) to HEAD (5.2)

125792 rmunteanu: Merged 5.1.N (5.1.2) to 5.2.N (5.2.1)
      125621 rmunteanu: Merged 5.0.N (5.0.4) to 5.1.N (5.1.2)
         125577 abalmus: MNT-15038 : Unexpected behavior when disabling Active Directory user (New feature to sync userAccountControl)
            - Implemented new requirements and tests


git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@127813 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
This commit is contained in:
Alan Davis
2016-06-04 11:00:24 +00:00
parent bd2cf2c35d
commit b4576ce3ca
15 changed files with 491 additions and 22 deletions

View File

@@ -93,6 +93,9 @@
<property name="sysAdminParams"> <property name="sysAdminParams">
<ref bean="sysAdminParams" /> <ref bean="sysAdminParams" />
</property> </property>
<property name="personService">
<ref bean="PersonService" />
</property>
</bean> </bean>
<!-- <!--
@@ -365,6 +368,13 @@
<value>${ldap.synchronization.timestampFormat}</value> <value>${ldap.synchronization.timestampFormat}</value>
</property> </property>
<!--
The user account status property interpreter. Unfortunately, this varies between directory servers.
-->
<property name="userAccountStatusInterpreter">
<ref bean="${ldap.synchronization.userAccountStatusInterpreter}" />
</property>
<!-- <!--
An attribute that is a unique identifier for each group found. An attribute that is a unique identifier for each group found.
This is also the name of the group with the current group implementation. This is also the name of the group with the current group implementation.
@@ -451,6 +461,12 @@
<entry key="cm:homeFolderProvider"> <entry key="cm:homeFolderProvider">
<null/> <null/>
</entry> </entry>
<entry key="cm:userAccountStatusProperty">
<!-- Oracle / Red Hat / 389 DS: "nsAccountLock"-->
<!-- OpenLDAP: "pwdAccountLockedTime" -->
<!-- Active Directory: "userAccountControl" -->
<value>${ldap.synchronization.userAccountStatusProperty}</value>
</entry>
</map> </map>
</property> </property>
<!-- Set a default home folder provider --> <!-- Set a default home folder provider -->

View File

@@ -7,4 +7,8 @@
defaults defaults
--> -->
<import resource="../common-ldap-context.xml" /> <import resource="../common-ldap-context.xml" />
<!-- LDAP-AD User Account Status Interpreter -->
<bean id="ldapadUserAccountStatusInterpreter" class="org.alfresco.repo.security.sync.ldap_ad.LDAPADUserAccountStatusInterpreter">
</bean>
</beans> </beans>

View File

@@ -165,4 +165,10 @@ ldap.pooling.com.sun.jndi.ldap.connect.pool.protocol=plain
ldap.pooling.com.sun.jndi.ldap.connect.pool.timeout= ldap.pooling.com.sun.jndi.ldap.connect.pool.timeout=
# The string representation of an integer that represents the number of milliseconds to specify how long to wait for a pooled connection. If you omit this property, the application will wait indefinitely. # The string representation of an integer that represents the number of milliseconds to specify how long to wait for a pooled connection. If you omit this property, the application will wait indefinitely.
ldap.pooling.com.sun.jndi.ldap.connect.timeout= ldap.pooling.com.sun.jndi.ldap.connect.timeout=
# LDAP-AD property name for user enabled/disabled status
ldap.synchronization.userAccountStatusProperty=userAccountControl
# The Account Status Interpreter bean name
ldap.synchronization.userAccountStatusInterpreter=ldapadUserAccountStatusInterpreter

View File

@@ -7,4 +7,14 @@
defaults defaults
--> -->
<import resource="../common-ldap-context.xml" /> <import resource="../common-ldap-context.xml" />
<!-- LDAP User Account Status Interpreter -->
<bean id="ldapUserAccountStatusInterpreter" class="org.alfresco.repo.security.sync.ldap.LDAPUserAccountStatusInterpreter">
<property name="disabledAccountPropertyValue">
<value>${ldap.synchronization.disabledAccountPropertyValue}</value>
</property>
<property name="acceptNullArgument">
<value>${ldap.synchronization.disabledAccountPropertyValueCanBeNull}</value>
</property>
</bean>
</beans> </beans>

View File

@@ -172,4 +172,20 @@ ldap.pooling.com.sun.jndi.ldap.connect.pool.timeout=
# The string representation of an integer that represents the number of milliseconds to specify how long to wait for a pooled connection. # The string representation of an integer that represents the number of milliseconds to specify how long to wait for a pooled connection.
# Empty value means the application will wait indefinitely. # Empty value means the application will wait indefinitely.
ldap.pooling.com.sun.jndi.ldap.connect.timeout= ldap.pooling.com.sun.jndi.ldap.connect.timeout=
# Enabled/disabled status - there is no standard way to check for this;
# "nsAccountLock" is used by most NDS derived directory systems (Oracle / Red Hat / 389 DS);
# For OpenLDAP you may want to specify "pwdAccountLockedTime" instead
ldap.synchronization.userAccountStatusProperty=nsAccountLock
# Expected value for disabled account;
# For NDS directory servers: nsAccountLock=true
# For OpenLDAP: pwdAccountLockedTime=000001010000Z
ldap.synchronization.disabledAccountPropertyValue=true
# Some directory servers may not send a status value at all if account is enabled
ldap.synchronization.disabledAccountPropertyValueCanBeNull=true
# The Account Status Interpreter bean name
ldap.synchronization.userAccountStatusInterpreter=ldapUserAccountStatusInterpreter

View File

@@ -84,6 +84,12 @@
<value>${synchronization.syncDelete}</value> <value>${synchronization.syncDelete}</value>
</property> </property>
<property name="nameChecker" ref="nameChecker" /> <property name="nameChecker" ref="nameChecker" />
<property name="externalUserControl">
<value>${synchronization.externalUserControl}</value>
</property>
<property name="externalUserControlSubsystemName">
<value>${synchronization.externalUserControlSubsystemName}</value>
</property>
</bean> </bean>

View File

@@ -33,4 +33,10 @@ synchronization.workerThreads=1
synchronization.allowDeletions=true synchronization.allowDeletions=true
# For large LDAP directories the delete query is expensive and time consuming, needing to read the entire LDAP directory. # For large LDAP directories the delete query is expensive and time consuming, needing to read the entire LDAP directory.
synchronization.syncDelete=true synchronization.syncDelete=true
# external setting (LDAP systems) - whether users can be enabled; if false then users have to be explicitly disabled in Alfresco
synchronization.externalUserControl=false
# Subsystem that will handle the external user control
synchronization.externalUserControlSubsystemName=

View File

@@ -31,6 +31,7 @@ import java.util.Set;
import org.alfresco.repo.management.subsystems.ActivateableBean; import org.alfresco.repo.management.subsystems.ActivateableBean;
import org.alfresco.repo.security.authentication.AuthenticationComponent.UserNameValidationMode; import org.alfresco.repo.security.authentication.AuthenticationComponent.UserNameValidationMode;
import org.alfresco.repo.tenant.TenantContextHolder; import org.alfresco.repo.tenant.TenantContextHolder;
import org.alfresco.service.cmr.security.PersonService;
import org.alfresco.util.Pair; import org.alfresco.util.Pair;
public class AuthenticationServiceImpl extends AbstractAuthenticationService implements ActivateableBean public class AuthenticationServiceImpl extends AbstractAuthenticationService implements ActivateableBean
@@ -42,7 +43,14 @@ public class AuthenticationServiceImpl extends AbstractAuthenticationService imp
private boolean allowsUserCreation = true; private boolean allowsUserCreation = true;
private boolean allowsUserDeletion = true; private boolean allowsUserDeletion = true;
private boolean allowsUserPasswordChange = true; private boolean allowsUserPasswordChange = true;
private PersonService personService;
public void setPersonService(PersonService personService)
{
this.personService = personService;
}
public AuthenticationServiceImpl() public AuthenticationServiceImpl()
{ {
super(); super();
@@ -336,6 +344,11 @@ public class AuthenticationServiceImpl extends AbstractAuthenticationService imp
*/ */
public boolean getAuthenticationEnabled(String userName) throws AuthenticationException public boolean getAuthenticationEnabled(String userName) throws AuthenticationException
{ {
if (personService.personExists(userName))
{
return personService.isEnabled(userName);
}
return true; return true;
} }
} }

View File

@@ -848,6 +848,9 @@ public class PersonServiceImpl extends TransactionListenerAdapter implements Per
properties.put(ContentModel.PROP_USERNAME, realUserName); properties.put(ContentModel.PROP_USERNAME, realUserName);
} }
} }
checkIfPersonShouldBeDisabledAndSetAspect(personNode, properties);
Map<QName, Serializable> update = nodeService.getProperties(personNode); Map<QName, Serializable> update = nodeService.getProperties(personNode);
update.putAll(properties); update.putAll(properties);
@@ -987,6 +990,8 @@ public class PersonServiceImpl extends TransactionListenerAdapter implements Per
beforeCreateNodeValidationBehaviour.enable(); beforeCreateNodeValidationBehaviour.enable();
} }
checkIfPersonShouldBeDisabledAndSetAspect(personRef, properties);
if (zones != null) if (zones != null)
{ {
for (String zone : zones) for (String zone : zones)
@@ -1004,6 +1009,26 @@ public class PersonServiceImpl extends TransactionListenerAdapter implements Per
return personRef; return personRef;
} }
private void checkIfPersonShouldBeDisabledAndSetAspect(NodeRef person, Map<QName, Serializable> properties)
{
if (properties.get(ContentModel.PROP_ENABLED) != null)
{
boolean isEnabled = Boolean.parseBoolean(properties.get(ContentModel.PROP_ENABLED).toString());
if (isEnabled)
{
if (nodeService.hasAspect(person, ContentModel.ASPECT_PERSON_DISABLED))
{
nodeService.removeAspect(person, ContentModel.ASPECT_PERSON_DISABLED);
}
}
else
{
nodeService.addAspect(person, ContentModel.ASPECT_PERSON_DISABLED, null);
}
}
}
/** /**
* {@inheritDoc} * {@inheritDoc}
*/ */

View File

@@ -39,6 +39,7 @@ import org.alfresco.repo.security.authentication.AuthenticationUtil;
import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork;
import org.alfresco.repo.security.authentication.AuthenticatorDeletedEvent; import org.alfresco.repo.security.authentication.AuthenticatorDeletedEvent;
import org.alfresco.repo.security.authority.UnknownAuthorityException; import org.alfresco.repo.security.authority.UnknownAuthorityException;
import org.alfresco.repo.security.sync.ldap.LDAPUserRegistry;
import org.alfresco.repo.transaction.AlfrescoTransactionSupport; import org.alfresco.repo.transaction.AlfrescoTransactionSupport;
import org.alfresco.repo.transaction.AlfrescoTransactionSupport.TxnReadState; import org.alfresco.repo.transaction.AlfrescoTransactionSupport.TxnReadState;
import org.alfresco.repo.transaction.RetryingTransactionHelper; import org.alfresco.repo.transaction.RetryingTransactionHelper;
@@ -65,6 +66,7 @@ import org.springframework.extensions.surf.util.AbstractLifecycleBean;
import org.springframework.extensions.surf.util.I18NUtil; import org.springframework.extensions.surf.util.I18NUtil;
import javax.management.*; import javax.management.*;
import java.io.IOException; import java.io.IOException;
import java.io.Serializable; import java.io.Serializable;
import java.io.UnsupportedEncodingException; import java.io.UnsupportedEncodingException;
@@ -195,6 +197,10 @@ public class ChainingUserRegistrySynchronizer extends AbstractLifecycleBean
private NameChecker nameChecker; private NameChecker nameChecker;
private SysAdminParams sysAdminParams; private SysAdminParams sysAdminParams;
private String externalUserControl = "";
private String externalUserControlSubsystemName = "";
public void init() public void init()
{ {
@@ -208,6 +214,16 @@ public class ChainingUserRegistrySynchronizer extends AbstractLifecycleBean
PropertyCheck.mandatory(this, "sysAdminParams", sysAdminParams); PropertyCheck.mandatory(this, "sysAdminParams", sysAdminParams);
} }
public void setExternalUserControl(String externalUserControl)
{
this.externalUserControl = externalUserControl;
}
public void setExternalUserControlSubsystemName(String externalUserControlSubsystemName)
{
this.externalUserControlSubsystemName = externalUserControlSubsystemName;
}
/** /**
* Sets name checker * Sets name checker
*/ */
@@ -1765,6 +1781,9 @@ public class ChainingUserRegistrySynchronizer extends AbstractLifecycleBean
this.applicationEventPublisher, this.applicationEventPublisher,
ChainingUserRegistrySynchronizer.logger, ChainingUserRegistrySynchronizer.logger,
this.loggingInterval); this.loggingInterval);
final UserRegistry userRegistryFinalRef = userRegistry;
class PersonWorker extends BaseBatchProcessWorker<NodeDescription> class PersonWorker extends BaseBatchProcessWorker<NodeDescription>
{ {
private long latestTime; private long latestTime;
@@ -1790,6 +1809,36 @@ public class ChainingUserRegistrySynchronizer extends AbstractLifecycleBean
HashMap<QName, Serializable> personProperties = new HashMap<QName, Serializable>(person.getProperties()); HashMap<QName, Serializable> personProperties = new HashMap<QName, Serializable>(person.getProperties());
String personName = personProperties.get(ContentModel.PROP_USERNAME).toString().trim(); String personName = personProperties.get(ContentModel.PROP_USERNAME).toString().trim();
personProperties.put(ContentModel.PROP_USERNAME, personName); personProperties.put(ContentModel.PROP_USERNAME, personName);
if (Boolean.parseBoolean(ChainingUserRegistrySynchronizer.this.externalUserControl)
&& ChainingUserRegistrySynchronizer.this.externalUserControlSubsystemName.equals(zone)
&& userRegistryFinalRef instanceof LDAPUserRegistry)
{
try
{
LDAPUserRegistry ldapUserRegistry = (LDAPUserRegistry) userRegistryFinalRef;
if (ldapUserRegistry.getUserAccountStatusInterpreter() != null)
{
QName propertyNameToCheck = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "userAccountStatusProperty");
if (personProperties.get(propertyNameToCheck) != null || ldapUserRegistry.getUserAccountStatusInterpreter().acceptsNullArgument())
{
boolean isUserAccountDisabled = ldapUserRegistry.getUserAccountStatusInterpreter().isUserAccountDisabled(
personProperties.get(propertyNameToCheck));
personProperties.put(ContentModel.PROP_ENABLED, !isUserAccountDisabled);
}
}
}
catch (IllegalArgumentException iae)
{
// Can be thrown by certain implementations of AbstractDirectoryServiceUserAccountStatusInterpreter;
// We'll just log it.
ChainingUserRegistrySynchronizer.logger.debug(iae.getMessage(), iae);
}
}
// for invalid names will throw ConstraintException that will be catched by BatchProcessor$TxnCallback // for invalid names will throw ConstraintException that will be catched by BatchProcessor$TxnCallback
nameChecker.evaluate(personName); nameChecker.evaluate(personName);
Set<String> zones = ChainingUserRegistrySynchronizer.this.authorityService Set<String> zones = ChainingUserRegistrySynchronizer.this.authorityService

View File

@@ -0,0 +1,53 @@
/*
* Copyright (C) 2005-2016 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* 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/>.
*/
package org.alfresco.repo.security.sync.ldap;
import java.io.Serializable;
public abstract class AbstractDirectoryServiceUserAccountStatusInterpreter
{
public static final String USER_ACCOUNT_STATUS_NOT_NULL_MESSAGE = "User account status property value must not be null.";
protected void checkForNullArgument(Serializable arg)
{
if (arg == null)
{
throw new IllegalArgumentException(USER_ACCOUNT_STATUS_NOT_NULL_MESSAGE);
}
}
/**
* Check if directory server user account status is disabled.
*
* @param userAccountStatusValue
* value to interpret user account status from;
*
* @return true if interpreted as disabled, false otherwise
*/
public abstract boolean isUserAccountDisabled(Serializable userAccountStatusValue) throws IllegalArgumentException;
/**
* Specify if the particular implementation of
* {@link AbstractDirectoryServiceUserAccountStatusInterpreter#isUserAccountDisabled(Serializable)}
* will accept null.
*
* @return true if accepts null.
*/
public abstract boolean acceptsNullArgument();
}

View File

@@ -0,0 +1,54 @@
/*
* Copyright (C) 2005-2016 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* 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/>.
*/
package org.alfresco.repo.security.sync.ldap;
import java.io.Serializable;
public class LDAPUserAccountStatusInterpreter extends AbstractDirectoryServiceUserAccountStatusInterpreter
{
private String disabledAccountPropertyValue = "";
private boolean acceptNullArgument;
public void setDisabledAccountPropertyValue(String disabledAccountPropertyValue)
{
this.disabledAccountPropertyValue = disabledAccountPropertyValue;
}
public void setAcceptNullArgument(boolean acceptNullArgument)
{
this.acceptNullArgument = acceptNullArgument;
}
@Override
public boolean isUserAccountDisabled(Serializable userAccountStatus)
{
if (!acceptsNullArgument())
{
checkForNullArgument(userAccountStatus);
}
return userAccountStatus != null && userAccountStatus.toString().equalsIgnoreCase(disabledAccountPropertyValue);
}
@Override
public boolean acceptsNullArgument()
{
return acceptNullArgument;
}
}

View File

@@ -191,6 +191,9 @@ public class LDAPUserRegistry implements UserRegistry, LDAPNameResolver, Initial
/** The LDAP generalized time format. */ /** The LDAP generalized time format. */
private DateFormat timestampFormat; private DateFormat timestampFormat;
/** The LDAP User Account Status Property Interpreter */
private AbstractDirectoryServiceUserAccountStatusInterpreter userAccountStatusInterpreter;
/** /**
* Instantiates a new lDAP user registry. * Instantiates a new lDAP user registry.
*/ */
@@ -505,6 +508,16 @@ public class LDAPUserRegistry implements UserRegistry, LDAPNameResolver, Initial
this.attributeBatchSize = attributeBatchSize; this.attributeBatchSize = attributeBatchSize;
} }
public void setUserAccountStatusInterpreter(AbstractDirectoryServiceUserAccountStatusInterpreter userAccountStatusInterpreter)
{
this.userAccountStatusInterpreter = userAccountStatusInterpreter;
}
public AbstractDirectoryServiceUserAccountStatusInterpreter getUserAccountStatusInterpreter()
{
return userAccountStatusInterpreter;
}
/* /*
* (non-Javadoc) * (non-Javadoc)
* @see org.alfresco.repo.management.subsystems.ActivateableBean#isActive() * @see org.alfresco.repo.management.subsystems.ActivateableBean#isActive()

View File

@@ -0,0 +1,45 @@
/*
* Copyright (C) 2005-2016 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* 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/>.
*/
package org.alfresco.repo.security.sync.ldap_ad;
import java.io.Serializable;
import org.alfresco.repo.security.sync.ldap.AbstractDirectoryServiceUserAccountStatusInterpreter;
public class LDAPADUserAccountStatusInterpreter extends AbstractDirectoryServiceUserAccountStatusInterpreter
{
@Override
public boolean isUserAccountDisabled(Serializable userAccountStatusValue)
{
checkForNullArgument(userAccountStatusValue);
/*
* References:
* https://blogs.technet.microsoft.com/heyscriptingguy/2005/05/12/how-can-i-get-a-list-of-all-the-disabled-user-accounts-in-active-directory
* http://stackoverflow.com/questions/19250969/include-enabled-disabled-account-status-of-ldap-user-in-results/19252033#19252033
*/
return ((Integer.parseInt(userAccountStatusValue.toString())) & 2) != 0;
}
@Override
public boolean acceptsNullArgument()
{
return false;
}
}

View File

@@ -36,6 +36,10 @@ import org.alfresco.repo.security.authentication.AuthenticationException;
import org.alfresco.repo.security.authentication.AuthenticationUtil; import org.alfresco.repo.security.authentication.AuthenticationUtil;
import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork;
import org.alfresco.repo.security.person.PersonServiceImpl; 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_ad.LDAPADUserAccountStatusInterpreter;
import org.alfresco.repo.transaction.RetryingTransactionHelper; import org.alfresco.repo.transaction.RetryingTransactionHelper;
import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeRef;
@@ -44,6 +48,7 @@ import org.alfresco.service.cmr.repository.datatype.DefaultTypeConverter;
import org.alfresco.service.cmr.security.AuthorityService; import org.alfresco.service.cmr.security.AuthorityService;
import org.alfresco.service.cmr.security.AuthorityType; import org.alfresco.service.cmr.security.AuthorityType;
import org.alfresco.service.cmr.security.PersonService; import org.alfresco.service.cmr.security.PersonService;
import org.alfresco.service.namespace.NamespaceService;
import org.alfresco.service.namespace.QName; import org.alfresco.service.namespace.QName;
import org.alfresco.util.GUID; import org.alfresco.util.GUID;
import org.alfresco.util.PropertyMap; import org.alfresco.util.PropertyMap;
@@ -407,6 +412,135 @@ public class ChainingUserRegistrySynchronizerTest extends TestCase
tearDownTestUsersAndGroups(); 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)
{
return mockUserRegistry.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, * 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 * whether or not they move registry. Groups that would have been deleted should have no members and should only be
@@ -785,6 +919,16 @@ public class ChainingUserRegistrySynchronizerTest extends TestCase
return person; 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. * Perform all the necessary assertions to ensure that an authority and its members exist in the correct zone.
* *
@@ -936,10 +1080,31 @@ public class ChainingUserRegistrySynchronizerTest extends TestCase
} }
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. * A Mock {@link UserRegistry} that returns a fixed set of users and groups.
*/ */
public static class MockUserRegistry implements UserRegistry, ActivateableBean public static class MockUserRegistry implements IMockUserRegistry
{ {
private boolean isActive = true; private boolean isActive = true;
@@ -973,15 +1138,7 @@ public class ChainingUserRegistrySynchronizerTest extends TestCase
/** @Override
* 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
*/
public void updateState(Collection<NodeDescription> persons, Collection<NodeDescription> groups) public void updateState(Collection<NodeDescription> persons, Collection<NodeDescription> groups)
{ {
List<NodeDescription> newPersons = new ArrayList<NodeDescription>(this.persons); List<NodeDescription> newPersons = new ArrayList<NodeDescription>(this.persons);
@@ -1066,11 +1223,7 @@ public class ChainingUserRegistrySynchronizerTest extends TestCase
this(zoneId, Arrays.asList(persons), Arrays.asList(groups)); this(zoneId, Arrays.asList(persons), Arrays.asList(groups));
} }
/** @Override
* Gets the zone id.
*
* @return the zoneId
*/
public String getZoneId() public String getZoneId()
{ {
return this.zoneId; return this.zoneId;
@@ -1195,10 +1348,10 @@ public class ChainingUserRegistrySynchronizerTest extends TestCase
* @param registries * @param registries
* the new user registries * the new user registries
*/ */
public void setUserRegistries(MockUserRegistry... registries) public void setUserRegistries(IMockUserRegistry... registries)
{ {
this.contexts = new LinkedHashMap<String, ApplicationContext>(registries.length * 2); this.contexts = new LinkedHashMap<String, ApplicationContext>(registries.length * 2);
for (MockUserRegistry registry : registries) for (IMockUserRegistry registry : registries)
{ {
StaticApplicationContext context = new StaticApplicationContext(); StaticApplicationContext context = new StaticApplicationContext();
context.getDefaultListableBeanFactory().registerSingleton("userRegistry", registry); context.getDefaultListableBeanFactory().registerSingleton("userRegistry", registry);
@@ -1230,7 +1383,7 @@ public class ChainingUserRegistrySynchronizerTest extends TestCase
public void updateZone(String zoneId, NodeDescription[] persons, NodeDescription[] groups) public void updateZone(String zoneId, NodeDescription[] persons, NodeDescription[] groups)
{ {
ApplicationContext context = this.contexts.get(zoneId); ApplicationContext context = this.contexts.get(zoneId);
MockUserRegistry registry = (MockUserRegistry) context.getBean("userRegistry"); IMockUserRegistry registry = (IMockUserRegistry) context.getBean("userRegistry");
registry.updateState(Arrays.asList(persons), Arrays.asList(groups)); registry.updateState(Arrays.asList(persons), Arrays.asList(groups));
} }