diff --git a/config/alfresco/authority-services-context.xml b/config/alfresco/authority-services-context.xml
index fb6bd7301d..c0681b3d91 100644
--- a/config/alfresco/authority-services-context.xml
+++ b/config/alfresco/authority-services-context.xml
@@ -101,6 +101,7 @@
+
diff --git a/config/alfresco/repository.properties b/config/alfresco/repository.properties
index 4816a96d0e..46363484ad 100644
--- a/config/alfresco/repository.properties
+++ b/config/alfresco/repository.properties
@@ -971,6 +971,7 @@ trashcan.MaxSize=1000
# Use bridge tables for caching authority evaluation.
#
authority.useBridgeTable=true
+authority.zoneAuthoritySampleSize=10000
# enable QuickShare - if false then the QuickShare-specific REST APIs will return 403 Forbidden
system.quickshare.enabled=true
diff --git a/source/java/org/alfresco/repo/security/authority/AuthorityDAOImpl.java b/source/java/org/alfresco/repo/security/authority/AuthorityDAOImpl.java
index 5e908cb4d9..63ea58e8bd 100644
--- a/source/java/org/alfresco/repo/security/authority/AuthorityDAOImpl.java
+++ b/source/java/org/alfresco/repo/security/authority/AuthorityDAOImpl.java
@@ -145,7 +145,11 @@ public class AuthorityDAOImpl implements AuthorityDAO, NodeServicePolicies.Befor
super();
}
-
+ public int getZoneAuthoritySampleSize()
+ {
+ return zoneAuthoritySampleSize;
+ }
+
/**
* Sets number of authorities in a zone to pre-cache, allowing quick generation of 'first n' results and adaption of
* search technique based on hit rate.
@@ -532,7 +536,7 @@ public class AuthorityDAOImpl implements AuthorityDAO, NodeServicePolicies.Befor
}
final PagingResults ppr = personService.getPeople(filter, true, sort, pagingRequest);
-
+
List result = ppr.getPage();
final List auths = new ArrayList(result.size());
@@ -911,7 +915,9 @@ public class AuthorityDAOImpl implements AuthorityDAO, NodeServicePolicies.Befor
}, tenantService.getDomainUser(AuthenticationUtil.getSystemUserName(), currentUserDomain));
zoneAuthorityCache.put(cacheKey, zoneAuthorities);
}
-
+
+ int numAuthorities = zoneAuthorities.size();
+
// Now search each for the required authority. If the number of results is greater than or close to the size
// limit, then this will be the most efficient route
Set result = new TreeSet();
@@ -950,7 +956,8 @@ public class AuthorityDAOImpl implements AuthorityDAO, NodeServicePolicies.Befor
}
// If this top down search is not providing an adequate hit count then resort to a naiive unlimited search
- if (processed >= maxToProcess)
+ // we need to compare to the actual number of authorities cached or newly fetched from the database
+ if (processed >= numAuthorities)
{
Set unfilteredResult;
boolean filterZone;
diff --git a/source/test-java/org/alfresco/repo/security/authority/AuthorityServiceTest.java b/source/test-java/org/alfresco/repo/security/authority/AuthorityServiceTest.java
index 8051b848bf..78e63bdf87 100644
--- a/source/test-java/org/alfresco/repo/security/authority/AuthorityServiceTest.java
+++ b/source/test-java/org/alfresco/repo/security/authority/AuthorityServiceTest.java
@@ -19,10 +19,12 @@
package org.alfresco.repo.security.authority;
import java.io.Serializable;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
+import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -31,13 +33,14 @@ import javax.transaction.Status;
import javax.transaction.UserTransaction;
import junit.framework.TestCase;
-
import net.sf.acegisecurity.AuthenticationCredentialsNotFoundException;
import org.alfresco.error.AlfrescoRuntimeException;
import org.alfresco.model.ContentModel;
+import org.alfresco.query.CannedQueryPageDetails;
import org.alfresco.query.PagingRequest;
import org.alfresco.query.PagingResults;
+import org.alfresco.repo.cache.SimpleCache;
import org.alfresco.repo.domain.permissions.AclDAO;
import org.alfresco.repo.node.NodeServicePolicies;
import org.alfresco.repo.node.archive.NodeArchiveService;
@@ -46,8 +49,11 @@ import org.alfresco.repo.policy.PolicyComponent;
import org.alfresco.repo.security.authentication.AuthenticationComponent;
import org.alfresco.repo.security.authentication.AuthenticationUtil;
import org.alfresco.repo.security.authentication.MutableAuthenticationDao;
+import org.alfresco.repo.site.SiteMembership;
+import org.alfresco.repo.site.SiteModel;
import org.alfresco.repo.transaction.AlfrescoTransactionSupport;
import org.alfresco.repo.transaction.AlfrescoTransactionSupport.TxnReadState;
+import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
import org.alfresco.service.ServiceRegistry;
import org.alfresco.service.cmr.repository.ChildAssociationRef;
import org.alfresco.service.cmr.repository.DuplicateChildNodeNameException;
@@ -60,13 +66,18 @@ import org.alfresco.service.cmr.security.AuthorityType;
import org.alfresco.service.cmr.security.MutableAuthenticationService;
import org.alfresco.service.cmr.security.PermissionService;
import org.alfresco.service.cmr.security.PersonService;
+import org.alfresco.service.cmr.site.SiteInfo;
import org.alfresco.service.cmr.site.SiteService;
+import org.alfresco.service.cmr.site.SiteVisibility;
import org.alfresco.service.namespace.NamespaceService;
import org.alfresco.service.namespace.QName;
import org.alfresco.service.namespace.RegexQNamePattern;
import org.alfresco.service.transaction.TransactionService;
import org.alfresco.test_category.OwnJVMTestsCategory;
import org.alfresco.util.ApplicationContextHelper;
+import org.alfresco.util.GUID;
+import org.alfresco.util.Pair;
+import org.alfresco.util.PropertyMap;
import org.junit.experimental.categories.Category;
import org.springframework.context.ApplicationContext;
@@ -81,6 +92,8 @@ public class AuthorityServiceTest extends TestCase
private AuthorityService authorityService;
private AuthorityService pubAuthorityService;
private PersonService personService;
+ private AuthorityDAOImpl authorityDAO;
+ private SiteService siteService;
private UserTransaction tx;
private AclDAO aclDaoComponent;
private NodeService nodeService;
@@ -88,7 +101,9 @@ public class AuthorityServiceTest extends TestCase
private NodeArchiveService nodeArchiveService;
private PolicyComponent policyComponent;
private TransactionService transactionService;
-
+
+ private SimpleCache, List> zoneToAuthorityCache;
+
public AuthorityServiceTest()
{
super();
@@ -103,6 +118,7 @@ public class AuthorityServiceTest extends TestCase
private int GRP_CNT = 0;
private int ROOT_GRP_CNT = 0;
+ @SuppressWarnings("unchecked")
public void setUp() throws Exception
{
if (AlfrescoTransactionSupport.getTransactionReadState() != TxnReadState.TXN_NONE)
@@ -117,14 +133,17 @@ public class AuthorityServiceTest extends TestCase
authorityService = (AuthorityService) ctx.getBean("authorityService");
pubAuthorityService = (AuthorityService) ctx.getBean("AuthorityService");
personService = (PersonService) ctx.getBean("personService");
+ siteService = (SiteService) ctx.getBean("siteService");
authenticationDAO = (MutableAuthenticationDao) ctx.getBean("authenticationDao");
+ authorityDAO = (AuthorityDAOImpl) ctx.getBean("authorityDAO");
aclDaoComponent = (AclDAO) ctx.getBean("aclDAO");
nodeService = (NodeService) ctx.getBean("nodeService");
authorityBridgeTableCache = (AuthorityBridgeTableAsynchronouslyRefreshedCache) ctx.getBean("authorityBridgeTableCache");
nodeArchiveService = (NodeArchiveService) ctx.getBean("nodeArchiveService");
policyComponent = (PolicyComponent) ctx.getBean("policyComponent");
transactionService = (TransactionService) ctx.getBean(ServiceRegistry.TRANSACTION_SERVICE.getLocalName());
-
+ zoneToAuthorityCache = (SimpleCache, List>) ctx.getBean("zoneToAuthorityCache");
+
String defaultAdminUser = AuthenticationUtil.getAdminUserName();
AuthenticationUtil.setFullyAuthenticatedUser(defaultAdminUser);
@@ -134,6 +153,7 @@ public class AuthorityServiceTest extends TestCase
// note: currently depends on any existing (and/or bootstrap) group data - eg. default site "swsdp" (Sample Web Site Design Project)
SiteService siteService = (SiteService) ctx.getBean("SiteService");
SITE_CNT = siteService.listSites(defaultAdminUser).size();
+
GRP_CNT = DEFAULT_GRP_CNT + (DEFAULT_SITE_GRP_CNT * SITE_CNT);
ROOT_GRP_CNT = DEFAULT_GRP_CNT + (DEFAULT_SITE_ROOT_GRP_CNT * SITE_CNT);
@@ -1504,4 +1524,121 @@ public class AuthorityServiceTest extends TestCase
{
return pubAuthorityService.getAuthorities(type, null, null, false, true, new PagingRequest(0, Integer.MAX_VALUE, null)).getPage();
}
+
+ protected void createUser(String userName, String password)
+ {
+ if (authenticationService.authenticationExists(userName) == false)
+ {
+ authenticationService.createAuthentication(userName, password.toCharArray());
+
+ PropertyMap ppOne = new PropertyMap(4);
+ ppOne.put(ContentModel.PROP_USERNAME, userName);
+ ppOne.put(ContentModel.PROP_FIRSTNAME, "firstName");
+ ppOne.put(ContentModel.PROP_LASTNAME, "lastName");
+ ppOne.put(ContentModel.PROP_EMAIL, "email@email.com");
+ ppOne.put(ContentModel.PROP_JOBTITLE, "jobTitle");
+
+ personService.createPerson(ppOne);
+ }
+ }
+
+ public void testMNT12849()
+ {
+ AuthenticationUtil.pushAuthentication();
+ AuthenticationUtil.setAdminUserAsFullyAuthenticatedUser();
+
+ // intentionally low zone authority sample size
+ int saveZoneAuthoritySampleSize = authorityDAO.getZoneAuthoritySampleSize();
+ authorityDAO.setZoneAuthoritySampleSize(2);
+
+ try
+ {
+ // create user
+ final String userName = GUID.generate();
+ transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback()
+ {
+ @Override
+ public Void execute() throws Throwable
+ {
+ createUser(userName, "password");
+ return null;
+ }
+ }, false, true);
+
+ final Set zones = new HashSet();
+ zones.add(AuthorityService.ZONE_APP_SHARE);
+
+ final List siteInfos = new LinkedList<>();
+
+ // create some sites with the user as member
+ transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback()
+ {
+ @Override
+ public Void execute() throws Throwable
+ {
+ for(int i = 0; i < 5; i++)
+ {
+ String siteName = GUID.generate();
+ SiteInfo siteInfo = siteService.createSite("", siteName, siteName, siteName, SiteVisibility.PUBLIC);
+ siteInfos.add(siteInfo);
+
+ siteService.setMembership(siteName, userName, SiteModel.SITE_COLLABORATOR);
+ }
+
+ return null;
+ }
+ }, false, true);
+
+ AuthenticationUtil.popAuthentication();
+ AuthenticationUtil.pushAuthentication();
+ AuthenticationUtil.setFullyAuthenticatedUser(userName);
+
+ final String authority = AuthenticationUtil.getFullyAuthenticatedUser();
+ final int maxResults = 1; // intentionally less than the authority.zoneAuthoritySampleSize
+
+ transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback()
+ {
+ @Override
+ public Void execute() throws Throwable
+ {
+ // get sites - size is intentionally less than the authority.zoneAuthoritySampleSize
+ // this is used by the legacy sites REST api
+ siteService.listSites(userName, 2);
+ return null;
+ }
+ }, false, true);
+
+ transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback()
+ {
+ @Override
+ public Void execute() throws Throwable
+ {
+ int numSiteMemberships = 5;
+
+ final List> sort = new ArrayList>();
+ sort.add(new Pair(SiteService.SortFields.SiteTitle, Boolean.TRUE));
+ sort.add(new Pair(SiteService.SortFields.Role, Boolean.TRUE));
+
+ PagingRequest pagingRequest = new PagingRequest(0, numSiteMemberships);
+ pagingRequest.setRequestTotalCountMax(CannedQueryPageDetails.DEFAULT_PAGE_SIZE);
+
+ // this is used by the public api sites REST api
+ PagingResults results = siteService.listSitesPaged(authority, sort, pagingRequest);
+ List siteMemberships = results.getPage();
+
+ assertEquals("Unexpected number of site memberships", numSiteMemberships, siteMemberships.size());
+
+ return null;
+ }
+ }, false, true);
+
+ AuthenticationUtil.popAuthentication();
+ }
+ finally
+ {
+ Pair cacheKey = new Pair("", AuthorityService.ZONE_APP_SHARE);
+ zoneToAuthorityCache.remove(cacheKey);
+ authorityDAO.setZoneAuthoritySampleSize(saveZoneAuthoritySampleSize);
+ }
+ }
}