diff --git a/repository/src/main/java/org/alfresco/repo/search/impl/lucene/PagingLuceneResultSet.java b/repository/src/main/java/org/alfresco/repo/search/impl/lucene/PagingLuceneResultSet.java index afaba6d182..5b50aa4f47 100644 --- a/repository/src/main/java/org/alfresco/repo/search/impl/lucene/PagingLuceneResultSet.java +++ b/repository/src/main/java/org/alfresco/repo/search/impl/lucene/PagingLuceneResultSet.java @@ -298,8 +298,8 @@ public class PagingLuceneResultSet implements ResultSet, Serializable return wrapped.getSpellCheckResult(); } - public void setTrimmedResultSet(boolean b) + public void setTrimmedResultSet(boolean value) { - this.trimmedResultSet = true; + this.trimmedResultSet = value; } } diff --git a/repository/src/main/java/org/alfresco/repo/search/impl/querymodel/impl/db/DBQueryEngine.java b/repository/src/main/java/org/alfresco/repo/search/impl/querymodel/impl/db/DBQueryEngine.java index 68797d0bbf..dbb829085a 100644 --- a/repository/src/main/java/org/alfresco/repo/search/impl/querymodel/impl/db/DBQueryEngine.java +++ b/repository/src/main/java/org/alfresco/repo/search/impl/querymodel/impl/db/DBQueryEngine.java @@ -27,7 +27,6 @@ package org.alfresco.repo.search.impl.querymodel.impl.db; import static org.alfresco.repo.domain.node.AbstractNodeDAOImpl.CACHE_REGION_NODES; import static org.alfresco.repo.search.impl.querymodel.impl.db.DBStats.aclOwnerStopWatch; -import static org.alfresco.repo.search.impl.querymodel.impl.db.DBStats.aclReadStopWatch; import static org.alfresco.repo.search.impl.querymodel.impl.db.DBStats.handlerStopWatch; import static org.alfresco.repo.search.impl.querymodel.impl.db.DBStats.resetStopwatches; @@ -113,7 +112,7 @@ public class DBQueryEngine implements QueryEngine private OptionalPatchApplicationCheckBootstrapBean metadataIndexCheck2; - private PermissionService permissionService; + PermissionService permissionService; private int maxPermissionChecks; @@ -125,7 +124,7 @@ public class DBQueryEngine implements QueryEngine private SimpleCache> aspectsCache; - private AclCrudDAO aclCrudDAO; + AclCrudDAO aclCrudDAO; public void setAclCrudDAO(AclCrudDAO aclCrudDAO) { @@ -331,7 +330,8 @@ public class DBQueryEngine implements QueryEngine NodePermissionAssessor createAssessor(QueryOptions options) { - NodePermissionAssessor permissionAssessor = new NodePermissionAssessor(); + Authority authority = aclCrudDAO.getAuthority(AuthenticationUtil.getRunAsUser()); + NodePermissionAssessor permissionAssessor = new NodePermissionAssessor(nodeService, permissionService, authority, nodesCache); int maxPermsChecks = options.getMaxPermissionChecks() < 0 ? maxPermissionChecks : options.getMaxPermissionChecks(); long maxPermCheckTimeMillis = options.getMaxPermissionCheckTimeMillis() < 0 ? maxPermissionCheckTimeMillis @@ -485,104 +485,6 @@ public class DBQueryEngine implements QueryEngine return new DBQueryModelFactory(); } - public class NodePermissionAssessor - { - private final boolean isAdminReading; - - private final Authority authority; - - private final Map aclReadCache = new HashMap<>(); - - private int checksPerformed; - - private long startTime; - - private int maxPermissionChecks; - - private long maxPermissionCheckTimeMillis; - - public NodePermissionAssessor() - { - this.checksPerformed = 0; - this.maxPermissionChecks = Integer.MAX_VALUE; - this.maxPermissionCheckTimeMillis = Long.MAX_VALUE; - - Set authorisations = permissionService.getAuthorisations(); - this.isAdminReading = authorisations.contains(AuthenticationUtil.getAdminRoleName()); - - authority = aclCrudDAO.getAuthority(AuthenticationUtil.getRunAsUser()); - } - - public boolean isIncluded(Node node) - { - if (isFirstRecord()) - { - this.startTime = System.currentTimeMillis(); - } - - checksPerformed++; - return isReallyIncluded(node); - } - - public boolean isFirstRecord() - { - return checksPerformed == 0; - } - - boolean isReallyIncluded(Node node) - { - return isAdminReading || - canRead(node.getAclId()) || - isOwnerReading(node, authority); - } - - public void setMaxPermissionChecks(int maxPermissionChecks) - { - this.maxPermissionChecks = maxPermissionChecks; - } - - public boolean shouldQuitChecks() - { - boolean result = false; - - if (checksPerformed >= maxPermissionChecks) - { - result = true; - } - - if ((System.currentTimeMillis() - startTime) >= maxPermissionCheckTimeMillis) - { - result = true; - } - - return result; - } - - public void setMaxPermissionCheckTimeMillis(long maxPermissionCheckTimeMillis) - { - this.maxPermissionCheckTimeMillis = maxPermissionCheckTimeMillis; - } - - boolean canRead(Long aclId) - { - aclReadStopWatch().start(); - try - { - Boolean res = aclReadCache.get(aclId); - if (res == null) - { - res = canCurrentUserRead(aclId); - aclReadCache.put(aclId, res); - } - return res; - } - finally - { - aclReadStopWatch().stop(); - } - } - } - protected boolean canCurrentUserRead(Long aclId) { // cache resolved ACLs diff --git a/repository/src/main/java/org/alfresco/repo/search/impl/querymodel/impl/db/NodePermissionAssessor.java b/repository/src/main/java/org/alfresco/repo/search/impl/querymodel/impl/db/NodePermissionAssessor.java new file mode 100644 index 0000000000..6323b76c29 --- /dev/null +++ b/repository/src/main/java/org/alfresco/repo/search/impl/querymodel/impl/db/NodePermissionAssessor.java @@ -0,0 +1,221 @@ +/* + * #%L + * Alfresco Repository + * %% + * Copyright (C) 2005 - 2021 Alfresco Software Limited + * %% + * This file is part of the Alfresco software. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + * #L% + */ +package org.alfresco.repo.search.impl.querymodel.impl.db; + +import static org.alfresco.repo.search.impl.querymodel.impl.db.DBStats.aclOwnerStopWatch; +import static org.alfresco.repo.search.impl.querymodel.impl.db.DBStats.aclReadStopWatch; + +import java.io.Serializable; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +import org.alfresco.model.ContentModel; +import org.alfresco.repo.cache.lookup.EntityLookupCache; +import org.alfresco.repo.domain.node.Node; +import org.alfresco.repo.domain.permissions.Authority; +import org.alfresco.repo.security.authentication.AuthenticationUtil; +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.PermissionService; +import org.alfresco.service.namespace.QName; +import org.alfresco.util.EqualsHelper; + +public class NodePermissionAssessor +{ + private final boolean isSystemReading; + private final boolean isAdminReading; + private final boolean isNullReading; + private final Authority authority; + private final Map aclReadCache = new HashMap<>(); + private int checksPerformed; + private long startTime; + private int maxPermissionChecks; + private long maxPermissionCheckTimeMillis; + + private EntityLookupCache nodesCache; + private NodeService nodeService; + private PermissionService permissionService; + + public NodePermissionAssessor(NodeService nodeService, PermissionService permissionService, + Authority authority, EntityLookupCache nodeCache) + { + this.permissionService = permissionService; + this.nodesCache = nodeCache; + this.nodeService = nodeService; + + this.checksPerformed = 0; + this.maxPermissionChecks = Integer.MAX_VALUE; + this.maxPermissionCheckTimeMillis = Long.MAX_VALUE; + + Set authorisations = permissionService.getAuthorisations(); + this.isSystemReading = AuthenticationUtil.isRunAsUserTheSystemUser(); + this.isAdminReading = authorisations.contains(AuthenticationUtil.getAdminRoleName()); + this.isNullReading = AuthenticationUtil.getRunAsUser() == null; + + this.authority = authority; + } + + public boolean isIncluded(Node node) + { + if (isFirstRecord()) + { + this.startTime = System.currentTimeMillis(); + } + + checksPerformed++; + return isReallyIncluded(node); + } + + public boolean isFirstRecord() + { + return checksPerformed == 0; + } + + protected boolean isOwnerReading(Node node, Authority authority) + { + aclOwnerStopWatch().start(); + try + { + if (authority == null) + { + return false; + } + + String owner = getOwner(node); + return EqualsHelper.nullSafeEquals(authority.getAuthority(), owner); + } + finally + { + aclOwnerStopWatch().stop(); + } + } + + private String getOwner(Node node) + { + nodesCache.setValue(node.getId(), node); + Set nodeAspects = nodeService.getAspects(node.getNodeRef()); + + String userName = null; + if (nodeAspects.contains(ContentModel.ASPECT_AUDITABLE)) + { + userName = node.getAuditableProperties().getAuditCreator(); + } + else if (nodeAspects.contains(ContentModel.ASPECT_OWNABLE)) + { + Serializable owner = nodeService.getProperty(node.getNodeRef(), ContentModel.PROP_OWNER); + userName = DefaultTypeConverter.INSTANCE.convert(String.class, owner); + } + + return userName; + } + + boolean isReallyIncluded(Node node) + { + if (isNullReading) + { + return false; + } + + return isSystemReading || + isAdminReading || + canRead(node.getAclId()) || + isOwnerReading(node, authority); + } + + public void setMaxPermissionChecks(int maxPermissionChecks) + { + this.maxPermissionChecks = maxPermissionChecks; + } + + public boolean shouldQuitChecks() + { + boolean result = false; + + if (checksPerformed >= maxPermissionChecks) + { + result = true; + } + + if ((System.currentTimeMillis() - startTime) >= maxPermissionCheckTimeMillis) + { + result = true; + } + + return result; + } + + public void setMaxPermissionCheckTimeMillis(long maxPermissionCheckTimeMillis) + { + this.maxPermissionCheckTimeMillis = maxPermissionCheckTimeMillis; + } + + protected boolean canRead(Long aclId) + { + aclReadStopWatch().start(); + try + { + Boolean res = aclReadCache.get(aclId); + if (res == null) + { + res = canCurrentUserRead(aclId); + aclReadCache.put(aclId, res); + } + return res; + } + finally + { + aclReadStopWatch().stop(); + } + } + + protected boolean canCurrentUserRead(Long aclId) + { + // cache resolved ACLs + Set authorities = permissionService.getAuthorisations(); + + Set aclReadersDenied = permissionService.getReadersDenied(aclId); + for (String auth : aclReadersDenied) + { + if (authorities.contains(auth)) + { + return false; + } + } + + Set aclReaders = permissionService.getReaders(aclId); + for (String auth : aclReaders) + { + if (authorities.contains(auth)) + { + return true; + } + } + + return false; + } +} \ No newline at end of file diff --git a/repository/src/test/java/org/alfresco/repo/search/impl/querymodel/impl/db/DBQueryEngineTest.java b/repository/src/test/java/org/alfresco/repo/search/impl/querymodel/impl/db/DBQueryEngineTest.java index e51e4ac4f9..1804642027 100644 --- a/repository/src/test/java/org/alfresco/repo/search/impl/querymodel/impl/db/DBQueryEngineTest.java +++ b/repository/src/test/java/org/alfresco/repo/search/impl/querymodel/impl/db/DBQueryEngineTest.java @@ -48,7 +48,6 @@ import org.alfresco.repo.domain.node.Node; import org.alfresco.repo.domain.node.NodeVersionKey; import org.alfresco.repo.domain.node.StoreEntity; import org.alfresco.repo.search.impl.querymodel.QueryOptions; -import org.alfresco.repo.search.impl.querymodel.impl.db.DBQueryEngine.NodePermissionAssessor; import org.alfresco.repo.security.permissions.impl.acegi.FilteringResultSet; import org.alfresco.service.cmr.search.ResultSet; import org.alfresco.service.cmr.search.SearchParameters; diff --git a/repository/src/test/java/org/alfresco/repo/search/impl/querymodel/impl/db/NodePermissionAssessorTest.java b/repository/src/test/java/org/alfresco/repo/search/impl/querymodel/impl/db/NodePermissionAssessorLimitsTest.java similarity index 86% rename from repository/src/test/java/org/alfresco/repo/search/impl/querymodel/impl/db/NodePermissionAssessorTest.java rename to repository/src/test/java/org/alfresco/repo/search/impl/querymodel/impl/db/NodePermissionAssessorLimitsTest.java index 74658a49aa..6f1dc6dcb7 100644 --- a/repository/src/test/java/org/alfresco/repo/search/impl/querymodel/impl/db/NodePermissionAssessorTest.java +++ b/repository/src/test/java/org/alfresco/repo/search/impl/querymodel/impl/db/NodePermissionAssessorLimitsTest.java @@ -35,14 +35,17 @@ import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +import org.alfresco.repo.cache.lookup.EntityLookupCache; import org.alfresco.repo.domain.node.Node; import org.alfresco.repo.domain.permissions.AclCrudDAO; -import org.alfresco.repo.search.impl.querymodel.impl.db.DBQueryEngine.NodePermissionAssessor; +import org.alfresco.repo.domain.permissions.Authority; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; import org.alfresco.service.cmr.security.PermissionService; import org.junit.Before; import org.junit.Test; -public class NodePermissionAssessorTest +public class NodePermissionAssessorLimitsTest { private NodePermissionAssessor assessor; private Node node; @@ -118,7 +121,10 @@ public class NodePermissionAssessorTest engine.setPermissionService(permissionService); engine.setAclCrudDAO(aclCrudDAO); - NodePermissionAssessor assessor = spy(engine.new NodePermissionAssessor()); + NodeService nodeService = mock(NodeService.class); + Authority authority = mock(Authority.class); + EntityLookupCache nodeCache = mock(EntityLookupCache.class); + NodePermissionAssessor assessor = spy(new NodePermissionAssessor(nodeService, permissionService, authority, nodeCache)); doReturn(true).when(assessor).isReallyIncluded(any(Node.class)); return assessor; } diff --git a/repository/src/test/java/org/alfresco/repo/search/impl/querymodel/impl/db/NodePermissionAssessorPermissionsTest.java b/repository/src/test/java/org/alfresco/repo/search/impl/querymodel/impl/db/NodePermissionAssessorPermissionsTest.java new file mode 100644 index 0000000000..f412aa4cb2 --- /dev/null +++ b/repository/src/test/java/org/alfresco/repo/search/impl/querymodel/impl/db/NodePermissionAssessorPermissionsTest.java @@ -0,0 +1,101 @@ +/* + * #%L + * Alfresco Repository + * %% + * Copyright (C) 2005 - 2021 Alfresco Software Limited + * %% + * This file is part of the Alfresco software. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + * #L% + */ +package org.alfresco.repo.search.impl.querymodel.impl.db; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; + +import java.util.Set; + +import org.alfresco.repo.cache.lookup.EntityLookupCache; +import org.alfresco.repo.domain.node.Node; +import org.alfresco.repo.domain.permissions.Authority; +import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.security.PermissionService; +import org.junit.Before; +import org.junit.Test; + +public class NodePermissionAssessorPermissionsTest +{ + private PermissionService permissionService; + + @Before + public void setup() + { + AuthenticationUtil.clearCurrentSecurityContext(); + permissionService = mock(PermissionService.class); + DBStats.resetStopwatches(); + } + + @Test + public void shouldGrantPermissionWhenSystemIsReading() + { + // setup + AuthenticationUtil.setRunAsUserSystem(); + + Node theNode = mock(Node.class); + NodePermissionAssessor assessor = createAssessor(); + when(assessor.isOwnerReading(any(Node.class), any(Authority.class))).thenReturn(false); + when(permissionService.getReaders(anyLong())).thenReturn(Set.of()); + + // call the assessor + boolean included = assessor.isIncluded(theNode); + + // the node is included + assertTrue(included); + } + + @Test + public void shouldDenyPermissionWhenNullUserIsReading() + { + // setup - AuthenticationUtil.getRunAsUser() will return null + Node theNode = mock(Node.class); + NodePermissionAssessor assessor = createAssessor(); + when(assessor.isOwnerReading(any(Node.class), any(Authority.class))).thenReturn(false); + when(permissionService.getReaders(anyLong())).thenReturn(Set.of()); + + // call the assessor + boolean included = assessor.isIncluded(theNode); + + // the node is included + assertFalse(included); + } + + private NodePermissionAssessor createAssessor() + { + NodeService nodeService = mock(NodeService.class); + Authority authority = mock(Authority.class); + EntityLookupCache nodeCache = mock(EntityLookupCache.class); + return spy(new NodePermissionAssessor(nodeService, permissionService, authority, nodeCache)); + } +}