mirror of
https://github.com/Alfresco/alfresco-community-repo.git
synced 2025-10-15 15:02:20 +00:00
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:
@@ -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 -->
|
||||||
|
@@ -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>
|
@@ -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
|
||||||
|
@@ -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>
|
@@ -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
|
||||||
|
@@ -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>
|
||||||
|
|
||||||
|
|
||||||
|
@@ -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=
|
||||||
|
@@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -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}
|
||||||
*/
|
*/
|
||||||
|
@@ -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
|
||||||
|
@@ -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();
|
||||||
|
}
|
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
@@ -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()
|
||||||
|
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
@@ -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));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user