Improvements to cold permission performance and caching.

git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@5247 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
This commit is contained in:
Andrew Hind
2007-03-01 15:45:00 +00:00
parent 00cb31a85a
commit 7e28341bf0
2 changed files with 164 additions and 92 deletions

View File

@@ -20,7 +20,7 @@
* and Open Source Software ("FLOSS") applications as described in Alfresco's * and Open Source Software ("FLOSS") applications as described in Alfresco's
* FLOSS exception. You should have recieved a copy of the text describing * FLOSS exception. You should have recieved a copy of the text describing
* the FLOSS exception, and it is also available here: * the FLOSS exception, and it is also available here:
* http://www.alfresco.com/legal/licensing" * http://www.alfresco.com/legal/licensing
*/ */
package org.alfresco.repo.security.permissions.impl; package org.alfresco.repo.security.permissions.impl;
@@ -61,23 +61,21 @@ import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.InitializingBean;
/** /**
* The Alfresco implementation of a permissions service against our APIs for the * The Alfresco implementation of a permissions service against our APIs for the permissions model and permissions persistence.
* permissions model and permissions persistence.
* *
* @author andyh * @author andyh
*/ */
public class PermissionServiceImpl implements PermissionServiceSPI, InitializingBean public class PermissionServiceImpl implements PermissionServiceSPI, InitializingBean
{ {
static SimplePermissionReference OLD_ALL_PERMISSIONS_REFERENCE = new SimplePermissionReference( static SimplePermissionReference OLD_ALL_PERMISSIONS_REFERENCE = new SimplePermissionReference(QName.createQName(
QName.createQName("", PermissionService.ALL_PERMISSIONS), "", PermissionService.ALL_PERMISSIONS), PermissionService.ALL_PERMISSIONS);
PermissionService.ALL_PERMISSIONS);
private static Log log = LogFactory.getLog(PermissionServiceImpl.class); private static Log log = LogFactory.getLog(PermissionServiceImpl.class);
/** a transactionally-safe cache to be injected */ /** a transactionally-safe cache to be injected */
private SimpleCache<Serializable, AccessStatus> accessCache; private SimpleCache<Serializable, AccessStatus> accessCache;
/* /*
* Access to the model * Access to the model
*/ */
@@ -107,7 +105,7 @@ public class PermissionServiceImpl implements PermissionServiceSPI, Initializing
* Access to the authority component * Access to the authority component
*/ */
private AuthorityService authorityService; private AuthorityService authorityService;
/* /*
* Dynamic authorities providers * Dynamic authorities providers
*/ */
@@ -151,7 +149,7 @@ public class PermissionServiceImpl implements PermissionServiceSPI, Initializing
{ {
this.authenticationComponent = authenticationComponent; this.authenticationComponent = authenticationComponent;
} }
public void setAuthorityService(AuthorityService authorityService) public void setAuthorityService(AuthorityService authorityService)
{ {
this.authorityService = authorityService; this.authorityService = authorityService;
@@ -177,12 +175,12 @@ public class PermissionServiceImpl implements PermissionServiceSPI, Initializing
{ {
this.policyComponent = policyComponent; this.policyComponent = policyComponent;
} }
public void onMoveNode(ChildAssociationRef oldChildAssocRef, ChildAssociationRef newChildAssocRef) public void onMoveNode(ChildAssociationRef oldChildAssocRef, ChildAssociationRef newChildAssocRef)
{ {
accessCache.clear(); accessCache.clear();
} }
public void afterPropertiesSet() throws Exception public void afterPropertiesSet() throws Exception
{ {
if (dictionaryService == null) if (dictionaryService == null)
@@ -205,9 +203,9 @@ public class PermissionServiceImpl implements PermissionServiceSPI, Initializing
{ {
throw new IllegalArgumentException("Property 'authenticationComponent' has not been set"); throw new IllegalArgumentException("Property 'authenticationComponent' has not been set");
} }
if(authorityService == null) if (authorityService == null)
{ {
throw new IllegalArgumentException("Property 'authorityService' has not been set"); throw new IllegalArgumentException("Property 'authorityService' has not been set");
} }
if (accessCache == null) if (accessCache == null)
{ {
@@ -217,9 +215,10 @@ public class PermissionServiceImpl implements PermissionServiceSPI, Initializing
{ {
throw new IllegalArgumentException("Property 'policyComponent' has not been set"); throw new IllegalArgumentException("Property 'policyComponent' has not been set");
} }
policyComponent.bindClassBehaviour(QName.createQName(NamespaceService.ALFRESCO_URI, "onMoveNode"), ContentModel.TYPE_BASE, new JavaBehaviour(this, "onMoveNode")); policyComponent.bindClassBehaviour(QName.createQName(NamespaceService.ALFRESCO_URI, "onMoveNode"),
ContentModel.TYPE_BASE, new JavaBehaviour(this, "onMoveNode"));
} }
// //
@@ -316,12 +315,11 @@ public class PermissionServiceImpl implements PermissionServiceSPI, Initializing
{ {
return authorityType; return authorityType;
} }
@Override @Override
public String toString() public String toString()
{ {
return accessStatus + " " + this.permission + " - " + return accessStatus + " " + this.permission + " - " + this.authority + " (" + this.authorityType + ")";
this.authority + " (" + this.authorityType + ")";
} }
@Override @Override
@@ -390,36 +388,31 @@ public class PermissionServiceImpl implements PermissionServiceSPI, Initializing
{ {
return AccessStatus.DENIED; return AccessStatus.DENIED;
} }
// Allow permissions for nodes that do not exist // Allow permissions for nodes that do not exist
if (!nodeService.exists(nodeRef)) if (!nodeService.exists(nodeRef))
{ {
return AccessStatus.ALLOWED; return AccessStatus.ALLOWED;
} }
// Get the current authentications // Get the current authentications
// Use the smart authentication cache to improve permissions performance // Use the smart authentication cache to improve permissions performance
Authentication auth = authenticationComponent.getCurrentAuthentication(); Authentication auth = authenticationComponent.getCurrentAuthentication();
Set<String> authorisations = getAuthorisations(auth, nodeRef); Set<String> authorisations = getAuthorisations(auth, nodeRef);
Serializable key = generateKey( Serializable key = generateKey(authorisations, nodeRef, perm, CacheType.HAS_PERMISSION);
authorisations,
nodeRef,
perm);
AccessStatus status = accessCache.get(key); AccessStatus status = accessCache.get(key);
if (status != null) if (status != null)
{ {
return status; return status;
} }
// If the node does not support the given permission there is no point // If the node does not support the given permission there is no point
// doing the test // doing the test
Set<PermissionReference> available = modelDAO.getAllPermissions(nodeRef); Set<PermissionReference> available = modelDAO.getAllPermissions(nodeRef);
available.add(getAllPermissionReference()); available.add(getAllPermissionReference());
available.add(OLD_ALL_PERMISSIONS_REFERENCE); available.add(OLD_ALL_PERMISSIONS_REFERENCE);
if (!(available.contains(perm))) if (!(available.contains(perm)))
{ {
accessCache.put(key, AccessStatus.DENIED); accessCache.put(key, AccessStatus.DENIED);
@@ -428,16 +421,15 @@ public class PermissionServiceImpl implements PermissionServiceSPI, Initializing
if (authenticationComponent.getCurrentUserName().equals(authenticationComponent.getSystemUserName())) if (authenticationComponent.getCurrentUserName().equals(authenticationComponent.getSystemUserName()))
{ {
return AccessStatus.ALLOWED; return AccessStatus.ALLOWED;
} }
// //
// TODO: Dynamic permissions via evaluators // TODO: Dynamic permissions via evaluators
// //
/* /*
* Does the current authentication have the supplied permission on the * Does the current authentication have the supplied permission on the given node.
* given node.
*/ */
QName typeQname = nodeService.getType(nodeRef); QName typeQname = nodeService.getType(nodeRef);
@@ -455,24 +447,28 @@ public class PermissionServiceImpl implements PermissionServiceSPI, Initializing
+ perm + "> is " + (result ? "allowed" : "denied") + " for " + perm + "> is " + (result ? "allowed" : "denied") + " for "
+ authenticationComponent.getCurrentUserName() + " on node " + nodeService.getPath(nodeRef)); + authenticationComponent.getCurrentUserName() + " on node " + nodeService.getPath(nodeRef));
} }
status = result ? AccessStatus.ALLOWED : AccessStatus.DENIED; status = result ? AccessStatus.ALLOWED : AccessStatus.DENIED;
accessCache.put(key, status); accessCache.put(key, status);
return status; return status;
} }
enum CacheType
{
HAS_PERMISSION, SINGLE_PERMISSION, SINGLE_PERMISSION_GLOBAL;
}
/** /**
* Key for a cache object is built from all the known Authorities (which can * Key for a cache object is built from all the known Authorities (which can change dynamically so they must all be used) the NodeRef ID and the permission reference itself.
* change dynamically so they must all be used) the NodeRef ID and the * This gives a unique key for each permission test.
* permission reference itself. This gives a unique key for each permission
* test.
*/ */
static Serializable generateKey(Set<String> auths, NodeRef nodeRef, PermissionReference perm) static Serializable generateKey(Set<String> auths, NodeRef nodeRef, PermissionReference perm, CacheType type)
{ {
LinkedHashSet<Serializable> key = new LinkedHashSet<Serializable>(); LinkedHashSet<Serializable> key = new LinkedHashSet<Serializable>();
key.add(perm.toString()); key.add(perm.toString());
key.addAll(auths); key.addAll(auths);
key.add(nodeRef); key.add(nodeRef);
key.add(type);
return key; return key;
} }
@@ -575,7 +571,7 @@ public class PermissionServiceImpl implements PermissionServiceSPI, Initializing
permissionsDaoComponent.setInheritParentPermissions(nodeRef, inheritParentPermissions); permissionsDaoComponent.setInheritParentPermissions(nodeRef, inheritParentPermissions);
accessCache.clear(); accessCache.clear();
} }
/** /**
* @see org.alfresco.service.cmr.security.PermissionService#getInheritParentPermissions(org.alfresco.service.cmr.repository.NodeRef) * @see org.alfresco.service.cmr.security.PermissionService#getInheritParentPermissions(org.alfresco.service.cmr.repository.NodeRef)
*/ */
@@ -584,7 +580,6 @@ public class PermissionServiceImpl implements PermissionServiceSPI, Initializing
return permissionsDaoComponent.getInheritParentPermissions(nodeRef); return permissionsDaoComponent.getInheritParentPermissions(nodeRef);
} }
public PermissionReference getPermissionReference(QName qname, String permissionName) public PermissionReference getPermissionReference(QName qname, String permissionName)
{ {
return modelDAO.getPermissionReference(qname, permissionName); return modelDAO.getPermissionReference(qname, permissionName);
@@ -594,7 +589,7 @@ public class PermissionServiceImpl implements PermissionServiceSPI, Initializing
{ {
return getPermissionReference(ALL_PERMISSIONS); return getPermissionReference(ALL_PERMISSIONS);
} }
public String getPermission(PermissionReference permissionReference) public String getPermission(PermissionReference permissionReference)
{ {
if (modelDAO.isUnique(permissionReference)) if (modelDAO.isUnique(permissionReference))
@@ -642,7 +637,7 @@ public class PermissionServiceImpl implements PermissionServiceSPI, Initializing
permissionsDaoComponent.deletePermissions(recipient); permissionsDaoComponent.deletePermissions(recipient);
accessCache.clear(); accessCache.clear();
} }
// //
// SUPPORT CLASSES // SUPPORT CLASSES
// //
@@ -866,64 +861,114 @@ public class PermissionServiceImpl implements PermissionServiceSPI, Initializing
public boolean hasSinglePermission(Set<String> authorisations, NodeRef nodeRef) public boolean hasSinglePermission(Set<String> authorisations, NodeRef nodeRef)
{ {
Serializable key = generateKey(
authorisations,
nodeRef,
this.required, CacheType.SINGLE_PERMISSION_GLOBAL);
AccessStatus status = accessCache.get(key);
if (status != null)
{
return status == AccessStatus.ALLOWED;
}
// Check global permission // Check global permission
if (checkGlobalPermissions(authorisations)) if (checkGlobalPermissions(authorisations))
{ {
accessCache.put(key, AccessStatus.ALLOWED);
return true; return true;
} }
Set<Pair<String, PermissionReference>> denied = new HashSet<Pair<String, PermissionReference>>(); Set<Pair<String, PermissionReference>> denied = new HashSet<Pair<String, PermissionReference>>();
// Keep track of permission that are denied return hasSinglePermission(authorisations, nodeRef, denied);
}
public boolean hasSinglePermission(Set<String> authorisations, NodeRef nodeRef,
Set<Pair<String, PermissionReference>> denied)
{
// Add any denied permission to the denied list - these can not
// then
// be used to given authentication.
// A -> B -> C
// If B denies all permissions to any - allowing all permissions
// to
// andy at node A has no effect
denied.addAll(getDenied(nodeRef));
// Cache non denied
Serializable key = null;
if (denied.size() == 0)
{
key = generateKey(authorisations, nodeRef, this.required, CacheType.SINGLE_PERMISSION);
}
if (key != null)
{
AccessStatus status = accessCache.get(key);
if (status != null)
{
return status == AccessStatus.ALLOWED;
}
}
// If the current node allows the permission we are done
// The test includes any parent or ancestor requirements
if (checkRequired(authorisations, nodeRef, denied))
{
if (key != null)
{
accessCache.put(key, AccessStatus.ALLOWED);
}
return true;
}
// Permissions are only evaluated up the primary parent chain // Permissions are only evaluated up the primary parent chain
// TODO: Do not ignore non primary permissions // TODO: Do not ignore non primary permissions
ChildAssociationRef car = nodeService.getPrimaryParent(nodeRef); ChildAssociationRef car = nodeService.getPrimaryParent(nodeRef);
// Work up the parent chain evaluating permissions.
while (car != null) // Build the next element of the evaluation chain
if (car.getParentRef() != null)
{ {
// Add any denied permission to the denied list - these can not NodePermissionEntry nodePermissions = permissionsDaoComponent.getPermissions(nodeRef);
// then if ((nodePermissions == null) || (nodePermissions.inheritPermissions()))
// be used to given authentication.
// A -> B -> C
// If B denies all permissions to any - allowing all permissions
// to
// andy at node A has no effect
denied.addAll(getDenied(car.getChildRef()));
// If the current node allows the permission we are done
// The test includes any parent or ancestor requirements
if (checkRequired(authorisations, car.getChildRef(), denied))
{ {
return true; if(hasSinglePermission(authorisations, car.getParentRef(), denied))
}
// Build the next element of the evaluation chain
if (car.getParentRef() != null)
{
NodePermissionEntry nodePermissions = permissionsDaoComponent.getPermissions(car.getChildRef());
if ((nodePermissions == null) || (nodePermissions.inheritPermissions()))
{ {
car = nodeService.getPrimaryParent(car.getParentRef()); if (key != null)
{
accessCache.put(key, AccessStatus.ALLOWED);
}
return true;
} }
else else
{ {
car = null; if (key != null)
{
accessCache.put(key, AccessStatus.DENIED);
}
return false;
} }
} }
else else
{ {
car = null; if (key != null)
{
accessCache.put(key, AccessStatus.DENIED);
}
return false;
} }
} }
else
// TODO: Support meta data permissions on the root node? {
if (key != null)
return false; {
accessCache.put(key, AccessStatus.DENIED);
}
return false;
}
} }
/** /**
@@ -982,7 +1027,8 @@ public class PermissionServiceImpl implements PermissionServiceSPI, Initializing
// All permission excludes all permissions available for // All permission excludes all permissions available for
// the node. // the node.
if (pe.getPermissionReference().equals(getAllPermissionReference()) || pe.getPermissionReference().equals(OLD_ALL_PERMISSIONS_REFERENCE)) if (pe.getPermissionReference().equals(getAllPermissionReference())
|| pe.getPermissionReference().equals(OLD_ALL_PERMISSIONS_REFERENCE))
{ {
for (PermissionReference deny : modelDAO.getAllPermissions(nodeRef)) for (PermissionReference deny : modelDAO.getAllPermissions(nodeRef))
{ {
@@ -1072,7 +1118,7 @@ public class PermissionServiceImpl implements PermissionServiceSPI, Initializing
// Default deny // Default deny
return false; return false;
} }
} }
/** /**

View File

@@ -20,12 +20,13 @@
* and Open Source Software ("FLOSS") applications as described in Alfresco's * and Open Source Software ("FLOSS") applications as described in Alfresco's
* FLOSS exception. You should have recieved a copy of the text describing * FLOSS exception. You should have recieved a copy of the text describing
* the FLOSS exception, and it is also available here: * the FLOSS exception, and it is also available here:
* http://www.alfresco.com/legal/licensing" * http://www.alfresco.com/legal/licensing
*/ */
package org.alfresco.repo.security.permissions.impl.model; package org.alfresco.repo.security.permissions.impl.model;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.Serializable;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
@@ -727,19 +728,44 @@ public class PermissionModel implements ModelDAO, InitializingBean
} }
return pg; return pg;
} }
static Serializable generateKey(PermissionReference required, QName qName, Set<QName> aspectQNames,
RequiredPermission.On on)
{
LinkedHashSet<Serializable> key = new LinkedHashSet<Serializable>();
key.add(required.toString());
key.add(qName);
key.addAll(aspectQNames);
key.add(on.toString());
return key;
}
private HashMap<Serializable, Set<PermissionReference>> requiredPermissionsCache = new HashMap<Serializable, Set<PermissionReference>>(
1024);
public Set<PermissionReference> getRequiredPermissions(PermissionReference required, QName qName, public Set<PermissionReference> getRequiredPermissions(PermissionReference required, QName qName,
Set<QName> aspectQNames, RequiredPermission.On on) Set<QName> aspectQNames, RequiredPermission.On on)
{ {
PermissionGroup pg = getBasePermissionGroupOrNull(getPermissionGroupOrNull(required)); // Cache lookup as this is static
if (pg == null)
Serializable key = generateKey(required, qName, aspectQNames, on);
Set<PermissionReference> answer = requiredPermissionsCache.get(key);
if (answer == null)
{ {
return getRequirementsForPermission(required, on); PermissionGroup pg = getBasePermissionGroupOrNull(getPermissionGroupOrNull(required));
} if (pg == null)
else {
{ answer = getRequirementsForPermission(required, on);
return getRequirementsForPermissionGroup(pg, on, qName, aspectQNames); }
else
{
answer = getRequirementsForPermissionGroup(pg, on, qName, aspectQNames);
}
requiredPermissionsCache.put(key, answer);
} }
return answer;
} }
/** /**