[MNT-24513] Immutable user (IDS): allow to change enabled status (#2789)

* [MNT-24513] Immutable user: allow enabled status change

* [MNT-24513] Created 'allow.immutable.user.enabled.status.update' to control whether an immutabled user enabled status can be changed or not

* [MNT-24513] Regardless user details enabled status, the person nodeRef enabled status is also checked

* [MNT-24513] Prevent LDAP users from being disabled. Changed variable name.
This commit is contained in:
Tiago Salvado
2024-07-25 21:23:51 +01:00
committed by GitHub
parent f9946827c4
commit 8a61badabc
5 changed files with 116 additions and 9 deletions

View File

@@ -125,7 +125,7 @@ public class PeopleImpl implements People
protected ResetPasswordService resetPasswordService; protected ResetPasswordService resetPasswordService;
protected UserRegistrySynchronizer userRegistrySynchronizer; protected UserRegistrySynchronizer userRegistrySynchronizer;
protected Renditions renditions; protected Renditions renditions;
private Boolean allowImmutableEnabledUpdate;
private final static Map<String, QName> sort_params_to_qnames; private final static Map<String, QName> sort_params_to_qnames;
static static
@@ -202,6 +202,11 @@ public class PeopleImpl implements People
this.userRegistrySynchronizer = userRegistrySynchronizer; this.userRegistrySynchronizer = userRegistrySynchronizer;
} }
public void setAllowImmutableEnabledUpdate(Boolean allowImmutableEnabledUpdate)
{
this.allowImmutableEnabledUpdate = allowImmutableEnabledUpdate;
}
/** /**
* Validate, perform -me- substitution and canonicalize the person ID. * Validate, perform -me- substitution and canonicalize the person ID.
* *
@@ -708,16 +713,26 @@ public class PeopleImpl implements People
// if requested, update password // if requested, update password
updatePassword(isAdmin, personIdToUpdate, person); updatePassword(isAdmin, personIdToUpdate, person);
if (person.isEnabled() != null) Set<QName> immutableProperties = userRegistrySynchronizer.getPersonMappedProperties(personIdToUpdate);
Boolean isEnabled = person.isEnabled();
if (isEnabled != null)
{ {
if (isAdminAuthority(personIdToUpdate)) if (isAdminAuthority(personIdToUpdate))
{ {
throw new PermissionDeniedException("Admin authority cannot be disabled."); throw new PermissionDeniedException("Admin authority cannot be disabled.");
} }
// note: if current user is not an admin then permission denied exception is thrown if (allowImmutableEnabledStatusUpdate(personIdToUpdate, isAdmin, immutableProperties))
MutableAuthenticationService mutableAuthenticationService = (MutableAuthenticationService) authenticationService; {
mutableAuthenticationService.setAuthenticationEnabled(personIdToUpdate, person.isEnabled()); LOGGER.info("User " + personIdToUpdate + " is immutable but enabled status will be set to: " + isEnabled);
}
else
{
// note: if current user is not an admin then permission denied exception is thrown
MutableAuthenticationService mutableAuthenticationService = (MutableAuthenticationService) authenticationService;
mutableAuthenticationService.setAuthenticationEnabled(personIdToUpdate, person.isEnabled());
}
} }
NodeRef personNodeRef = personService.getPerson(personIdToUpdate, false); NodeRef personNodeRef = personService.getPerson(personIdToUpdate, false);
@@ -742,9 +757,7 @@ public class PeopleImpl implements People
properties.putAll(nodes.mapToNodeProperties(customProps)); properties.putAll(nodes.mapToNodeProperties(customProps));
} }
// MNT-21150 LDAP synced attributes can be changed using REST API // MNT-21150 LDAP synced attributes can't be changed using REST API
Set<QName> immutableProperties = userRegistrySynchronizer.getPersonMappedProperties(personIdToUpdate);
immutableProperties.forEach(immutableProperty -> { immutableProperties.forEach(immutableProperty -> {
if (properties.containsKey(immutableProperty)) if (properties.containsKey(immutableProperty))
{ {
@@ -768,6 +781,28 @@ public class PeopleImpl implements People
return getPerson(personId); return getPerson(personId);
} }
private boolean allowImmutableEnabledStatusUpdate(String userId, boolean isAdmin, Set<QName> immutableProperties)
{
if (allowImmutableEnabledUpdate)
{
boolean containLdapUserAccountStatus = false;
QName propertyNameToCheck = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "userAccountStatusProperty");
for (QName immutableProperty : immutableProperties)
{
if (immutableProperty.equals(propertyNameToCheck))
{
containLdapUserAccountStatus = true;
break;
}
}
return isAdmin && !containLdapUserAccountStatus && !isMutableAuthority(userId);
}
return false;
}
private boolean checkCurrentUserOrAdmin(String personId) private boolean checkCurrentUserOrAdmin(String personId)
{ {
boolean isAdmin = isAdminAuthority(); boolean isAdmin = isAdminAuthority();

View File

@@ -764,6 +764,7 @@
<property name="thumbnailService" ref="ThumbnailService" /> <property name="thumbnailService" ref="ThumbnailService" />
<property name="resetPasswordService" ref="resetPasswordService" /> <property name="resetPasswordService" ref="resetPasswordService" />
<property name="userRegistrySynchronizer" ref="userRegistrySynchronizer" /> <property name="userRegistrySynchronizer" ref="userRegistrySynchronizer" />
<property name="allowImmutableEnabledUpdate" value="${allow.immutable.user.enabled.status.update}" />
</bean> </bean>
<bean id="People" class="org.springframework.aop.framework.ProxyFactoryBean"> <bean id="People" class="org.springframework.aop.framework.ProxyFactoryBean">

View File

@@ -36,7 +36,11 @@ import net.sf.acegisecurity.UserDetails;
import net.sf.acegisecurity.providers.UsernamePasswordAuthenticationToken; import net.sf.acegisecurity.providers.UsernamePasswordAuthenticationToken;
import net.sf.acegisecurity.providers.dao.User; import net.sf.acegisecurity.providers.dao.User;
import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork;
import org.alfresco.repo.tenant.TenantService; import org.alfresco.repo.tenant.TenantService;
import org.alfresco.service.cmr.security.AuthenticationService;
import org.alfresco.service.cmr.security.MutableAuthenticationService;
import org.alfresco.service.cmr.security.PersonService;
import org.apache.commons.logging.Log; import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory; import org.apache.commons.logging.LogFactory;
@@ -49,12 +53,30 @@ public class AuthenticationContextImpl implements AuthenticationContext
private final Log logger = LogFactory.getLog(getClass()); private final Log logger = LogFactory.getLog(getClass());
private TenantService tenantService; private TenantService tenantService;
private PersonService personService;
private AuthenticationService authenticationService;
private Boolean allowImmutableEnabledUpdate;
public void setTenantService(TenantService tenantService) public void setTenantService(TenantService tenantService)
{ {
this.tenantService = tenantService; this.tenantService = tenantService;
} }
public void setPersonService(PersonService personService)
{
this.personService = personService;
}
public void setAuthenticationService(AuthenticationService authenticationService)
{
this.authenticationService = authenticationService;
}
public void setAllowImmutableEnabledUpdate(Boolean allowImmutableEnabledUpdate)
{
this.allowImmutableEnabledUpdate = allowImmutableEnabledUpdate;
}
/** /**
* Explicitly set the given validated user details to be authenticated. * Explicitly set the given validated user details to be authenticated.
* *
@@ -70,7 +92,7 @@ public class AuthenticationContextImpl implements AuthenticationContext
{ {
// Apply the same validation that ACEGI would have to the user details - we may be going through a 'back // Apply the same validation that ACEGI would have to the user details - we may be going through a 'back
// door'. // door'.
if (!ud.isEnabled()) if (isDisabled(userId, ud))
{ {
throw new DisabledException("User is disabled"); throw new DisabledException("User is disabled");
} }
@@ -114,6 +136,43 @@ public class AuthenticationContextImpl implements AuthenticationContext
} }
} }
private boolean isDisabled(String userId, UserDetails ud)
{
boolean isDisabled = !ud.isEnabled();
boolean isSystemUser = isSystemUserName(userId);
if (allowImmutableEnabledUpdate && !isSystemUser)
{
try
{
boolean isImmutable = isImmutableAuthority(userId);
boolean isPersonEnabled = personService.isEnabled(userId);
isDisabled = isDisabled || (isImmutable && !isPersonEnabled);
}
catch (Exception e)
{
if (logger.isWarnEnabled())
{
logger.warn("Failed to determine if person is enabled: " + userId + ", using user details status: " + isDisabled);
}
}
}
return isDisabled;
}
private boolean isImmutableAuthority(String authorityName)
{
return AuthenticationUtil.runAsSystem(new RunAsWork<Boolean>()
{
@Override public Boolean doWork() throws Exception
{
MutableAuthenticationService mutableAuthenticationService = (MutableAuthenticationService) authenticationService;
return !mutableAuthenticationService.isAuthenticationMutable(authorityName);
}
});
}
public Authentication setSystemUserAsCurrentUser() public Authentication setSystemUserAsCurrentUser()
{ {
return setSystemUserAsCurrentUser(TenantService.DEFAULT_DOMAIN); return setSystemUserAsCurrentUser(TenantService.DEFAULT_DOMAIN);

View File

@@ -274,6 +274,15 @@
<property name="tenantService"> <property name="tenantService">
<ref bean="tenantService" /> <ref bean="tenantService" />
</property> </property>
<property name="personService">
<ref bean="personService" />
</property>
<property name="authenticationService">
<ref bean="AuthenticationService" />
</property>
<property name="allowImmutableEnabledUpdate">
<value>${allow.immutable.user.enabled.status.update}</value>
</property>
</bean> </bean>
<!-- Simple Authentication component that rejects all authentication requests --> <!-- Simple Authentication component that rejects all authentication requests -->

View File

@@ -435,6 +435,9 @@ repo.remote.endpoint=/service
# persisted. # persisted.
create.missing.people=${server.transaction.allow-writes} create.missing.people=${server.transaction.allow-writes}
# Allow an immutable user to have its enabled status changed
allow.immutable.user.enabled.status.update=false
# Create home folders (unless disabled, see next property) as people are created (true) or create them lazily (false) # Create home folders (unless disabled, see next property) as people are created (true) or create them lazily (false)
home.folder.creation.eager=true home.folder.creation.eager=true
# Disable home folder creation - if true then home folders are not created (neither eagerly nor lazily) # Disable home folder creation - if true then home folders are not created (neither eagerly nor lazily)