From 8f6773284f9fd1f4e714db3abdf21a94c3012524 Mon Sep 17 00:00:00 2001 From: Dave Ward Date: Wed, 21 Oct 2009 15:52:13 +0000 Subject: [PATCH] Merged V3.2 to HEAD 16939: Merged V3.1 to V3.2 16938: ETHREEOH-622: AuthorityServiceImpl uses userNameMatcher to check for admin users according to case sensitivity settings 16934: ETHREEOH-2584: Coding error in BaseSSOAuthenticationFilter 16924: LDAP Performance - Created NodeService addChild variants that can add associations to multiple parents (groups/zones) at the same time with a single path check. - Created AuthorityService addAuthority variant that can add an authority to multiple groups at the same time, using the above - Optimized group association creation strategy. Groups and Persons created in 'depth first' order (root groups first, parents last). Prevents the nodes having to be reindexed. git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@17070 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261 --- .../alfresco/authority-services-context.xml | 3 + .../org/alfresco/repo/avm/AVMNodeService.java | 59 +- .../repo/node/db/DbNodeServiceImpl.java | 67 +- .../repo/security/authority/AuthorityDAO.java | 8 +- .../security/authority/AuthorityDAOImpl.java | 46 +- .../authority/AuthorityServiceImpl.java | 966 +++++++++--------- .../authority/SimpleAuthorityServiceImpl.java | 8 +- .../ChainingUserRegistrySynchronizer.java | 771 ++++++++------ .../ChainingUserRegistrySynchronizerTest.java | 32 +- .../repo/security/sync/UserRegistry.java | 19 +- .../security/sync/ldap/LDAPUserRegistry.java | 32 +- .../repo/version/NodeServiceImpl.java | 12 + .../service/cmr/repository/NodeService.java | 20 + .../cmr/security/AuthorityService.java | 13 + 14 files changed, 1151 insertions(+), 905 deletions(-) diff --git a/config/alfresco/authority-services-context.xml b/config/alfresco/authority-services-context.xml index 77e255b0fc..b329b69dd6 100644 --- a/config/alfresco/authority-services-context.xml +++ b/config/alfresco/authority-services-context.xml @@ -25,6 +25,9 @@ + + + diff --git a/source/java/org/alfresco/repo/avm/AVMNodeService.java b/source/java/org/alfresco/repo/avm/AVMNodeService.java index 6effe3c69f..813886f84b 100644 --- a/source/java/org/alfresco/repo/avm/AVMNodeService.java +++ b/source/java/org/alfresco/repo/avm/AVMNodeService.java @@ -949,6 +949,28 @@ public class AVMNodeService extends AbstractNodeServiceImpl implements NodeServi NodeRef childRef, QName assocTypeQName, QName qname) throws InvalidNodeRefException + { + return addChild(Collections.singletonList(parentRef), childRef, assocTypeQName, qname).get(0); + } + + /** + * Associates a given child node with a given collection of parents. All nodes must belong to the same store. + *

+ * + * + * @param parentRefs + * @param childRef + * @param assocTypeQName the qualified name of the association type as defined in the datadictionary + * @param qname the qualified name of the association + * @return Returns a reference to the newly created child association + * @throws InvalidNodeRefException if the parent or child nodes could not be found + * @throws CyclicChildRelationshipException if the child partakes in a cyclic relationship after the add + */ + public List addChild( + Collection parentRefs, + NodeRef childRef, + QName assocTypeQName, + QName qname) throws InvalidNodeRefException { Pair childVersionPath = AVMNodeConverter.ToAVMVersionPath(childRef); AVMNodeDescriptor child = fAVMService.lookup(childVersionPath.getFirst(), @@ -957,24 +979,29 @@ public class AVMNodeService extends AbstractNodeServiceImpl implements NodeServi { throw new InvalidNodeRefException(childVersionPath.getSecond() + " not found.", childRef); } - Pair parentVersionPath = AVMNodeConverter.ToAVMVersionPath(parentRef); - if (parentVersionPath.getFirst() >= 0) + + List childAssociationRefs = new ArrayList(parentRefs.size()); + for (NodeRef parentRef : parentRefs) { - throw new InvalidNodeRefException("Read Only.", parentRef); - } - try - { - fAVMService.link(parentVersionPath.getSecond(), qname.getLocalName(), child); - ChildAssociationRef newChild = - new ChildAssociationRef(assocTypeQName, parentRef, qname, - AVMNodeConverter.ToNodeRef(-1, - AVMNodeConverter.ExtendAVMPath(parentVersionPath.getSecond(), qname.getLocalName()))); - return newChild; - } - catch (AVMException e) - { - throw new InvalidNodeRefException("Could not link.", childRef); + Pair parentVersionPath = AVMNodeConverter.ToAVMVersionPath(parentRef); + if (parentVersionPath.getFirst() >= 0) + { + throw new InvalidNodeRefException("Read Only.", parentRef); + } + try + { + fAVMService.link(parentVersionPath.getSecond(), qname.getLocalName(), child); + ChildAssociationRef newChild = new ChildAssociationRef(assocTypeQName, parentRef, qname, + AVMNodeConverter.ToNodeRef(-1, AVMNodeConverter.ExtendAVMPath(parentVersionPath.getSecond(), + qname.getLocalName()))); + childAssociationRefs.add(newChild); + } + catch (AVMException e) + { + throw new InvalidNodeRefException("Could not link.", childRef); + } } + return childAssociationRefs; } /** diff --git a/source/java/org/alfresco/repo/node/db/DbNodeServiceImpl.java b/source/java/org/alfresco/repo/node/db/DbNodeServiceImpl.java index d6d723b890..06b2346752 100644 --- a/source/java/org/alfresco/repo/node/db/DbNodeServiceImpl.java +++ b/source/java/org/alfresco/repo/node/db/DbNodeServiceImpl.java @@ -852,48 +852,63 @@ public class DbNodeServiceImpl extends AbstractNodeServiceImpl // invokeOnDeleteNode(childParentAssocRef, childNodeType, childNodeQNames, true); } } - + public ChildAssociationRef addChild(NodeRef parentRef, NodeRef childRef, QName assocTypeQName, QName assocQName) { - Pair parentNodePair = getNodePairNotNull(parentRef); - Long parentNodeId = parentNodePair.getFirst(); + return addChild(Collections.singletonList(parentRef), childRef, assocTypeQName, assocQName).get(0); + } + + public List addChild(Collection parentRefs, NodeRef childRef, QName assocTypeQName, QName assocQName) + { + // Get the node's name, if present Pair childNodePair = getNodePairNotNull(childRef); Long childNodeId = childNodePair.getFirst(); - - // Invoke policy behaviours - invokeBeforeCreateChildAssociation(parentRef, childRef, assocTypeQName, assocQName, false); - - // Get the node's name, if present Map childNodeProperties = nodeDaoService.getNodeProperties(childNodePair.getFirst()); String childNodeName = extractNameProperty(childNodeProperties); - - // make the association - Pair childAssocPair = nodeDaoService.newChildAssoc( - parentNodeId, - childNodeId, - false, - assocTypeQName, - assocQName, - childNodeName); - ChildAssociationRef childAssocRef = childAssocPair.getSecond(); - // ensure name uniqueness - setChildNameUnique(childAssocPair, childNodePair); - NodeRef childNodeRef = childAssocRef.getChildRef(); + + List childAssociationRefs = new ArrayList(parentRefs.size()); + List> parentNodePairs = new ArrayList>(parentRefs.size()); + for (NodeRef parentRef : parentRefs) + { + Pair parentNodePair = getNodePairNotNull(parentRef); + Long parentNodeId = parentNodePair.getFirst(); + parentNodePairs.add(parentNodePair); + + // Invoke policy behaviours + invokeBeforeCreateChildAssociation(parentRef, childRef, assocTypeQName, assocQName, false); + + // make the association + Pair childAssocPair = nodeDaoService.newChildAssoc(parentNodeId, childNodeId, + false, assocTypeQName, assocQName, childNodeName); + // ensure name uniqueness + setChildNameUnique(childAssocPair, childNodePair); + + childAssociationRefs.add(childAssocPair.getSecond()); + } // check that the child addition of the child has not created a cyclic relationship // this functionality is provided for free in getPath - getPaths(childNodeRef, false); + getPaths(childRef, false); // Invoke policy behaviours - invokeOnCreateChildAssociation(childAssocRef, false); + for (ChildAssociationRef childAssocRef : childAssociationRefs) + { + invokeOnCreateChildAssociation(childAssocRef, false); + } // Add missing aspects - addMissingAspects(parentNodePair, assocTypeQName); + for (Pair parentNodePair : parentNodePairs) + { + addMissingAspects(parentNodePair, assocTypeQName); + } // Index - nodeIndexer.indexCreateChildAssociation(childAssocRef); + for (ChildAssociationRef childAssocRef : childAssociationRefs) + { + nodeIndexer.indexCreateChildAssociation(childAssocRef); + } - return childAssocRef; + return childAssociationRefs; } public void removeChild(NodeRef parentRef, NodeRef childRef) throws InvalidNodeRefException diff --git a/source/java/org/alfresco/repo/security/authority/AuthorityDAO.java b/source/java/org/alfresco/repo/security/authority/AuthorityDAO.java index 37ea38488c..42ad11619a 100644 --- a/source/java/org/alfresco/repo/security/authority/AuthorityDAO.java +++ b/source/java/org/alfresco/repo/security/authority/AuthorityDAO.java @@ -24,21 +24,21 @@ */ package org.alfresco.repo.security.authority; +import java.util.Collection; import java.util.Set; import org.alfresco.service.cmr.repository.NodeRef; -import org.alfresco.service.cmr.security.AuthorityService; import org.alfresco.service.cmr.security.AuthorityType; public interface AuthorityDAO { /** - * Add an authority to another. + * Add a child authority to the given parent authorities * - * @param parentName + * @param parentNames * @param childName */ - void addAuthority(String parentName, String childName); + void addAuthority(Collection parentNames, String childName); /** * Create an authority. diff --git a/source/java/org/alfresco/repo/security/authority/AuthorityDAOImpl.java b/source/java/org/alfresco/repo/security/authority/AuthorityDAOImpl.java index fc7b15d3fe..3677b10718 100644 --- a/source/java/org/alfresco/repo/security/authority/AuthorityDAOImpl.java +++ b/source/java/org/alfresco/repo/security/authority/AuthorityDAOImpl.java @@ -149,26 +149,34 @@ public class AuthorityDAOImpl implements AuthorityDAO, NodeServicePolicies.Befor return ref != null; } - public void addAuthority(String parentName, String childName) + public void addAuthority(Collection parentNames, String childName) { - NodeRef parentRef = getAuthorityOrNull(parentName); - if (parentRef == null) - { - throw new UnknownAuthorityException("An authority was not found for " + parentName); - } + Set parentRefs = new HashSet(parentNames.size() * 2); AuthorityType authorityType = AuthorityType.getAuthorityType(childName); - if (!authorityType.equals(AuthorityType.USER) - && !authorityType.equals(AuthorityType.GROUP) - && !(authorityType.equals(AuthorityType.ROLE) && AuthorityType.getAuthorityType(parentName).equals(AuthorityType.ROLE))) + boolean notUserOrGroup = !authorityType.equals(AuthorityType.USER) && !authorityType.equals(AuthorityType.GROUP); + for (String parentName : parentNames) { - throw new AlfrescoRuntimeException("Authorities of the type " + authorityType + " may not be added to other authorities"); + NodeRef parentRef = getAuthorityOrNull(parentName); + if (parentRef == null) + { + throw new UnknownAuthorityException("An authority was not found for " + parentName); + } + if (notUserOrGroup + && !(authorityType.equals(AuthorityType.ROLE) && AuthorityType.getAuthorityType(parentName).equals( + AuthorityType.ROLE))) + { + throw new AlfrescoRuntimeException("Authorities of the type " + authorityType + + " may not be added to other authorities"); + } + parentRefs.add(parentRef); } NodeRef childRef = getAuthorityOrNull(childName); if (childRef == null) { throw new UnknownAuthorityException("An authority was not found for " + childName); } - nodeService.addChild(parentRef, childRef, ContentModel.ASSOC_MEMBER, QName.createQName("cm", childName, namespacePrefixResolver)); + nodeService.addChild(parentRefs, childRef, ContentModel.ASSOC_MEMBER, QName.createQName("cm", childName, + namespacePrefixResolver)); authorityLookupCache.clear(); } @@ -183,10 +191,12 @@ public class AuthorityDAOImpl implements AuthorityDAO, NodeServicePolicies.Befor ContentModel.TYPE_AUTHORITY_CONTAINER, props).getChildRef(); if (authorityZones != null) { + Set zoneRefs = new HashSet(authorityZones.size() * 2); for (String authorityZone : authorityZones) { - nodeService.addChild(getOrCreateZone(authorityZone), childRef, ContentModel.ASSOC_IN_ZONE, QName.createQName("cm", name, namespacePrefixResolver)); + zoneRefs.add(getOrCreateZone(authorityZone)); } + nodeService.addChild(zoneRefs, childRef, ContentModel.ASSOC_IN_ZONE, QName.createQName("cm", name, namespacePrefixResolver)); } authorityLookupCache.clear(); } @@ -607,15 +617,15 @@ public class AuthorityDAOImpl implements AuthorityDAO, NodeServicePolicies.Befor { if ((zones != null) && (zones.size() > 0)) { + Set zoneRefs = new HashSet(zones.size() * 2); + for (String authorityZone : zones) + { + zoneRefs.add(getOrCreateZone(authorityZone)); + } NodeRef authRef = getAuthorityOrNull(authorityName); if (authRef != null) { - for (String zone : zones) - { - // Add the person to an authentication zone (corresponding to an external user registry) - // Let's preserve case on this child association - nodeService.addChild(getOrCreateZone(zone), authRef, ContentModel.ASSOC_IN_ZONE, QName.createQName("cm", authorityName, namespacePrefixResolver)); - } + nodeService.addChild(zoneRefs, authRef, ContentModel.ASSOC_IN_ZONE, QName.createQName("cm", authorityName, namespacePrefixResolver)); } } } diff --git a/source/java/org/alfresco/repo/security/authority/AuthorityServiceImpl.java b/source/java/org/alfresco/repo/security/authority/AuthorityServiceImpl.java index f188ae3fbd..ce8ad64667 100644 --- a/source/java/org/alfresco/repo/security/authority/AuthorityServiceImpl.java +++ b/source/java/org/alfresco/repo/security/authority/AuthorityServiceImpl.java @@ -1,141 +1,150 @@ -/* - * Copyright (C) 2005-2007 Alfresco Software Limited. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - - * This program 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 General Public License for more details. - - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - - * As a special exception to the terms and conditions of version 2.0 of - * the GPL, you may redistribute this Program in connection with Free/Libre - * and Open Source Software ("FLOSS") applications as described in Alfresco's - * FLOSS exception. You should have recieved a copy of the text describing - * the FLOSS exception, and it is also available here: - * http://www.alfresco.com/legal/licensing" - */ -package org.alfresco.repo.security.authority; - -import java.util.Collections; -import java.util.HashSet; -import java.util.Set; - -import org.alfresco.model.ContentModel; -import org.alfresco.repo.security.authentication.AuthenticationUtil; -import org.alfresco.repo.security.permissions.PermissionServiceSPI; -import org.alfresco.repo.tenant.TenantService; -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.AuthenticationService; -import org.alfresco.service.cmr.security.AuthorityService; -import org.alfresco.service.cmr.security.AuthorityType; -import org.alfresco.service.cmr.security.PermissionService; -import org.alfresco.service.cmr.security.PersonService; -import org.springframework.beans.factory.InitializingBean; - -/** - * The default implementation of the authority service. - * - * @author Andy Hind - */ -public class AuthorityServiceImpl implements AuthorityService, InitializingBean -{ - private static Set DEFAULT_ZONES = new HashSet(); - - private PersonService personService; - - private NodeService nodeService; - - private TenantService tenantService; - - private AuthorityDAO authorityDAO; - - private AuthenticationService authenticationService; - - private PermissionServiceSPI permissionServiceSPI; +/* + * Copyright (C) 2005-2007 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program 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 General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have recieved a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ +package org.alfresco.repo.security.authority; + +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +import org.alfresco.model.ContentModel; +import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.repo.security.permissions.PermissionServiceSPI; +import org.alfresco.repo.security.person.UserNameMatcher; +import org.alfresco.repo.tenant.TenantService; +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.AuthenticationService; +import org.alfresco.service.cmr.security.AuthorityService; +import org.alfresco.service.cmr.security.AuthorityType; +import org.alfresco.service.cmr.security.PermissionService; +import org.alfresco.service.cmr.security.PersonService; +import org.springframework.beans.factory.InitializingBean; + +/** + * The default implementation of the authority service. + * + * @author Andy Hind + */ +public class AuthorityServiceImpl implements AuthorityService, InitializingBean +{ + private static Set DEFAULT_ZONES = new HashSet(); + + private PersonService personService; + + private NodeService nodeService; + + private TenantService tenantService; + + private AuthorityDAO authorityDAO; + + private UserNameMatcher userNameMatcher; + + private AuthenticationService authenticationService; + + private PermissionServiceSPI permissionServiceSPI; + + private Set adminSet = Collections.singleton(PermissionService.ADMINISTRATOR_AUTHORITY); + + private Set guestSet = Collections.singleton(PermissionService.GUEST_AUTHORITY); + + private Set allSet = Collections.singleton(PermissionService.ALL_AUTHORITIES); + + private Set adminGroups = Collections.emptySet(); - private Set adminSet = Collections.singleton(PermissionService.ADMINISTRATOR_AUTHORITY); - - private Set guestSet = Collections.singleton(PermissionService.GUEST_AUTHORITY); - - private Set allSet = Collections.singleton(PermissionService.ALL_AUTHORITIES); - - private Set adminGroups = Collections.emptySet(); - private Set guestGroups = Collections.emptySet(); - static - { - DEFAULT_ZONES.add(AuthorityService.ZONE_APP_DEFAULT); - DEFAULT_ZONES.add(AuthorityService.ZONE_AUTH_ALFRESCO); - } - - public AuthorityServiceImpl() - { - super(); - } - - public void setNodeService(NodeService nodeService) - { - this.nodeService = nodeService; - } - - public void setTenantService(TenantService tenantService) - { - this.tenantService = tenantService; - } - - public void setPersonService(PersonService personService) - { - this.personService = personService; - } - - public void setAuthorityDAO(AuthorityDAO authorityDAO) - { - this.authorityDAO = authorityDAO; - } - - public void setAuthenticationService(AuthenticationService authenticationService) - { - this.authenticationService = authenticationService; - } - - public void setPermissionServiceSPI(PermissionServiceSPI permissionServiceSPI) - { - this.permissionServiceSPI = permissionServiceSPI; - } + static + { + DEFAULT_ZONES.add(AuthorityService.ZONE_APP_DEFAULT); + DEFAULT_ZONES.add(AuthorityService.ZONE_AUTH_ALFRESCO); + } + + public AuthorityServiceImpl() + { + super(); + } + + public void setNodeService(NodeService nodeService) + { + this.nodeService = nodeService; + } + + public void setTenantService(TenantService tenantService) + { + this.tenantService = tenantService; + } + + public void setPersonService(PersonService personService) + { + this.personService = personService; + } + + public void setAuthorityDAO(AuthorityDAO authorityDAO) + { + this.authorityDAO = authorityDAO; + } + + public void setUserNameMatcher(UserNameMatcher userNameMatcher) + { + this.userNameMatcher = userNameMatcher; + } + + public void setAuthenticationService(AuthenticationService authenticationService) + { + this.authenticationService = authenticationService; + } + + public void setPermissionServiceSPI(PermissionServiceSPI permissionServiceSPI) + { + this.permissionServiceSPI = permissionServiceSPI; + } + + public void setAdminGroups(Set adminGroups) + { + this.adminGroups = adminGroups; + } - public void setAdminGroups(Set adminGroups) - { - this.adminGroups = adminGroups; - } - public void setGuestGroups(Set guestGroups) { this.guestGroups = guestGroups; } - public void afterPropertiesSet() throws Exception - { - // Fully qualify the admin group names - if (!this.adminGroups.isEmpty()) - { - Set adminGroups = new HashSet(this.adminGroups.size()); - for (String group : this.adminGroups) - { - adminGroups.add(getName(AuthorityType.GROUP, group)); - } - this.adminGroups = adminGroups; - } + public void afterPropertiesSet() throws Exception + { + // Fully qualify the admin group names + if (!this.adminGroups.isEmpty()) + { + Set adminGroups = new HashSet(this.adminGroups.size()); + for (String group : this.adminGroups) + { + adminGroups.add(getName(AuthorityType.GROUP, group)); + } + this.adminGroups = adminGroups; + } // Fully qualify the guest group names if (!this.guestGroups.isEmpty()) { @@ -146,28 +155,28 @@ public class AuthorityServiceImpl implements AuthorityService, InitializingBean } this.guestGroups = guestGroups; } - } - - public boolean hasAdminAuthority() - { - String currentUserName = AuthenticationUtil.getRunAsUser(); - - // Determine whether the administrator role is mapped to this user or one of their groups - return ((currentUserName != null) && getAuthoritiesForUser(currentUserName).contains(PermissionService.ADMINISTRATOR_AUTHORITY)); - } - - public boolean isAdminAuthority(String authorityName) - { - String canonicalName = personService.getUserIdentifier(authorityName); - if (canonicalName == null) - { - canonicalName = authorityName; - } - - // Determine whether the administrator role is mapped to this user or one of their groups - return getAuthoritiesForUser(canonicalName).contains(PermissionService.ADMINISTRATOR_AUTHORITY); - } - + } + + public boolean hasAdminAuthority() + { + String currentUserName = AuthenticationUtil.getRunAsUser(); + + // Determine whether the administrator role is mapped to this user or one of their groups + return ((currentUserName != null) && getAuthoritiesForUser(currentUserName).contains(PermissionService.ADMINISTRATOR_AUTHORITY)); + } + + public boolean isAdminAuthority(String authorityName) + { + String canonicalName = personService.getUserIdentifier(authorityName); + if (canonicalName == null) + { + canonicalName = authorityName; + } + + // Determine whether the administrator role is mapped to this user or one of their groups + return getAuthoritiesForUser(canonicalName).contains(PermissionService.ADMINISTRATOR_AUTHORITY); + } + public boolean hasGuestAuthority() { String currentUserName = AuthenticationUtil.getRunAsUser(); @@ -188,75 +197,40 @@ public class AuthorityServiceImpl implements AuthorityService, InitializingBean return getAuthoritiesForUser(canonicalName).contains(PermissionService.GUEST_AUTHORITY); } - public Set getAuthorities() - { - String currentUserName = AuthenticationUtil.getRunAsUser(); - return getAuthoritiesForUser(currentUserName); - } - - public Set getAuthoritiesForUser(String currentUserName) - { - Set authorities = new HashSet(64); - - authorities.addAll(getContainingAuthorities(null, currentUserName, false)); - - // Work out mapped roles - + public Set getAuthorities() + { + String currentUserName = AuthenticationUtil.getRunAsUser(); + return getAuthoritiesForUser(currentUserName); + } + + public Set getAuthoritiesForUser(String currentUserName) + { + Set authorities = new HashSet(64); + + authorities.addAll(getContainingAuthorities(null, currentUserName, false)); + + // Work out mapped roles + // Check named guest and admin users - Set adminUsers = this.authenticationService.getDefaultAdministratorUserNames(); + Set adminUsers = this.authenticationService.getDefaultAdministratorUserNames(); Set guestUsers = this.authenticationService.getDefaultGuestUserNames(); - // note: for multi-tenancy, this currently relies on a naming convention which assumes that all tenant admins will - // have the same base name as the default non-tenant specific admin. Typically "admin" is the default required admin user, - // although, if for example "bob" is also listed as an admin then all tenant-specific bob's will also have admin authority - String currentUserBaseName = tenantService.getBaseNameUser(currentUserName); - - boolean isAdminUser = false; - boolean isGuestUser = false; - if (tenantService.isEnabled()) - { - // note: for multi-tenancy, this currently relies on a naming convention which assumes that all tenant admins will - // have the same base name as the default non-tenant specific admin. Typically "admin" is the default required admin user, - // although, if for example "bob" is also listed as an admin then all tenant-specific bob's will also have admin authority - - for (String adminUser : adminUsers) - { - if (adminUser.equals(currentUserName) || tenantService.getBaseNameUser(adminUser).equals(currentUserBaseName)) - { - isAdminUser = true; - break; - } - } - if (!isAdminUser) - { - for (String guestUser : guestUsers) - { - if (guestUser.equals(currentUserName) || tenantService.getBaseNameUser(guestUser).equals(currentUserBaseName)) - { - isGuestUser = true; - break; - } - } - } - } - else - { - isAdminUser = (adminUsers.contains(currentUserName) || adminUsers.contains(currentUserBaseName)); - isGuestUser = (guestUsers.contains(currentUserName) || guestUsers.contains(currentUserBaseName)); - } - + // Check for name matches using MT + case sensitivity rules + boolean isAdminUser = containsMatch(adminUsers, currentUserName); + boolean isGuestUser = containsMatch(guestUsers, currentUserName); + // Check if any of the user's groups are listed as admin groups - if (!isAdminUser && !adminGroups.isEmpty()) - { - for (String authority : authorities) - { - if (adminGroups.contains(authority) || adminGroups.contains(tenantService.getBaseNameUser(authority))) - { - isAdminUser = true; - break; - } - } - } + if (!isAdminUser && !adminGroups.isEmpty()) + { + for (String authority : authorities) + { + if (adminGroups.contains(authority) || adminGroups.contains(tenantService.getBaseNameUser(authority))) + { + isAdminUser = true; + break; + } + } + } // Check if any of the user's groups are listed as guest groups if (!isAdminUser && !isGuestUser && !guestGroups.isEmpty()) { @@ -269,272 +243,308 @@ public class AuthorityServiceImpl implements AuthorityService, InitializingBean } } } - + // Give admin user's the ADMINISTRATOR authorities - if (isAdminUser) - { - authorities.addAll(adminSet); - } + if (isAdminUser) + { + authorities.addAll(adminSet); + } // Give all non-guest users the ALL authorities if (!isGuestUser) - { - authorities.addAll(allSet); - } + { + authorities.addAll(allSet); + } else { authorities.addAll(guestSet); } - return authorities; - } - - public Set getAllAuthorities(AuthorityType type) - { - Set authorities = new HashSet(); - switch (type) - { - case ADMIN: - authorities.addAll(adminSet); - break; - case EVERYONE: - authorities.addAll(allSet); - break; - case GUEST: - authorities.addAll(guestSet); - break; - case GROUP: - authorities.addAll(authorityDAO.getAllAuthorities(type)); - break; - case OWNER: - break; - case ROLE: - authorities.addAll(authorityDAO.getAllAuthorities(type)); - break; - case USER: - for (NodeRef personRef : personService.getAllPeople()) - { - authorities.add(DefaultTypeConverter.INSTANCE.convert(String.class, nodeService.getProperty(personRef, - ContentModel.PROP_USERNAME))); - } - break; - default: - break; - } - return authorities; - } - - - - public Set findAuthorities(AuthorityType type, String namePattern) - { - return findAuthoritiesInZone(type, namePattern, null); - } - - - public Set findAuthoritiesByShortName(AuthorityType type, String shortNamePattern) - { - String fullNamePattern = getName(type, shortNamePattern); - return findAuthorities(type, fullNamePattern); - } - - public void addAuthority(String parentName, String childName) - { - authorityDAO.addAuthority(parentName, childName); - } - - private void checkTypeIsMutable(AuthorityType type) - { - if((type == AuthorityType.GROUP) || (type == AuthorityType.ROLE)) - { - return; - } - else - { - throw new AuthorityException("Trying to modify a fixed authority"); - } - } - - public String createAuthority(AuthorityType type, String shortName) - { - return createAuthority(type, shortName, shortName, getDefaultZones()); - } - - public void deleteAuthority(String name) - { - deleteAuthority(name, false); - } - - public void deleteAuthority(String name, boolean cascade) - { - AuthorityType type = AuthorityType.getAuthorityType(name); - checkTypeIsMutable(type); - if (cascade) - { - for (String child : getContainedAuthorities(type, name, true)) - { - deleteAuthority(child, true); - } - } - authorityDAO.deleteAuthority(name); - permissionServiceSPI.deletePermissions(name); - } - - public Set getAllRootAuthorities(AuthorityType type) - { - return authorityDAO.getAllRootAuthorities(type); - } - - public Set getContainedAuthorities(AuthorityType type, String name, boolean immediate) - { - return authorityDAO.getContainedAuthorities(type, name, immediate); - } - - public Set getContainingAuthorities(AuthorityType type, String name, boolean immediate) - { - return authorityDAO.getContainingAuthorities(type, name, immediate); - } - - public String getName(AuthorityType type, String shortName) - { - if (type.isFixedString()) - { - return type.getFixedString(); - } - else if (type.isPrefixed()) - { - return type.getPrefixString() + shortName; - } - else - { - return shortName; - } - } - - public String getShortName(String name) - { - AuthorityType type = AuthorityType.getAuthorityType(name); - if (type.isFixedString()) - { - return ""; - } - else if (type.isPrefixed()) - { - return name.substring(type.getPrefixString().length()); - } - else - { - return name; - } - - } - - public void removeAuthority(String parentName, String childName) - { - authorityDAO.removeAuthority(parentName, childName); - } - - public boolean authorityExists(String name) - { - return authorityDAO.authorityExists(name); - } - - public String createAuthority(AuthorityType type, String shortName, String authorityDisplayName, - Set authorityZones) - { - checkTypeIsMutable(type); - String name = getName(type, shortName); - authorityDAO.createAuthority(name, authorityDisplayName, authorityZones); - return name; - } - - public String getAuthorityDisplayName(String name) - { - String displayName = authorityDAO.getAuthorityDisplayName(name); - if(displayName == null) - { - displayName = getShortName(name); - } - return displayName; - } - - public void setAuthorityDisplayName(String authorityName, String authorityDisplayName) - { - AuthorityType type = AuthorityType.getAuthorityType(authorityName); - checkTypeIsMutable(type); - authorityDAO.setAuthorityDisplayName(authorityName, authorityDisplayName); - } - - public Set getAuthorityZones(String name) - { - return authorityDAO.getAuthorityZones(name); - } - - public NodeRef getOrCreateZone(String zoneName) - { - return authorityDAO.getOrCreateZone(zoneName); - } - - public NodeRef getZone(String zoneName) - { - return authorityDAO.getZone(zoneName); - } - - public Set getAllAuthoritiesInZone(String zoneName, AuthorityType type) - { - return authorityDAO.getAllAuthoritiesInZone(zoneName, type); - } - - public void addAuthorityToZones(String authorityName, Set zones) - { - authorityDAO.addAuthorityToZones(authorityName, zones); - - } - - public void removeAuthorityFromZones(String authorityName, Set zones) - { - authorityDAO.removeAuthorityFromZones(authorityName, zones); - } - - public Set getDefaultZones() - { - return DEFAULT_ZONES; - } - - public Set getAllRootAuthoritiesInZone(String zoneName, AuthorityType type) - { - return authorityDAO.getAllRootAuthoritiesInZone(zoneName, type); - } - - public Set findAuthoritiesByShortNameInZone(AuthorityType type, String shortNamePattern, String zone) - { - String fullNamePattern = getName(type, shortNamePattern); - return findAuthoritiesInZone(type, fullNamePattern, zone); - } - - public Set findAuthoritiesInZone(AuthorityType type, String namePattern, String zone) - { - Set authorities = new HashSet(); - switch (type) - { - case ADMIN: - case EVERYONE: - case GUEST: - throw new UnsupportedOperationException(); - case GROUP: - Set zones = null; - if(zone != null) - { - zones = Collections.singleton(zone); - } - authorities.addAll(authorityDAO.findAuthorities(type, namePattern, zones)); - break; - case OWNER: - case ROLE: - throw new UnsupportedOperationException(); - case USER: - throw new UnsupportedOperationException(); - default: - break; - } - return authorities; - } -} + return authorities; + } + + public Set getAllAuthorities(AuthorityType type) + { + Set authorities = new HashSet(); + switch (type) + { + case ADMIN: + authorities.addAll(adminSet); + break; + case EVERYONE: + authorities.addAll(allSet); + break; + case GUEST: + authorities.addAll(guestSet); + break; + case GROUP: + authorities.addAll(authorityDAO.getAllAuthorities(type)); + break; + case OWNER: + break; + case ROLE: + authorities.addAll(authorityDAO.getAllAuthorities(type)); + break; + case USER: + for (NodeRef personRef : personService.getAllPeople()) + { + authorities.add(DefaultTypeConverter.INSTANCE.convert(String.class, nodeService.getProperty(personRef, + ContentModel.PROP_USERNAME))); + } + break; + default: + break; + } + return authorities; + } + + + + public Set findAuthorities(AuthorityType type, String namePattern) + { + return findAuthoritiesInZone(type, namePattern, null); + } + + + public Set findAuthoritiesByShortName(AuthorityType type, String shortNamePattern) + { + String fullNamePattern = getName(type, shortNamePattern); + return findAuthorities(type, fullNamePattern); + } + + public void addAuthority(String parentName, String childName) + { + addAuthority(Collections.singleton(parentName), childName); + } + + public void addAuthority(Collection parentNames, String childName) + { + authorityDAO.addAuthority(parentNames, childName); + } + + private boolean containsMatch(Set names, String name) + { + String baseName = this.tenantService.getBaseNameUser(name); + if (this.tenantService.isEnabled()) + { + // note: for multi-tenancy, this currently relies on a naming convention which assumes that all tenant + // admins will have the same base name as the default non-tenant specific admin. Typically "admin" is the + // default required admin user, although, if for example "bob" is also listed as an admin then all + // tenant-specific bob's will also have admin authority + for (String candidate : names) + { + if (this.userNameMatcher.matches(candidate, name) + || this.userNameMatcher.matches(this.tenantService.getBaseNameUser(candidate), baseName)) + { + return true; + } + } + } + else + { + for (String candidate : names) + { + if (this.userNameMatcher.matches(candidate, name) || this.userNameMatcher.matches(candidate, baseName)) + { + return true; + } + } + } + return false; + } + + private void checkTypeIsMutable(AuthorityType type) + { + if((type == AuthorityType.GROUP) || (type == AuthorityType.ROLE)) + { + return; + } + else + { + throw new AuthorityException("Trying to modify a fixed authority"); + } + } + + public String createAuthority(AuthorityType type, String shortName) + { + return createAuthority(type, shortName, shortName, getDefaultZones()); + } + + public void deleteAuthority(String name) + { + deleteAuthority(name, false); + } + + public void deleteAuthority(String name, boolean cascade) + { + AuthorityType type = AuthorityType.getAuthorityType(name); + checkTypeIsMutable(type); + if (cascade) + { + for (String child : getContainedAuthorities(type, name, true)) + { + deleteAuthority(child, true); + } + } + authorityDAO.deleteAuthority(name); + permissionServiceSPI.deletePermissions(name); + } + + public Set getAllRootAuthorities(AuthorityType type) + { + return authorityDAO.getAllRootAuthorities(type); + } + + public Set getContainedAuthorities(AuthorityType type, String name, boolean immediate) + { + return authorityDAO.getContainedAuthorities(type, name, immediate); + } + + public Set getContainingAuthorities(AuthorityType type, String name, boolean immediate) + { + return authorityDAO.getContainingAuthorities(type, name, immediate); + } + + public String getName(AuthorityType type, String shortName) + { + if (type.isFixedString()) + { + return type.getFixedString(); + } + else if (type.isPrefixed()) + { + return type.getPrefixString() + shortName; + } + else + { + return shortName; + } + } + + public String getShortName(String name) + { + AuthorityType type = AuthorityType.getAuthorityType(name); + if (type.isFixedString()) + { + return ""; + } + else if (type.isPrefixed()) + { + return name.substring(type.getPrefixString().length()); + } + else + { + return name; + } + + } + + public void removeAuthority(String parentName, String childName) + { + authorityDAO.removeAuthority(parentName, childName); + } + + public boolean authorityExists(String name) + { + return authorityDAO.authorityExists(name); + } + + public String createAuthority(AuthorityType type, String shortName, String authorityDisplayName, + Set authorityZones) + { + checkTypeIsMutable(type); + String name = getName(type, shortName); + authorityDAO.createAuthority(name, authorityDisplayName, authorityZones); + return name; + } + + public String getAuthorityDisplayName(String name) + { + String displayName = authorityDAO.getAuthorityDisplayName(name); + if(displayName == null) + { + displayName = getShortName(name); + } + return displayName; + } + + public void setAuthorityDisplayName(String authorityName, String authorityDisplayName) + { + AuthorityType type = AuthorityType.getAuthorityType(authorityName); + checkTypeIsMutable(type); + authorityDAO.setAuthorityDisplayName(authorityName, authorityDisplayName); + } + + public Set getAuthorityZones(String name) + { + return authorityDAO.getAuthorityZones(name); + } + + public NodeRef getOrCreateZone(String zoneName) + { + return authorityDAO.getOrCreateZone(zoneName); + } + + public NodeRef getZone(String zoneName) + { + return authorityDAO.getZone(zoneName); + } + + public Set getAllAuthoritiesInZone(String zoneName, AuthorityType type) + { + return authorityDAO.getAllAuthoritiesInZone(zoneName, type); + } + + public void addAuthorityToZones(String authorityName, Set zones) + { + authorityDAO.addAuthorityToZones(authorityName, zones); + + } + + public void removeAuthorityFromZones(String authorityName, Set zones) + { + authorityDAO.removeAuthorityFromZones(authorityName, zones); + } + + public Set getDefaultZones() + { + return DEFAULT_ZONES; + } + + public Set getAllRootAuthoritiesInZone(String zoneName, AuthorityType type) + { + return authorityDAO.getAllRootAuthoritiesInZone(zoneName, type); + } + + public Set findAuthoritiesByShortNameInZone(AuthorityType type, String shortNamePattern, String zone) + { + String fullNamePattern = getName(type, shortNamePattern); + return findAuthoritiesInZone(type, fullNamePattern, zone); + } + + public Set findAuthoritiesInZone(AuthorityType type, String namePattern, String zone) + { + Set authorities = new HashSet(); + switch (type) + { + case ADMIN: + case EVERYONE: + case GUEST: + throw new UnsupportedOperationException(); + case GROUP: + Set zones = null; + if(zone != null) + { + zones = Collections.singleton(zone); + } + authorities.addAll(authorityDAO.findAuthorities(type, namePattern, zones)); + break; + case OWNER: + case ROLE: + throw new UnsupportedOperationException(); + case USER: + throw new UnsupportedOperationException(); + default: + break; + } + return authorities; + } +} diff --git a/source/java/org/alfresco/repo/security/authority/SimpleAuthorityServiceImpl.java b/source/java/org/alfresco/repo/security/authority/SimpleAuthorityServiceImpl.java index 55644e2a28..cc27f6f315 100644 --- a/source/java/org/alfresco/repo/security/authority/SimpleAuthorityServiceImpl.java +++ b/source/java/org/alfresco/repo/security/authority/SimpleAuthorityServiceImpl.java @@ -24,6 +24,7 @@ */ package org.alfresco.repo.security.authority; +import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.Set; @@ -204,7 +205,12 @@ public class SimpleAuthorityServiceImpl implements AuthorityService { } - + + public void addAuthority(Collection parentNames, String childName) + { + + } + public String createAuthority(AuthorityType type, String shortName) { return ""; diff --git a/source/java/org/alfresco/repo/security/sync/ChainingUserRegistrySynchronizer.java b/source/java/org/alfresco/repo/security/sync/ChainingUserRegistrySynchronizer.java index cc568a38c2..569393ddfa 100644 --- a/source/java/org/alfresco/repo/security/sync/ChainingUserRegistrySynchronizer.java +++ b/source/java/org/alfresco/repo/security/sync/ChainingUserRegistrySynchronizer.java @@ -25,12 +25,16 @@ package org.alfresco.repo.security.sync; import java.text.DateFormat; +import java.util.ArrayList; import java.util.Collection; import java.util.Collections; -import java.util.Comparator; import java.util.Date; import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; import java.util.Set; +import java.util.TreeMap; import java.util.TreeSet; import org.alfresco.model.ContentModel; @@ -55,7 +59,6 @@ import org.alfresco.service.cmr.security.PersonService; import org.alfresco.service.namespace.NamespaceService; import org.alfresco.service.namespace.QName; import org.alfresco.util.AbstractLifecycleBean; -import org.alfresco.util.Pair; import org.alfresco.util.PropertyMap; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -365,17 +368,7 @@ public class ChainingUserRegistrySynchronizer extends AbstractLifecycleBean impl boolean requiresNew = splitTxns || AlfrescoTransactionSupport.getTransactionReadState() == TxnReadState.TXN_READ_ONLY; - int personsProcessed = syncPersonsWithPlugin(id, plugin, requiresNew, visitedZoneIds, - allZoneIds); - int groupsProcessed = syncGroupsWithPlugin(id, plugin, force, requiresNew, visitedZoneIds, - allZoneIds); - if (ChainingUserRegistrySynchronizer.logger.isInfoEnabled()) - { - ChainingUserRegistrySynchronizer.logger - .info("Finished synchronizing users and groups with user registry '" + id + "'"); - ChainingUserRegistrySynchronizer.logger.info(personsProcessed + " user(s) and " - + groupsProcessed + " group(s) processed"); - } + syncWithPlugin(id, plugin, force, requiresNew, visitedZoneIds, allZoneIds); } } catch (NoSuchBeanDefinitionException e) @@ -431,29 +424,31 @@ public class ChainingUserRegistrySynchronizer extends AbstractLifecycleBean impl } /** - * Synchronizes changes only to local users (persons) with a {@link UserRegistry} for a particular zone. + * Synchronizes local groups and users with a {@link UserRegistry} for a particular zone, optionally handling + * deletions. * * @param zone - * the zone id. This identifier is used to tag all created users, so that in the future we can tell those - * that have been deleted from the registry. + * the zone id. This identifier is used to tag all created groups and users, so that in the future we can + * tell those that have been deleted from the registry. * @param userRegistry * the user registry for the zone. + * @param force + * true if user and group deletions are to be processed. * @param splitTxns * Can the modifications to Alfresco be split across multiple transactions for maximum performance? If - * true, users and groups are created/updated in batches of 10 for increased performance. If + * true, users and groups are created/updated in batches for increased performance. If * false, all users and groups are processed in the current transaction. This is required if * calling synchronously (e.g. in response to an authentication event in the same transaction). * @param visitedZoneIds * the set of zone ids already processed. These zones have precedence over the current zone when it comes - * to user name 'collisions'. If a user is queried that already exists locally but is tagged with one of - * the zones in this set, then it will be ignored as this zone has lower priority. + * to group name 'collisions'. If a user or group is queried that already exists locally but is tagged + * with one of the zones in this set, then it will be ignored as this zone has lower priority. * @param allZoneIds * the set of all zone ids in the authentication chain. Helps us work out whether the zone information - * recorded against a user is invalid for the current authentication chain and whether the user needs to - * be 're-zoned'. - * @return the number of users processed + * recorded against a user or group is invalid for the current authentication chain and whether the user + * or group needs to be 're-zoned'. */ - private int syncPersonsWithPlugin(final String zone, UserRegistry userRegistry, boolean splitTxns, + private void syncWithPlugin(final String zone, UserRegistry userRegistry, boolean force, boolean splitTxns, final Set visitedZoneIds, final Set allZoneIds) { // Create a prefixed zone ID for use with the authority service @@ -462,9 +457,314 @@ public class ChainingUserRegistrySynchronizer extends AbstractLifecycleBean impl // The set of zones we associate with new objects (default plus registry specific) final Set zoneSet = getZones(zoneId); - final long lastModifiedMillis = getMostRecentUpdateTime( - ChainingUserRegistrySynchronizer.PERSON_LAST_MODIFIED_ATTRIBUTE, zoneId); - final Date lastModified = lastModifiedMillis == -1 ? null : new Date(lastModifiedMillis); + long lastModifiedMillis = getMostRecentUpdateTime( + ChainingUserRegistrySynchronizer.GROUP_LAST_MODIFIED_ATTRIBUTE, zoneId); + Date lastModified = lastModifiedMillis == -1 ? null : new Date(lastModifiedMillis); + + if (ChainingUserRegistrySynchronizer.logger.isInfoEnabled()) + { + if (lastModified == null) + { + ChainingUserRegistrySynchronizer.logger.info("Retrieving all groups from user registry '" + zone + "'"); + } + else + { + ChainingUserRegistrySynchronizer.logger.info("Retrieving groups changed since " + + DateFormat.getDateTimeInstance().format(lastModified) + " from user registry '" + zone + "'"); + } + } + + // Get current set of known authorities + Set allZoneAuthorities = this.retryingTransactionHelper.doInTransaction( + new RetryingTransactionCallback>() + { + public Set execute() throws Throwable + { + return ChainingUserRegistrySynchronizer.this.authorityService.getAllAuthoritiesInZone(zoneId, + null); + } + }, false, splitTxns); + + // First, analyze the group structure. Create maps of authorities to their parents for associations to create + // and delete. Also deal with 'overlaps' with other zones in the authentication chain. + final BatchProcessor groupProcessor = new BatchProcessor( + this.retryingTransactionHelper, this.applicationEventPublisher, userRegistry.getGroups(lastModified), + zone + " Group Analysis", this.loggingInterval, this.workerThreads, 20); + class Analyzer implements Worker + { + private final Set allZoneAuthorities; + private final Set groupsToCreate = new TreeSet(); + private final Map> groupAssocsToCreate = new TreeMap>(); + private final Map> groupAssocsToDelete = new TreeMap>(); + private long latestTime; + + public Analyzer(final Set allZoneAuthorities, final long latestTime) + { + this.allZoneAuthorities = allZoneAuthorities; + this.latestTime = latestTime; + } + + public long getLatestTime() + { + return this.latestTime; + } + + public Set getGroupsToCreate() + { + return this.groupsToCreate; + } + + public Map> getGroupAssocsToCreate() + { + return this.groupAssocsToCreate; + } + + public Map> getGroupAssocsToDelete() + { + return this.groupAssocsToDelete; + } + + /* + * (non-Javadoc) + * @see org.alfresco.repo.security.sync.BatchProcessor.Worker#getIdentifier(java.lang.Object) + */ + public String getIdentifier(NodeDescription entry) + { + return entry.getSourceId(); + } + + /* + * (non-Javadoc) + * @see org.alfresco.repo.security.sync.BatchProcessor.Worker#process(java.lang.Object) + */ + public void process(NodeDescription group) throws Throwable + { + PropertyMap groupProperties = group.getProperties(); + String groupName = (String) groupProperties.get(ContentModel.PROP_AUTHORITY_NAME); + String groupShortName = ChainingUserRegistrySynchronizer.this.authorityService.getShortName(groupName); + Set groupZones = ChainingUserRegistrySynchronizer.this.authorityService + .getAuthorityZones(groupName); + + if (groupZones == null) + { + // The group did not exist at all + addAssociations(groupName, group.getChildAssociations(), false); + } + else + { + // Check whether the group is in any of the authentication chain zones + Set intersection = new TreeSet(groupZones); + intersection.retainAll(allZoneIds); + if (intersection.isEmpty()) + { + // The group exists, but not in a zone that's in the authentication chain. May be due to + // upgrade or zone changes. Let's re-zone them + if (ChainingUserRegistrySynchronizer.logger.isWarnEnabled()) + { + ChainingUserRegistrySynchronizer.logger.warn("Updating group '" + groupShortName + + "'. This group will in future be assumed to originate from user registry '" + + zone + "'."); + } + ChainingUserRegistrySynchronizer.this.authorityService.removeAuthorityFromZones(groupName, + groupZones); + ChainingUserRegistrySynchronizer.this.authorityService.addAuthorityToZones(groupName, zoneSet); + } + if (groupZones.contains(zoneId) || intersection.isEmpty()) + { + // The group already existed in this zone or no valid zone: update the group + updateAssociations(group, groupName); + } + else + { + // Check whether the group is in any of the higher priority authentication chain zones + intersection.retainAll(visitedZoneIds); + if (!intersection.isEmpty()) + { + // A group that exists in a different zone with higher precedence + return; + } + // The group existed, but in a zone with lower precedence + if (ChainingUserRegistrySynchronizer.logger.isWarnEnabled()) + { + ChainingUserRegistrySynchronizer.logger + .warn("Recreating occluded group '" + + groupShortName + + "'. This group was previously created through synchronization with a lower priority user registry."); + } + ChainingUserRegistrySynchronizer.this.authorityService.deleteAuthority(groupName); + // create the group + addAssociations(groupName, group.getChildAssociations(), false); + } + } + + synchronized (this) + { + // Maintain the last modified date + Date groupLastModified = group.getLastModified(); + if (groupLastModified != null) + { + this.latestTime = Math.max(this.latestTime, groupLastModified.getTime()); + } + } + } + + private synchronized void updateAssociations(NodeDescription group, String groupName) + { + Set oldChildren = ChainingUserRegistrySynchronizer.this.authorityService + .getContainedAuthorities(null, groupName, true); + Set newChildren = group.getChildAssociations(); + Set toDelete = new TreeSet(oldChildren); + Set toAdd = new TreeSet(newChildren); + toDelete.removeAll(newChildren); + toAdd.removeAll(oldChildren); + addAssociations(groupName, toAdd, true); + deleteAssociations(groupName, toDelete); + } + + private synchronized void addAssociations(String groupName, Set children, boolean existed) + { + this.allZoneAuthorities.add(groupName); + if (!existed) + { + this.groupsToCreate.add(groupName); + } + // Add an entry for the parent itself, in case it is a root group + Set parents = this.groupAssocsToCreate.get(groupName); + if (parents == null) + { + parents = new TreeSet(); + this.groupAssocsToCreate.put(groupName, parents); + } + for (String child : children) + { + parents = this.groupAssocsToCreate.get(child); + if (parents == null) + { + parents = new TreeSet(); + this.groupAssocsToCreate.put(child, parents); + } + parents.add(groupName); + } + } + + private synchronized void deleteAssociations(String groupName, Set children) + { + for (String child : children) + { + // Make sure each child features as a key in the creation map + addAssociations(child, Collections. emptySet(), true); + + Set parents = this.groupAssocsToDelete.get(child); + if (parents == null) + { + parents = new TreeSet(); + this.groupAssocsToDelete.put(child, parents); + } + parents.add(groupName); + } + } + } + + Analyzer groupAnalyzer = new Analyzer(allZoneAuthorities, lastModifiedMillis); + int groupProcessedCount = groupProcessor.process(groupAnalyzer, splitTxns); + final Map> groupAssocsToCreate = groupAnalyzer.getGroupAssocsToCreate(); + final Map> groupAssocsToDelete = groupAnalyzer.getGroupAssocsToDelete(); + + // Prune our set of authorities according to deletions + Set deletionCandidates = null; + if (force) + { + deletionCandidates = new TreeSet(allZoneAuthorities); + userRegistry.processDeletions(deletionCandidates); + allZoneAuthorities.removeAll(deletionCandidates); + groupAssocsToCreate.keySet().removeAll(deletionCandidates); + groupAssocsToDelete.keySet().removeAll(deletionCandidates); + } + + // Sort the group associations in depth-first order (root groups first) + Map> sortedGroupAssociations = new LinkedHashMap>(groupAssocsToCreate + .size() * 2); + List authorityPath = new ArrayList(5); + for (String authority : groupAssocsToCreate.keySet()) + { + if (allZoneAuthorities.contains(authority)) + { + authorityPath.add(authority); + visitGroupAssociations(authorityPath, allZoneAuthorities, groupAssocsToCreate, sortedGroupAssociations); + authorityPath.clear(); + } + } + + // Add the groups and their parent associations in depth-first order + final Set groupsToCreate = groupAnalyzer.getGroupsToCreate(); + BatchProcessor>> groupCreator = new BatchProcessor>>( + this.retryingTransactionHelper, this.applicationEventPublisher, sortedGroupAssociations.entrySet(), + zone + " Group Creation and Association", this.loggingInterval, 1, 20); + groupCreator.process(new Worker>>() + { + + public String getIdentifier(Map.Entry> entry) + { + return entry.getKey() + " " + entry.getValue(); + } + + public void process(Map.Entry> entry) throws Throwable + { + Set parents = entry.getValue(); + String child = entry.getKey(); + + if (groupsToCreate.contains(child)) + { + String groupShortName = ChainingUserRegistrySynchronizer.this.authorityService.getShortName(child); + if (ChainingUserRegistrySynchronizer.logger.isDebugEnabled()) + { + ChainingUserRegistrySynchronizer.logger.debug("Creating group '" + groupShortName + "'"); + } + // create the group + ChainingUserRegistrySynchronizer.this.authorityService.createAuthority(AuthorityType + .getAuthorityType(child), groupShortName, groupShortName, zoneSet); + } + if (!parents.isEmpty()) + { + if (ChainingUserRegistrySynchronizer.logger.isDebugEnabled()) + { + for (String groupName : parents) + { + ChainingUserRegistrySynchronizer.logger.debug("Adding '" + + ChainingUserRegistrySynchronizer.this.authorityService.getShortName(child) + + "' to group '" + + ChainingUserRegistrySynchronizer.this.authorityService.getShortName(groupName) + + "'"); + } + } + ChainingUserRegistrySynchronizer.this.authorityService.addAuthority(parents, child); + } + Set parentsToDelete = groupAssocsToDelete.get(child); + if (parentsToDelete != null && !parentsToDelete.isEmpty()) + { + for (String parent : parentsToDelete) + { + if (ChainingUserRegistrySynchronizer.logger.isDebugEnabled()) + { + ChainingUserRegistrySynchronizer.logger + .debug("Removing '" + + ChainingUserRegistrySynchronizer.this.authorityService + .getShortName(child) + + "' from group '" + + ChainingUserRegistrySynchronizer.this.authorityService + .getShortName(parent) + "'"); + } + ChainingUserRegistrySynchronizer.this.authorityService.removeAuthority(parent, child); + } + } + } + }, splitTxns); + + // Process persons and their parent associations + + lastModifiedMillis = getMostRecentUpdateTime(ChainingUserRegistrySynchronizer.PERSON_LAST_MODIFIED_ATTRIBUTE, + zoneId); + lastModified = lastModifiedMillis == -1 ? null : new Date(lastModifiedMillis); if (ChainingUserRegistrySynchronizer.logger.isInfoEnabled()) { if (lastModified == null) @@ -479,10 +779,15 @@ public class ChainingUserRegistrySynchronizer extends AbstractLifecycleBean impl } final BatchProcessor personProcessor = new BatchProcessor( this.retryingTransactionHelper, this.applicationEventPublisher, userRegistry.getPersons(lastModified), - zone + " User Creation", this.loggingInterval, this.workerThreads, 10); - class CreationWorker implements Worker + zone + " User Creation and Association", this.loggingInterval, this.workerThreads, 10); + class PersonWorker implements Worker { - private long latestTime = lastModifiedMillis; + private long latestTime; + + public PersonWorker(final long latestTime) + { + this.latestTime = latestTime; + } public long getLatestTime() { @@ -562,6 +867,42 @@ public class ChainingUserRegistrySynchronizer extends AbstractLifecycleBean impl ChainingUserRegistrySynchronizer.this.personService.createPerson(personProperties, zoneSet); } } + // Maintain associations + Set parents = groupAssocsToCreate.get(personName); + if (parents != null && !parents.isEmpty()) + { + if (ChainingUserRegistrySynchronizer.logger.isDebugEnabled()) + { + for (String groupName : parents) + { + ChainingUserRegistrySynchronizer.logger.debug("Adding '" + + ChainingUserRegistrySynchronizer.this.authorityService.getShortName(personName) + + "' to group '" + + ChainingUserRegistrySynchronizer.this.authorityService.getShortName(groupName) + + "'"); + } + } + ChainingUserRegistrySynchronizer.this.authorityService.addAuthority(parents, personName); + } + Set parentsToDelete = groupAssocsToDelete.get(personName); + if (parentsToDelete != null && !parentsToDelete.isEmpty()) + { + for (String parent : parentsToDelete) + { + if (ChainingUserRegistrySynchronizer.logger.isDebugEnabled()) + { + ChainingUserRegistrySynchronizer.logger + .debug("Removing '" + + ChainingUserRegistrySynchronizer.this.authorityService + .getShortName(personName) + + "' from group '" + + ChainingUserRegistrySynchronizer.this.authorityService + .getShortName(parent) + "'"); + } + ChainingUserRegistrySynchronizer.this.authorityService.removeAuthority(parent, personName); + } + } + synchronized (this) { // Maintain the last modified date @@ -574,293 +915,22 @@ public class ChainingUserRegistrySynchronizer extends AbstractLifecycleBean impl } } - CreationWorker creations = new CreationWorker(); - int processedCount = personProcessor.process(creations, splitTxns); - long latestTime = creations.getLatestTime(); - if (latestTime != -1) - { - setMostRecentUpdateTime(ChainingUserRegistrySynchronizer.PERSON_LAST_MODIFIED_ATTRIBUTE, zoneId, - latestTime, splitTxns); - } + PersonWorker persons = new PersonWorker(lastModifiedMillis); + int personProcessedCount = personProcessor.process(persons, splitTxns); - // Remember we have visited this zone - visitedZoneIds.add(zoneId); - - return processedCount; - } - - /** - * Synchronizes local groups with a {@link UserRegistry} for a particular zone and also handles deletions of local - * groups and users. - * - * @param zone - * the zone id. This identifier is used to tag all created groups, so that in the future we can tell - * those that have been deleted from the registry. - * @param userRegistry - * the user registry for the zone. - * @param force - * true if user and group deletions are to be processed. - * @param splitTxns - * Can the modifications to Alfresco be split across multiple transactions for maximum performance? If - * true, users and groups are created/updated in batches of 10 for increased performance. If - * false, all users and groups are processed in the current transaction. This is required if - * calling synchronously (e.g. in response to an authentication event in the same transaction). - * @param visitedZoneIds - * the set of zone ids already processed. These zones have precedence over the current zone when it comes - * to group name 'collisions'. If a group is queried that already exists locally but is tagged with one - * of the zones in this set, then it will be ignored as this zone has lower priority. - * @param allZoneIds - * the set of all zone ids in the authentication chain. Helps us work out whether the zone information - * recorded against a group is invalid for the current authentication chain and whether the group needs - * to be 're-zoned'. - * @return the number of groups processed - */ - private int syncGroupsWithPlugin(final String zone, UserRegistry userRegistry, boolean force, boolean splitTxns, - final Set visitedZoneIds, final Set allZoneIds) - { - // Create a prefixed zone ID for use with the authority service - final String zoneId = AuthorityService.ZONE_AUTH_EXT_PREFIX + zone; - - // The set of zones we associate with new objects (default plus registry specific) - final Set zoneSet = getZones(zoneId); - - final long lastModifiedMillis = getMostRecentUpdateTime( - ChainingUserRegistrySynchronizer.GROUP_LAST_MODIFIED_ATTRIBUTE, zoneId); - final Date lastModified = lastModifiedMillis == -1 ? null : new Date(lastModifiedMillis); - - if (ChainingUserRegistrySynchronizer.logger.isInfoEnabled()) - { - if (lastModified == null) - { - ChainingUserRegistrySynchronizer.logger.info("Retrieving all groups from user registry '" + zone + "'"); - } - else - { - ChainingUserRegistrySynchronizer.logger.info("Retrieving groups changed since " - + DateFormat.getDateTimeInstance().format(lastModified) + " from user registry '" + zone + "'"); - } - } - - // Get current set of known authorities - Set deletionCandidates = this.retryingTransactionHelper.doInTransaction( - new RetryingTransactionCallback>() - { - public Set execute() throws Throwable - { - return ChainingUserRegistrySynchronizer.this.authorityService.getAllAuthoritiesInZone(zoneId, - null); - } - }, false, splitTxns); - - final BatchProcessor groupProcessor = new BatchProcessor( - this.retryingTransactionHelper, this.applicationEventPublisher, userRegistry.getGroups(lastModified, - deletionCandidates, force), zone + " Group Creation", this.loggingInterval, this.workerThreads, - 20); - class CreationWorker implements Worker - { - private final Set> groupAssocsToCreate = new TreeSet>( - new Comparator>() - { - - public int compare(Pair o1, Pair o2) - { - int result = o1.getFirst().compareTo(o2.getFirst()); - if (result == 0) - { - return o1.getSecond().compareTo(o2.getSecond()); - } - return result; - } - }); - private long latestTime = lastModifiedMillis; - - public long getLatestTime() - { - return this.latestTime; - } - - public Set> getGroupAssocsToCreate() - { - return this.groupAssocsToCreate; - } - - /* - * (non-Javadoc) - * @see org.alfresco.repo.security.sync.BatchProcessor.Worker#getIdentifier(java.lang.Object) - */ - public String getIdentifier(NodeDescription entry) - { - return entry.getSourceId(); - } - - /* - * (non-Javadoc) - * @see org.alfresco.repo.security.sync.BatchProcessor.Worker#process(java.lang.Object) - */ - public void process(NodeDescription group) throws Throwable - { - PropertyMap groupProperties = group.getProperties(); - String groupName = (String) groupProperties.get(ContentModel.PROP_AUTHORITY_NAME); - String groupShortName = ChainingUserRegistrySynchronizer.this.authorityService.getShortName(groupName); - Set groupZones = ChainingUserRegistrySynchronizer.this.authorityService - .getAuthorityZones(groupName); - - if (groupZones == null) - { - // The group did not exist at all - if (ChainingUserRegistrySynchronizer.logger.isDebugEnabled()) - { - ChainingUserRegistrySynchronizer.logger.debug("Creating group '" + groupShortName + "'"); - } - // create the group - ChainingUserRegistrySynchronizer.this.authorityService.createAuthority(AuthorityType - .getAuthorityType(groupName), groupShortName, (String) groupProperties - .get(ContentModel.PROP_AUTHORITY_DISPLAY_NAME), zoneSet); - Set children = group.getChildAssociations(); - if (!children.isEmpty()) - { - synchronized (this) - { - for (String child : children) - { - this.groupAssocsToCreate.add(new Pair(groupName, child)); - } - } - } - } - else - { - // Check whether the group is in any of the authentication chain zones - Set intersection = new TreeSet(groupZones); - intersection.retainAll(allZoneIds); - if (intersection.isEmpty()) - { - // The group exists, but not in a zone that's in the authentication chain. May be due to - // upgrade or zone changes. Let's re-zone them - if (ChainingUserRegistrySynchronizer.logger.isWarnEnabled()) - { - ChainingUserRegistrySynchronizer.logger.warn("Updating group '" + groupShortName - + "'. This group will in future be assumed to originate from user registry '" - + zone + "'."); - } - ChainingUserRegistrySynchronizer.this.authorityService.removeAuthorityFromZones(groupName, - groupZones); - ChainingUserRegistrySynchronizer.this.authorityService.addAuthorityToZones(groupName, zoneSet); - } - if (groupZones.contains(zoneId) || intersection.isEmpty()) - { - // The group already existed in this zone or no valid zone: update the group - Set oldChildren = ChainingUserRegistrySynchronizer.this.authorityService - .getContainedAuthorities(null, groupName, true); - Set newChildren = group.getChildAssociations(); - Set toDelete = new TreeSet(oldChildren); - Set toAdd = new TreeSet(newChildren); - toDelete.removeAll(newChildren); - toAdd.removeAll(oldChildren); - if (!toAdd.isEmpty()) - { - synchronized (this) - { - for (String child : toAdd) - { - this.groupAssocsToCreate.add(new Pair(groupName, child)); - } - } - } - for (String child : toDelete) - { - if (ChainingUserRegistrySynchronizer.logger.isDebugEnabled()) - { - ChainingUserRegistrySynchronizer.logger.debug("Removing '" - + ChainingUserRegistrySynchronizer.this.authorityService.getShortName(child) - + "' from group '" + groupShortName + "'"); - } - ChainingUserRegistrySynchronizer.this.authorityService.removeAuthority(groupName, child); - } - } - else - { - // Check whether the group is in any of the higher priority authentication chain zones - intersection.retainAll(visitedZoneIds); - if (!intersection.isEmpty()) - { - // A group that exists in a different zone with higher precedence - return; - } - // The group existed, but in a zone with lower precedence - if (ChainingUserRegistrySynchronizer.logger.isWarnEnabled()) - { - ChainingUserRegistrySynchronizer.logger - .warn("Recreating occluded group '" - + groupShortName - + "'. This group was previously created through synchronization with a lower priority user registry."); - } - ChainingUserRegistrySynchronizer.this.authorityService.deleteAuthority(groupName); - // create the group - ChainingUserRegistrySynchronizer.this.authorityService.createAuthority(AuthorityType - .getAuthorityType(groupName), groupShortName, (String) groupProperties - .get(ContentModel.PROP_AUTHORITY_DISPLAY_NAME), zoneSet); - Set children = group.getChildAssociations(); - if (!children.isEmpty()) - { - synchronized (this) - { - for (String child : children) - { - this.groupAssocsToCreate.add(new Pair(groupName, child)); - } - } - } - } - } - - synchronized (this) - { - // Maintain the last modified date - Date groupLastModified = group.getLastModified(); - if (groupLastModified != null) - { - this.latestTime = Math.max(this.latestTime, groupLastModified.getTime()); - } - } - } - } - - CreationWorker creations = new CreationWorker(); - int processedCount = groupProcessor.process(creations, splitTxns); - long latestTime = creations.getLatestTime(); + // Only now that the whole tree has been processed is it safe to persist the last modified dates + long latestTime = groupAnalyzer.getLatestTime(); if (latestTime != -1) { setMostRecentUpdateTime(ChainingUserRegistrySynchronizer.GROUP_LAST_MODIFIED_ATTRIBUTE, zoneId, latestTime, splitTxns); } - - // Add the new associations, now that we have created everything - BatchProcessor> groupAssocProcessor = new BatchProcessor>( - this.retryingTransactionHelper, this.applicationEventPublisher, creations.getGroupAssocsToCreate(), - zone + " Group Association Creation", this.loggingInterval, this.workerThreads, 20); - groupAssocProcessor.process(new Worker>() + latestTime = persons.getLatestTime(); + if (latestTime != -1) { - - public String getIdentifier(Pair entry) - { - return entry.toString(); - } - - public void process(Pair entry) throws Throwable - { - String groupName = entry.getFirst(); - String child = entry.getSecond(); - if (ChainingUserRegistrySynchronizer.logger.isDebugEnabled()) - { - ChainingUserRegistrySynchronizer.logger.debug("Adding '" - + ChainingUserRegistrySynchronizer.this.authorityService.getShortName(child) - + "' to group '" - + ChainingUserRegistrySynchronizer.this.authorityService.getShortName(groupName) + "'"); - } - ChainingUserRegistrySynchronizer.this.authorityService.addAuthority(groupName, child); - } - }, splitTxns); + setMostRecentUpdateTime(ChainingUserRegistrySynchronizer.PERSON_LAST_MODIFIED_ATTRIBUTE, zoneId, + latestTime, splitTxns); + } // Delete authorities if we have complete information for the zone if (force) @@ -868,8 +938,20 @@ public class ChainingUserRegistrySynchronizer extends AbstractLifecycleBean impl BatchProcessor authorityDeletionProcessor = new BatchProcessor( this.retryingTransactionHelper, this.applicationEventPublisher, deletionCandidates, zone + " Authority Deletion", this.loggingInterval, this.workerThreads, 10); - processedCount += authorityDeletionProcessor.process(new Worker() + class AuthorityDeleter implements Worker { + private int personProcessedCount; + private int groupProcessedCount; + + public int getPersonProcessedCount() + { + return this.personProcessedCount; + } + + public int getGroupProcessedCount() + { + return this.groupProcessedCount; + } public String getIdentifier(String entry) { @@ -885,6 +967,10 @@ public class ChainingUserRegistrySynchronizer extends AbstractLifecycleBean impl ChainingUserRegistrySynchronizer.logger.debug("Deleting user '" + authority + "'"); } ChainingUserRegistrySynchronizer.this.personService.deletePerson(authority); + synchronized (this) + { + this.personProcessedCount++; + } } else { @@ -895,15 +981,76 @@ public class ChainingUserRegistrySynchronizer extends AbstractLifecycleBean impl + "'"); } ChainingUserRegistrySynchronizer.this.authorityService.deleteAuthority(authority); + synchronized (this) + { + this.groupProcessedCount++; + } } } - }, splitTxns); + } + AuthorityDeleter authorityDeleter = new AuthorityDeleter(); + authorityDeletionProcessor.process(authorityDeleter, splitTxns); + groupProcessedCount += authorityDeleter.getGroupProcessedCount(); + personProcessedCount += authorityDeleter.getPersonProcessedCount(); } // Remember we have visited this zone visitedZoneIds.add(zoneId); - return processedCount; + if (ChainingUserRegistrySynchronizer.logger.isInfoEnabled()) + { + ChainingUserRegistrySynchronizer.logger.info("Finished synchronizing users and groups with user registry '" + + zone + "'"); + ChainingUserRegistrySynchronizer.logger.info(personProcessedCount + " user(s) and " + groupProcessedCount + + " group(s) processed"); + } + } + + /** + * Visits the last authority in the given list by recursively visiting its parents in associationsOld and then + * adding the authority to associationsNew. Used to sort associationsOld into 'depth-first' order. + * + * @param authorityPath + * The authority to visit, preceeded by all its descendants. Allows detection of cyclic child + * associations. + * @param allAuthorities + * the set of all known authorities + * @param associationsOld + * the association map to sort + * @param associationsNew + * the association map to add to in depth first order + */ + private void visitGroupAssociations(List authorityPath, Set allAuthorities, + Map> associationsOld, Map> associationsNew) + { + String authorityName = authorityPath.get(authorityPath.size() - 1); + if (!associationsNew.containsKey(authorityName)) + { + Set associations = associationsOld.get(authorityName); + + if (!associations.isEmpty()) + { + // Filter out associations to unknown parent authorities + associations.retainAll(allAuthorities); + int insertIndex = authorityPath.size(); + for (String parentAuthority : associations) + { + // Prevent cyclic paths + if (!authorityPath.contains(parentAuthority)) + { + authorityPath.add(parentAuthority); + visitGroupAssociations(authorityPath, allAuthorities, associationsOld, associationsNew); + authorityPath.remove(insertIndex); + } + } + } + + // Omit associations from users from this map, as they will be processed separately + if (AuthorityType.getAuthorityType(authorityName) != AuthorityType.USER) + { + associationsNew.put(authorityName, associations); + } + } } /** diff --git a/source/java/org/alfresco/repo/security/sync/ChainingUserRegistrySynchronizerTest.java b/source/java/org/alfresco/repo/security/sync/ChainingUserRegistrySynchronizerTest.java index 4657d483d8..0d4940ba14 100644 --- a/source/java/org/alfresco/repo/security/sync/ChainingUserRegistrySynchronizerTest.java +++ b/source/java/org/alfresco/repo/security/sync/ChainingUserRegistrySynchronizerTest.java @@ -631,25 +631,29 @@ public class ChainingUserRegistrySynchronizerTest extends TestCase { return this.zoneId; } + + - /* - * (non-Javadoc) - * @see org.alfresco.repo.security.sync.UserRegistry#getGroups(java.util.Date, java.util.Set, boolean) + /* (non-Javadoc) + * @see org.alfresco.repo.security.sync.UserRegistry#processDeletions(java.util.Set) */ - public Collection getGroups(Date modifiedSince, Set candidateAuthoritiesForDeletion, - boolean prune) + public void processDeletions(Set candidateAuthoritiesForDeletion) { - if (prune) + for (NodeDescription person : this.persons) { - for (NodeDescription person : this.persons) - { - candidateAuthoritiesForDeletion.remove(person.getProperties().get(ContentModel.PROP_USERNAME)); - } - for (NodeDescription group : this.groups) - { - candidateAuthoritiesForDeletion.remove(group.getProperties().get(ContentModel.PROP_AUTHORITY_NAME)); - } + candidateAuthoritiesForDeletion.remove(person.getProperties().get(ContentModel.PROP_USERNAME)); } + for (NodeDescription group : this.groups) + { + candidateAuthoritiesForDeletion.remove(group.getProperties().get(ContentModel.PROP_AUTHORITY_NAME)); + } + } + + /* (non-Javadoc) + * @see org.alfresco.repo.security.sync.UserRegistry#getGroups(java.util.Date) + */ + public Collection getGroups(Date modifiedSince) + { return this.groups; } diff --git a/source/java/org/alfresco/repo/security/sync/UserRegistry.java b/source/java/org/alfresco/repo/security/sync/UserRegistry.java index f45fb649c9..58da7b888c 100644 --- a/source/java/org/alfresco/repo/security/sync/UserRegistry.java +++ b/source/java/org/alfresco/repo/security/sync/UserRegistry.java @@ -50,22 +50,23 @@ public interface UserRegistry public Collection getPersons(Date modifiedSince); /** - * Gets descriptions of all the groups in the user registry or all those changed since a certain date. Group - * associations should be restricted to those in the given set of known authorities. Optionally this set is 'pruned' - * to contain only those authorities that no longer exist in the user registry, i.e. the deletion candidates. + * Gets descriptions of all the groups in the user registry or all those changed since a certain date. * * @param modifiedSince * if non-null, then only descriptions of groups modified since this date should be returned; if * null then descriptions of all groups should be returned. - * @param knownAuthorities - * the current set of known authorities - * @param prune - * should this set be 'pruned' so that it contains only those authorities that do not exist in the - * registry, i.e. the deletion candidates? * @return a {@link Collection} of {@link NodeDescription}s of all the groups in the user registry or all those * changed since a certain date. The description properties should correspond to those of an Alfresco * authority node. */ - public Collection getGroups(Date modifiedSince, Set knownAuthorities, boolean prune); + public Collection getGroups(Date modifiedSince); + /** + * Retrieves the complete set of known users and groups from the user registry and removes them from the set of + * candidate local authorities to be deleted. + * + * @param candidateAuthoritiesForDeletion + * the candidate authorities for deletion + */ + public void processDeletions(final Set candidateAuthoritiesForDeletion); } diff --git a/source/java/org/alfresco/repo/security/sync/ldap/LDAPUserRegistry.java b/source/java/org/alfresco/repo/security/sync/ldap/LDAPUserRegistry.java index 86e4180c31..7f65dc2397 100644 --- a/source/java/org/alfresco/repo/security/sync/ldap/LDAPUserRegistry.java +++ b/source/java/org/alfresco/repo/security/sync/ldap/LDAPUserRegistry.java @@ -480,14 +480,11 @@ public class LDAPUserRegistry implements UserRegistry, LDAPNameResolver, Initial return new PersonCollection(modifiedSince); } - /** - * Retrieves the complete set of known users and groups from the LDAP directory and removes them from the set of - * candidate local authorities to be deleted. - * - * @param candidateAuthoritiesForDeletion - * the candidate authorities for deletion + /* + * (non-Javadoc) + * @see org.alfresco.repo.security.sync.UserRegistry#processDeletions(java.util.Set) */ - private void processDeletions(final Set candidateAuthoritiesForDeletion) + public void processDeletions(final Set candidateAuthoritiesForDeletion) { processQuery(new SearchCallback() { @@ -562,20 +559,8 @@ public class LDAPUserRegistry implements UserRegistry, LDAPNameResolver, Initial * (non-Javadoc) * @see org.alfresco.repo.security.sync.UserRegistry#getGroups(java.util.Date) */ - public Collection getGroups(Date modifiedSince, final Set candidateAuthoritiesForDeletion, - boolean prune) + public Collection getGroups(Date modifiedSince) { - // Take the given set of authorities as a starting point for the set of all authorities - final Set allAuthorities = new TreeSet(candidateAuthoritiesForDeletion); - - // If required, work out what authority deletions are required by pruning down the deletion set and the set of - // all authorities - if (prune) - { - processDeletions(candidateAuthoritiesForDeletion); - allAuthorities.removeAll(candidateAuthoritiesForDeletion); - } - // Work out whether the user and group trees are disjoint. This may allow us to optimize reverse DN // resolution. final LdapName groupDistinguishedNamePrefix; @@ -636,7 +621,6 @@ public class LDAPUserRegistry implements UserRegistry, LDAPNameResolver, Initial group = new NodeDescription(result.getNameInNamespace()); group.getProperties().put(ContentModel.PROP_AUTHORITY_NAME, gid); lookup.put(gid, group); - allAuthorities.add(gid); } else if (LDAPUserRegistry.this.errorOnDuplicateGID) { @@ -799,12 +783,6 @@ public class LDAPUserRegistry implements UserRegistry, LDAPNameResolver, Initial LDAPUserRegistry.logger.debug("Found " + lookup.size()); } - // Post-process the group associations to filter out those that point to excluded users or groups (now that we - // know the full set of groups) - for (NodeDescription group : lookup.values()) - { - group.getChildAssociations().retainAll(allAuthorities); - } return lookup.values(); } diff --git a/source/java/org/alfresco/repo/version/NodeServiceImpl.java b/source/java/org/alfresco/repo/version/NodeServiceImpl.java index 7139c57d4c..24b194f08b 100644 --- a/source/java/org/alfresco/repo/version/NodeServiceImpl.java +++ b/source/java/org/alfresco/repo/version/NodeServiceImpl.java @@ -234,6 +234,18 @@ public class NodeServiceImpl implements NodeService, VersionModel throw new UnsupportedOperationException(MSG_UNSUPPORTED); } + /** + * @throws UnsupportedOperationException always + */ + public List addChild(Collection parentRefs, + NodeRef childRef, + QName assocTypeQName, + QName qname) throws InvalidNodeRefException + { + // This operation is not supported for a version store + throw new UnsupportedOperationException(MSG_UNSUPPORTED); + } + /** * @throws UnsupportedOperationException always */ diff --git a/source/java/org/alfresco/service/cmr/repository/NodeService.java b/source/java/org/alfresco/service/cmr/repository/NodeService.java index d22704a3a0..d003ae02fd 100644 --- a/source/java/org/alfresco/service/cmr/repository/NodeService.java +++ b/source/java/org/alfresco/service/cmr/repository/NodeService.java @@ -324,6 +324,26 @@ public interface NodeService QName assocTypeQName, QName qname) throws InvalidNodeRefException; + /** + * Associates a given child node with a given collection of parents. All nodes must belong to the same store. + *

+ * + * + * @param parentRefs + * @param childRef + * @param assocTypeQName the qualified name of the association type as defined in the datadictionary + * @param qname the qualified name of the association + * @return Returns a reference to the newly created child association + * @throws InvalidNodeRefException if the parent or child nodes could not be found + * @throws CyclicChildRelationshipException if the child partakes in a cyclic relationship after the add + */ + @Auditable(key = Auditable.Key.ARG_0 ,parameters = {"parentRefs", "childRef", "assocTypeQName", "qname"}) + public List addChild( + Collection parentRefs, + NodeRef childRef, + QName assocTypeQName, + QName qname) throws InvalidNodeRefException; + /** * Severs all parent-child relationships between two nodes. *

diff --git a/source/java/org/alfresco/service/cmr/security/AuthorityService.java b/source/java/org/alfresco/service/cmr/security/AuthorityService.java index 6fd74fa9cb..6442841087 100644 --- a/source/java/org/alfresco/service/cmr/security/AuthorityService.java +++ b/source/java/org/alfresco/service/cmr/security/AuthorityService.java @@ -24,6 +24,7 @@ */ package org.alfresco.service.cmr.security; +import java.util.Collection; import java.util.Set; import org.alfresco.service.Auditable; @@ -215,6 +216,18 @@ public interface AuthorityService @Auditable(parameters = {"parentName", "childName"}) public void addAuthority(String parentName, String childName); + /** + * Set a given child authority to be included by the given parent authorities. For example, adding a + * group to groups or adding a user to groups. + * + * @param parentNames - + * the full name string identifier for the parents. + * @param childName - + * the string identifier for the child. + */ + @Auditable(parameters = {"parentNames", "childName"}) + public void addAuthority(Collection parentNames, String childName); + /** * Remove an authority as a member of another authority. The child authority * will still exist. If the child authority was not created as a root