/* * Copyright (C) 2005-2010 Alfresco Software Limited. * * This file is part of Alfresco * * 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 . */ package org.alfresco.repo.domain.permissions; import java.io.Serializable; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Set; import org.alfresco.repo.cache.SimpleCache; import org.alfresco.repo.domain.qname.QNameDAO; import org.alfresco.repo.security.permissions.ACEType; import org.alfresco.repo.security.permissions.ACLCopyMode; import org.alfresco.repo.security.permissions.ACLType; import org.alfresco.repo.security.permissions.AccessControlEntry; import org.alfresco.repo.security.permissions.AccessControlList; import org.alfresco.repo.security.permissions.AccessControlListProperties; import org.alfresco.repo.security.permissions.SimpleAccessControlEntry; import org.alfresco.repo.security.permissions.SimpleAccessControlList; import org.alfresco.repo.security.permissions.SimpleAccessControlListProperties; import org.alfresco.repo.security.permissions.impl.AclChange; import org.alfresco.repo.security.permissions.impl.SimplePermissionReference; import org.alfresco.repo.transaction.AlfrescoTransactionSupport; import org.alfresco.service.cmr.security.AccessStatus; import org.alfresco.service.cmr.security.AuthorityType; import org.alfresco.service.namespace.QName; import org.alfresco.util.GUID; import org.alfresco.util.Pair; import org.alfresco.util.ParameterCheck; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; /** * DAO to manage ACL persistence * * Note: based on earlier AclDaoComponentImpl * * @author Andy Hind, janv * @since 3.4 */ public class AclDAOImpl implements AclDAO { private static Log logger = LogFactory.getLog(AclDAOImpl.class); /** Access to QName entities */ private QNameDAO qnameDAO; /** Access to ACL entities */ private AclCrudDAO aclCrudDAO; /** a transactionally-safe cache to be injected */ private SimpleCache aclCache; private SimpleCache> readersCache; private enum WriteMode { /** * Remove inherited ACEs after that set */ TRUNCATE_INHERITED, /** * Add inherited ACEs */ ADD_INHERITED, /** * The source of inherited ACEs is changing */ CHANGE_INHERITED, /** * Remove all inherited ACEs */ REMOVE_INHERITED, /** * Insert inherited ACEs */ INSERT_INHERITED, /** * Copy ACLs and update ACEs and inheritance */ COPY_UPDATE_AND_INHERIT, /** * Simple copy */ COPY_ONLY, CREATE_AND_INHERIT; } public void setQnameDAO(QNameDAO qnameDAO) { this.qnameDAO = qnameDAO; } public void setAclCrudDAO(AclCrudDAO aclCrudDAO) { this.aclCrudDAO = aclCrudDAO; } /** * Set the ACL cache * * @param aclCache */ public void setAclCache(SimpleCache aclCache) { this.aclCache = aclCache; } /** * @param readersCache the readersCache to set */ public void setReadersCache(SimpleCache> readersCache) { this.readersCache = readersCache; } /* (non-Javadoc) * @see org.alfresco.repo.domain.permissions.AclDAO#createAccessControlList() */ public Long createAccessControlList() { return createAccessControlList(getDefaultProperties()).getId(); } /* (non-Javadoc) * @see org.alfresco.repo.domain.permissions.AclDAO#getDefaultProperties() */ public AccessControlListProperties getDefaultProperties() { SimpleAccessControlListProperties properties = new SimpleAccessControlListProperties(); properties.setAclType(ACLType.DEFINING); properties.setInherits(true); properties.setVersioned(false); return properties; } /* (non-Javadoc) * @see org.alfresco.repo.domain.permissions.AclDAO#createAcl(org.alfresco.repo.security.permissions.AccessControlListProperties) */ public Acl createAccessControlList(AccessControlListProperties properties) { if (properties == null) { throw new IllegalArgumentException("Properties cannot be null"); } if (properties.getAclType() == null) { throw new IllegalArgumentException("ACL Type must be defined"); } switch (properties.getAclType()) { case OLD: if (properties.isVersioned() == Boolean.TRUE) { throw new IllegalArgumentException("Old acls can not be versioned"); } break; case SHARED: throw new IllegalArgumentException("Can not create shared acls direct - use get inherited"); case DEFINING: case LAYERED: break; case FIXED: if (properties.getInherits() == Boolean.TRUE) { throw new IllegalArgumentException("Fixed ACLs can not inherit"); } case GLOBAL: if (properties.getInherits() == Boolean.TRUE) { throw new IllegalArgumentException("Fixed ACLs can not inherit"); } default: break; } return createAccessControlList(properties, null, null); } /* (non-Javadoc) * @see org.alfresco.repo.domain.permissions.AclDAO#createAcl(org.alfresco.repo.security.permissions.AccessControlListProperties, java.util.List, java.lang.Long) */ public Acl createAccessControlList(AccessControlListProperties properties, List aces, Long inherited) { if (properties == null) { throw new IllegalArgumentException("Properties cannot be null"); } AclEntity acl = new AclEntity(); if (properties.getAclId() != null) { acl.setAclId(properties.getAclId()); } else { acl.setAclId(GUID.generate()); } acl.setAclType(properties.getAclType()); acl.setAclVersion(Long.valueOf(1l)); switch (properties.getAclType()) { case FIXED: case GLOBAL: acl.setInherits(Boolean.FALSE); case OLD: case SHARED: case DEFINING: case LAYERED: default: if (properties.getInherits() != null) { acl.setInherits(properties.getInherits()); } else { acl.setInherits(Boolean.TRUE); } break; } acl.setLatest(Boolean.TRUE); switch (properties.getAclType()) { case OLD: acl.setVersioned(Boolean.FALSE); break; case LAYERED: if (properties.isVersioned() != null) { acl.setVersioned(properties.isVersioned()); } else { acl.setVersioned(Boolean.TRUE); } break; case FIXED: case GLOBAL: case SHARED: case DEFINING: default: if (properties.isVersioned() != null) { acl.setVersioned(properties.isVersioned()); } else { acl.setVersioned(Boolean.FALSE); } break; } acl.setAclChangeSetId(getCurrentChangeSetId()); acl.setRequiresVersion(false); Acl createdAcl = (AclEntity)aclCrudDAO.createAcl(acl); long created = createdAcl.getId(); if ((aces != null) && aces.size() > 0) { List changes = new ArrayList(); List toAdd = new ArrayList(aces.size()); List excluded = new ArrayList(aces.size()); for (AccessControlEntry ace : aces) { if ((ace.getPosition() != null) && (ace.getPosition() != 0)) { throw new IllegalArgumentException("Invalid position"); } // Find authority Authority authority = aclCrudDAO.getOrCreateAuthority(ace.getAuthority()); Permission permission = aclCrudDAO.getOrCreatePermission(ace.getPermission()); // Find context if (ace.getContext() != null) { throw new UnsupportedOperationException(); } // Find ACE Ace entry = aclCrudDAO.getOrCreateAce(permission, authority, ace.getAceType(), ace.getAccessStatus()); // Wire up // COW and remove any existing matches SimpleAccessControlEntry exclude = new SimpleAccessControlEntry(); // match any access status exclude.setAceType(ace.getAceType()); exclude.setAuthority(ace.getAuthority()); exclude.setPermission(ace.getPermission()); exclude.setPosition(0); toAdd.add(entry); excluded.add(exclude); // Will remove from the cache } Long toInherit = null; if (inherited != null) { toInherit = getInheritedAccessControlList(inherited); } getWritable(created, toInherit, excluded, toAdd, toInherit, false, changes, WriteMode.CREATE_AND_INHERIT); } return createdAcl; } private void getWritable(final Long id, final Long parent, List exclude, List toAdd, Long inheritsFrom, boolean cascade, List changes, WriteMode mode) { List inherited = null; List positions = null; if ((mode == WriteMode.ADD_INHERITED) || (mode == WriteMode.INSERT_INHERITED) || (mode == WriteMode.CHANGE_INHERITED)) { inherited = new ArrayList(); positions = new ArrayList(); // get aces for acl (via acl member) List members = aclCrudDAO.getAclMembersByAcl(parent); for (AclMember member : members) { Ace aceEntity = aclCrudDAO.getAce(member.getAceId()); if ((mode == WriteMode.INSERT_INHERITED) && (member.getPos() == 0)) { inherited.add(aceEntity); positions.add(member.getPos()); } else { inherited.add(aceEntity); positions.add(member.getPos()); } } } getWritable(id, parent, exclude, toAdd, inheritsFrom, inherited, positions, cascade, 0, changes, mode, false); } /** * Make a whole tree of ACLs copy on write if required Includes adding and removing ACEs which can be optimised * slightly for copy on write (no need to add and then remove) * * @param id * @param parent * @param exclude * @param toAdd * @param inheritsFrom * @param cascade * @param depth * @param changes */ private void getWritable(final Long id, final Long parent, List exclude, List toAdd, Long inheritsFrom, List inherited, List positions, boolean cascade, int depth, List changes, WriteMode mode, boolean requiresVersion) { AclChange current = getWritable(id, parent, exclude, toAdd, inheritsFrom, inherited, positions, depth, mode, requiresVersion); changes.add(current); boolean cascadeVersion = requiresVersion; if (!cascadeVersion) { cascadeVersion = !current.getBefore().equals(current.getAfter()); } if (cascade) { List inheritors = aclCrudDAO.getAclsThatInheritFromAcl(id); for (Long nextId : inheritors) { // Check for those that inherit themselves to other nodes ... if (nextId != id) { getWritable(nextId, current.getAfter(), exclude, toAdd, current.getAfter(), inherited, positions, cascade, depth + 1, changes, mode, cascadeVersion); } } } } /** * COW for an individual ACL * * @param id * @param parent * @param exclude * @param toAdd * @param inheritsFrom * @param depth * @return - an AclChange */ private AclChange getWritable(final Long id, final Long parent, List exclude, List acesToAdd, Long inheritsFrom, List inherited, List positions, int depth, WriteMode mode, boolean requiresVersion) { AclUpdateEntity acl = aclCrudDAO.getAclForUpdate(id); if (!acl.isLatest()) { aclCache.remove(id); readersCache.remove(id); return new AclChangeImpl(id, id, acl.getAclType(), acl.getAclType()); } List toAdd = new ArrayList(0); if (acesToAdd != null) { for (Ace ace : acesToAdd) { toAdd.add(ace.getId()); } } if (!acl.isVersioned()) { switch (mode) { case COPY_UPDATE_AND_INHERIT: removeAcesFromAcl(id, exclude, depth); aclCrudDAO.addAclMembersToAcl(acl.getId(), toAdd, depth); break; case CHANGE_INHERITED: replaceInherited(id, acl, inherited, positions, depth); break; case ADD_INHERITED: addInherited(acl, inherited, positions, depth); break; case TRUNCATE_INHERITED: truncateInherited(id, depth); break; case INSERT_INHERITED: insertInherited(id, acl, inherited, positions, depth); break; case REMOVE_INHERITED: removeInherited(id, depth); break; case CREATE_AND_INHERIT: aclCrudDAO.addAclMembersToAcl(acl.getId(), toAdd, depth); addInherited(acl, inherited, positions, depth); case COPY_ONLY: default: break; } if (inheritsFrom != null) { acl.setInheritsFrom(inheritsFrom); aclCrudDAO.updateAcl(acl); } aclCache.remove(id); readersCache.remove(id); return new AclChangeImpl(id, id, acl.getAclType(), acl.getAclType()); } else if ((acl.getAclChangeSetId() == getCurrentChangeSetId()) && (!requiresVersion) && (!acl.getRequiresVersion())) { switch (mode) { case COPY_UPDATE_AND_INHERIT: removeAcesFromAcl(id, exclude, depth); aclCrudDAO.addAclMembersToAcl(acl.getId(), toAdd, depth); break; case CHANGE_INHERITED: replaceInherited(id, acl, inherited, positions, depth); break; case ADD_INHERITED: addInherited(acl, inherited, positions, depth); break; case TRUNCATE_INHERITED: truncateInherited(id, depth); break; case INSERT_INHERITED: insertInherited(id, acl, inherited, positions, depth); break; case REMOVE_INHERITED: removeInherited(id, depth); break; case CREATE_AND_INHERIT: aclCrudDAO.addAclMembersToAcl(acl.getId(), toAdd, depth); addInherited(acl, inherited, positions, depth); case COPY_ONLY: default: break; } if (inheritsFrom != null) { acl.setInheritsFrom(inheritsFrom); aclCrudDAO.updateAcl(acl); } aclCache.remove(id); readersCache.remove(id); return new AclChangeImpl(id, id, acl.getAclType(), acl.getAclType()); } else { AclEntity newAcl = new AclEntity(); newAcl.setAclChangeSetId(getCurrentChangeSetId()); newAcl.setAclId(acl.getAclId()); newAcl.setAclType(acl.getAclType()); newAcl.setAclVersion(acl.getAclVersion() + 1); newAcl.setInheritedAcl(-1l); newAcl.setInherits(acl.getInherits()); newAcl.setInheritsFrom((inheritsFrom != null) ? inheritsFrom : acl.getInheritsFrom()); newAcl.setLatest(Boolean.TRUE); newAcl.setVersioned(Boolean.TRUE); newAcl.setRequiresVersion(Boolean.FALSE); AclEntity createdAcl = (AclEntity)aclCrudDAO.createAcl(newAcl); long created = createdAcl.getId(); // Create new membership entries - excluding those in the given pattern // AcePatternMatcher excluder = new AcePatternMatcher(exclude); // get aces for acl (via acl member) List members = aclCrudDAO.getAclMembersByAcl(id); if (members.size() > 0) { List> aceIdsWithDepths = new ArrayList>(members.size()); for (AclMember member : members) { aceIdsWithDepths.add(new Pair(member.getAceId(), member.getPos())); } // copy acl members to new acl aclCrudDAO.addAclMembersToAcl(newAcl.getId(), aceIdsWithDepths); } // add new switch (mode) { case COPY_UPDATE_AND_INHERIT: // Done above removeAcesFromAcl(newAcl.getId(), exclude, depth); aclCrudDAO.addAclMembersToAcl(newAcl.getId(), toAdd, depth); break; case CHANGE_INHERITED: replaceInherited(newAcl.getId(), newAcl, inherited, positions, depth); break; case ADD_INHERITED: addInherited(newAcl, inherited, positions, depth); break; case TRUNCATE_INHERITED: truncateInherited(newAcl.getId(), depth); break; case INSERT_INHERITED: insertInherited(newAcl.getId(), newAcl, inherited, positions, depth); break; case REMOVE_INHERITED: removeInherited(newAcl.getId(), depth); break; case CREATE_AND_INHERIT: aclCrudDAO.addAclMembersToAcl(acl.getId(), toAdd, depth); addInherited(acl, inherited, positions, depth); case COPY_ONLY: default: break; } // Fix up inherited ACL if required if (newAcl.getAclType() == ACLType.SHARED) { if (parent != null) { Long writableParentAcl = getWritable(parent, null, null, null, null, null, null, 0, WriteMode.COPY_ONLY, false).getAfter(); AclUpdateEntity parentAcl = aclCrudDAO.getAclForUpdate(writableParentAcl); parentAcl.setInheritedAcl(created); aclCrudDAO.updateAcl(parentAcl); } } // fix up old version acl.setLatest(Boolean.FALSE); acl.setRequiresVersion(Boolean.FALSE); aclCrudDAO.updateAcl(acl); aclCache.remove(id); readersCache.remove(id); return new AclChangeImpl(id, created, acl.getAclType(), newAcl.getAclType()); } } /** * Helper to remove ACEs from an ACL * * @param id * @param exclude * @param depth */ private void removeAcesFromAcl(final Long id, final List exclude, final int depth) { if (exclude == null) { // cascade delete all acl members - no exclusion aclCrudDAO.deleteAclMembersByAcl(id); } else { AcePatternMatcher excluder = new AcePatternMatcher(exclude); List> results = aclCrudDAO.getAcesAndAuthoritiesByAcl(id); List memberIds = new ArrayList(results.size()); for (Map result : results) { Long result_aclmemId = (Long) result.get("aclmemId"); if ((exclude != null) && excluder.matches(aclCrudDAO, result, depth)) { memberIds.add(result_aclmemId); } } // delete list of acl members aclCrudDAO.deleteAclMembers(memberIds); } } private void replaceInherited(Long id, Acl acl, List inherited, List positions, int depth) { truncateInherited(id, depth); addInherited(acl, inherited, positions, depth); } private void truncateInherited(final Long id, int depth) { List members = aclCrudDAO.getAclMembersByAcl(id); List membersToDelete = new ArrayList(members.size()); for (AclMember member : members) { if (member.getPos() > depth) { membersToDelete.add(member.getId()); } } if (membersToDelete.size() > 0) { // delete list of acl members aclCrudDAO.deleteAclMembers(membersToDelete); } } private void removeInherited(final Long id, int depth) { List members = aclCrudDAO.getAclMembersByAclForUpdate(id); List membersToDelete = new ArrayList(members.size()); for (AclMemberEntity member : members) { if (member.getPos() == depth + 1) { membersToDelete.add(member.getId()); } else if (member.getPos() > (depth + 1)) { member.setPos(member.getPos() - 1); aclCrudDAO.updateAclMember(member); } } if (membersToDelete.size() > 0) { // delete list of acl members aclCrudDAO.deleteAclMembers(membersToDelete); } } private void addInherited(Acl acl, List inherited, List positions, int depth) { if ((inherited != null) && (inherited.size() > 0)) { List> aceIdsWithDepths = new ArrayList>(inherited.size()); for (int i = 0; i < inherited.size(); i++) { Ace add = inherited.get(i); Integer position = positions.get(i); aceIdsWithDepths.add(new Pair(add.getId(), position.intValue() + depth + 1)); } aclCrudDAO.addAclMembersToAcl(acl.getId(), aceIdsWithDepths); } } private void insertInherited(final Long id, AclEntity acl, List inherited, List positions, int depth) { // get aces for acl (via acl member) List members = aclCrudDAO.getAclMembersByAclForUpdate(id); for (AclMemberEntity member : members) { if (member.getPos() > depth) { member.setPos(member.getPos() + 1); aclCrudDAO.updateAclMember(member); } } addInherited(acl, inherited, positions, depth); } /* (non-Javadoc) * @see org.alfresco.repo.domain.permissions.AclDAO#deleteAccessControlEntries(java.lang.String) */ public List deleteAccessControlEntries(final String authority) { List acls = new ArrayList(); // get authority Authority authEntity = aclCrudDAO.getAuthority(authority); if (authEntity == null) { return acls; } List aces = new ArrayList(); List members = aclCrudDAO.getAclMembersByAuthority(authority); if (members.size() > 0) { List membersToDelete = new ArrayList(members.size()); // fix up members and extract acls and aces for (AclMember member : members) { // Delete acl entry Long aclMemberId = member.getId(); Long aclId = member.getAclId(); Long aceId = member.getAceId(); aclCache.remove(aclId); readersCache.remove(aclId); Acl list = aclCrudDAO.getAcl(aclId); acls.add(new AclChangeImpl(aclId, aclId, list.getAclType(), list.getAclType())); membersToDelete.add(aclMemberId); aces.add((Long)aceId); } // delete list of acl members aclCrudDAO.deleteAclMembers(membersToDelete); } // remove ACEs aclCrudDAO.deleteAces(aces); // Tidy up any unreferenced ACEs // get aces by authority List unreferenced = aclCrudDAO.getAcesByAuthority(authEntity.getId()); if (unreferenced.size() > 0) { List unrefencedAcesToDelete = new ArrayList(unreferenced.size()); for (Ace ace : unreferenced) { unrefencedAcesToDelete.add(ace.getId()); } aclCrudDAO.deleteAces(unrefencedAcesToDelete); } // remove authority if (authEntity != null) { aclCrudDAO.deleteAuthority(authEntity.getId()); } return acls; } /* (non-Javadoc) * @see org.alfresco.repo.domain.permissions.AclDAO#deleteAclForNode(long, boolean) */ public void deleteAclForNode(long aclId, boolean isAVMNode) { Acl dbAcl = getAcl(aclId); if (dbAcl.getAclType() == ACLType.DEFINING) { // delete acl members & acl aclCrudDAO.deleteAclMembersByAcl(aclId); aclCrudDAO.deleteAcl(aclId); aclCache.remove(aclId); readersCache.remove(aclId); } if (dbAcl.getAclType() == ACLType.SHARED) { // check unused Long defining = dbAcl.getInheritsFrom(); if (aclCrudDAO.getAcl(defining) == null) { if (! isAVMNode) { // ADM if (getADMNodesByAcl(aclId, 1).size() == 0) { // delete acl members & acl aclCrudDAO.deleteAclMembersByAcl(aclId); aclCrudDAO.deleteAcl(aclId); aclCache.remove(aclId); readersCache.remove(aclId); } } else { // TODO: AVM } } } } /* (non-Javadoc) * @see org.alfresco.repo.domain.permissions.AclDAO#deleteAccessControlList(java.lang.Long) */ public List deleteAccessControlList(final Long id) { if (logger.isDebugEnabled()) { // debug only int maxForDebug = 11; List nodeIds = getADMNodesByAcl(id, maxForDebug); for (Long nodeId : nodeIds) { logger.debug("deleteAccessControlList: Found nodeId=" + nodeId + ", aclId=" + id); } } List acls = new ArrayList(); final AclUpdateEntity acl = aclCrudDAO.getAclForUpdate(id); if (!acl.isLatest()) { throw new UnsupportedOperationException("Old ACL versions can not be updated"); } if (acl.getAclType() == ACLType.SHARED) { throw new UnsupportedOperationException("Delete is not supported for shared acls - they are deleted with the defining acl"); } if ((acl.getAclType() == ACLType.DEFINING) || (acl.getAclType() == ACLType.LAYERED)) { if ((acl.getInheritedAcl() != null) && (acl.getInheritedAcl() != -1)) { final Acl inherited = aclCrudDAO.getAcl(acl.getInheritedAcl()); // Will remove from the cache getWritable(inherited.getId(), acl.getInheritsFrom(), null, null, null, true, acls, WriteMode.REMOVE_INHERITED); Acl unusedInherited = null; for (AclChange change : acls) { if (change.getBefore() == inherited.getId()) { unusedInherited = aclCrudDAO.getAcl(change.getAfter()); } } final Long newId = unusedInherited.getId(); List inheritors = aclCrudDAO.getAclsThatInheritFromAcl(newId); for (Long nextId : inheritors) { // Will remove from the cache getWritable(nextId, acl.getInheritsFrom(), null, null, acl.getInheritsFrom(), true, acls, WriteMode.REMOVE_INHERITED); } // delete acl members aclCrudDAO.deleteAclMembersByAcl(newId); // delete 'unusedInherited' acl aclCrudDAO.deleteAcl(unusedInherited.getId()); if (inherited.isVersioned()) { AclUpdateEntity inheritedForUpdate = aclCrudDAO.getAclForUpdate(inherited.getId()); if (inheritedForUpdate != null) { inheritedForUpdate.setLatest(Boolean.FALSE); aclCrudDAO.updateAcl(inheritedForUpdate); } } else { // delete 'inherited' acl aclCrudDAO.deleteAcl(inherited.getId()); } } } else { List inheritors = aclCrudDAO.getAclsThatInheritFromAcl(id); for (Long nextId : inheritors) { // Will remove from the cache getWritable(nextId, acl.getInheritsFrom(), null, null, null, true, acls, WriteMode.REMOVE_INHERITED); } } // delete if (acl.isVersioned()) { acl.setLatest(Boolean.FALSE); aclCrudDAO.updateAcl(acl); } else { // delete acl members & acl aclCrudDAO.deleteAclMembersByAcl(id); aclCrudDAO.deleteAcl(acl.getId()); } // remove the deleted acl from the cache aclCache.remove(id); readersCache.remove(id); acls.add(new AclChangeImpl(id, null, acl.getAclType(), null)); return acls; } /** * {@inheritDoc} */ public List deleteLocalAccessControlEntries(Long id) { List changes = new ArrayList(); SimpleAccessControlEntry pattern = new SimpleAccessControlEntry(); pattern.setPosition(Integer.valueOf(0)); // Will remove from the cache getWritable(id, null, Collections.singletonList(pattern), null, null, true, changes, WriteMode.COPY_UPDATE_AND_INHERIT); return changes; } /** * {@inheritDoc} */ public List deleteInheritedAccessControlEntries(Long id) { List changes = new ArrayList(); SimpleAccessControlEntry pattern = new SimpleAccessControlEntry(); pattern.setPosition(Integer.valueOf(-1)); // Will remove from the cache getWritable(id, null, Collections.singletonList(pattern), null, null, true, changes, WriteMode.COPY_UPDATE_AND_INHERIT); return changes; } /** * {@inheritDoc} */ public List deleteAccessControlEntries(Long id, AccessControlEntry pattern) { List changes = new ArrayList(); // Will remove from the cache getWritable(id, null, Collections.singletonList(pattern), null, null, true, changes, WriteMode.COPY_UPDATE_AND_INHERIT); return changes; } /** * {@inheritDoc} */ public Acl getAcl(Long id) { return aclCrudDAO.getAcl(id); } /** * {@inheritDoc} */ public AccessControlListProperties getAccessControlListProperties(Long id) { ParameterCheck.mandatory("id", id); // Prevent unboxing failures return aclCrudDAO.getAcl(id); } /** * {@inheritDoc} */ public AccessControlList getAccessControlList(Long id) { AccessControlList acl = aclCache.get(id); if (acl == null) { acl = getAccessControlListImpl(id); aclCache.put(id, acl); } else { // System.out.println("Used cache for "+id); } return acl; } /** * @return the access control list */ private AccessControlList getAccessControlListImpl(final Long id) { SimpleAccessControlList acl = new SimpleAccessControlList(); AccessControlListProperties properties = getAccessControlListProperties(id); if (properties == null) { return null; } acl.setProperties(properties); List> results = aclCrudDAO.getAcesAndAuthoritiesByAcl(id); List entries = new ArrayList(results.size()); for (Map result : results) // for (AclMemberEntity member : members) { Boolean aceIsAllowed = (Boolean) result.get("allowed"); Integer aceType = (Integer) result.get("applies"); String authority = (String) result.get("authority"); Long permissionId = (Long) result.get("permissionId"); Integer position = (Integer) result.get("pos"); //Long result_aclmemId = (Long) result.get("aclmemId"); // not used here SimpleAccessControlEntry sacEntry = new SimpleAccessControlEntry(); sacEntry.setAccessStatus(aceIsAllowed ? AccessStatus.ALLOWED : AccessStatus.DENIED); sacEntry.setAceType(ACEType.getACETypeFromId(aceType)); sacEntry.setAuthority(authority); // if (entry.getContext() != null) // { // SimpleAccessControlEntryContext context = new SimpleAccessControlEntryContext(); // context.setClassContext(entry.getContext().getClassContext()); // context.setKVPContext(entry.getContext().getKvpContext()); // context.setPropertyContext(entry.getContext().getPropertyContext()); // sacEntry.setContext(context); // } Permission perm = aclCrudDAO.getPermission(permissionId); QName permTypeQName = qnameDAO.getQName(perm.getTypeQNameId()).getSecond(); // Has an ID so must exist SimplePermissionReference permissionRefernce = SimplePermissionReference.getPermissionReference(permTypeQName, perm.getName()); sacEntry.setPermission(permissionRefernce); sacEntry.setPosition(position); entries.add(sacEntry); } Collections.sort(entries); acl.setEntries(entries); return acl; } /* (non-Javadoc) * @see org.alfresco.repo.domain.permissions.AclDAO#getInheritedAccessControlList(java.lang.Long) */ public Long getInheritedAccessControlList(Long id) { AclUpdateEntity acl = aclCrudDAO.getAclForUpdate(id); if (acl.getAclType() == ACLType.OLD) { return null; } if ((acl.getInheritedAcl() != null) && (acl.getInheritedAcl() != -1)) { return acl.getInheritedAcl(); } Long inheritedAclId = null; if ((acl.getAclType() == ACLType.DEFINING) || (acl.getAclType() == ACLType.LAYERED)) { List changes = new ArrayList(); // created shared acl SimpleAccessControlListProperties properties = new SimpleAccessControlListProperties(); properties.setAclType(ACLType.SHARED); properties.setInherits(Boolean.TRUE); properties.setVersioned(acl.isVersioned()); Long sharedId = createAccessControlList(properties, null, null).getId(); getWritable(sharedId, id, null, null, id, true, changes, WriteMode.ADD_INHERITED); acl.setInheritedAcl(sharedId); inheritedAclId = sharedId; } else { acl.setInheritedAcl(acl.getId()); inheritedAclId = acl.getId(); } aclCrudDAO.updateAcl(acl); return inheritedAclId; } /* (non-Javadoc) * @see org.alfresco.repo.domain.permissions.AclDAO#mergeInheritedAccessControlList(java.lang.Long, java.lang.Long) */ public List mergeInheritedAccessControlList(Long inherited, Long target) { // TODO: For now we do a replace - we could do an insert if both inherit from the same acl List changes = new ArrayList(); Acl targetAcl = aclCrudDAO.getAcl(target); Acl inheritedAcl = null; if (inherited != null) { inheritedAcl = aclCrudDAO.getAcl(inherited); } else { // Assume we are just resetting it to inherit as before if (targetAcl.getInheritsFrom() != null) { inheritedAcl = aclCrudDAO.getAcl(targetAcl.getInheritsFrom()); if (inheritedAcl == null) { // TODO: Try previous versions throw new IllegalStateException("No old inheritance definition to use"); } else { // find the latest version of the acl if (!inheritedAcl.isLatest()) { final String searchAclId = inheritedAcl.getAclId(); Long actualInheritor = (Long)aclCrudDAO.getLatestAclByGuid(searchAclId); inheritedAcl = aclCrudDAO.getAcl(actualInheritor); if (inheritedAcl == null) { // TODO: Try previous versions throw new IllegalStateException("No ACL found"); } } } } else { // There is no inheritance to set return changes; } } // recursion test // if inherited already inherits from the target Acl test = inheritedAcl; while (test != null) { if (test.getId() == target) { throw new IllegalStateException("Cyclical ACL detected"); } Long parent = test.getInheritsFrom(); if ((parent == null) || (parent == -1l)) { test = null; } else { test = aclCrudDAO.getAcl(test.getInheritsFrom()); } } if ((targetAcl.getAclType() != ACLType.DEFINING) && (targetAcl.getAclType() != ACLType.LAYERED)) { throw new IllegalArgumentException("Only defining ACLs can have their inheritance set"); } if (!targetAcl.getInherits()) { return changes; } Long actualInheritedId = inheritedAcl.getId(); if ((inheritedAcl.getAclType() == ACLType.DEFINING) || (inheritedAcl.getAclType() == ACLType.LAYERED)) { actualInheritedId = getInheritedAccessControlList(actualInheritedId); } // Will remove from the cache getWritable(target, actualInheritedId, null, null, actualInheritedId, true, changes, WriteMode.CHANGE_INHERITED); return changes; } /* (non-Javadoc) * @see org.alfresco.repo.domain.permissions.AclDAO#setAccessControlEntry(java.lang.Long, org.alfresco.repo.security.permissions.AccessControlEntry) */ public List setAccessControlEntry(final Long id, final AccessControlEntry ace) { Acl target = aclCrudDAO.getAcl(id); if (target.getAclType() == ACLType.SHARED) { throw new IllegalArgumentException("Shared ACLs are immutable"); } List changes = new ArrayList(); if ((ace.getPosition() != null) && (ace.getPosition() != 0)) { throw new IllegalArgumentException("Invalid position"); } // Find authority Authority authority = aclCrudDAO.getOrCreateAuthority(ace.getAuthority()); Permission permission = aclCrudDAO.getOrCreatePermission(ace.getPermission()); // Find context if (ace.getContext() != null) { throw new UnsupportedOperationException(); } // Find ACE Ace entry = aclCrudDAO.getOrCreateAce(permission, authority, ace.getAceType(), ace.getAccessStatus()); // Wire up // COW and remove any existing matches SimpleAccessControlEntry exclude = new SimpleAccessControlEntry(); // match any access status exclude.setAceType(ace.getAceType()); exclude.setAuthority(ace.getAuthority()); exclude.setPermission(ace.getPermission()); exclude.setPosition(0); List toAdd = new ArrayList(1); toAdd.add(entry); // Will remove from the cache getWritable(id, null, Collections.singletonList(exclude), toAdd, null, true, changes, WriteMode.COPY_UPDATE_AND_INHERIT); return changes; } /* (non-Javadoc) * @see org.alfresco.repo.domain.permissions.AclDAO#enableInheritance(java.lang.Long, java.lang.Long) */ public List enableInheritance(Long id, Long parent) { List changes = new ArrayList(); AclUpdateEntity acl = aclCrudDAO.getAclForUpdate(id); switch (acl.getAclType()) { case FIXED: case GLOBAL: throw new IllegalArgumentException("Fixed and global permissions can not inherit"); case OLD: acl.setInherits(Boolean.TRUE); aclCrudDAO.updateAcl(acl); aclCache.remove(id); readersCache.remove(id); changes.add(new AclChangeImpl(id, id, acl.getAclType(), acl.getAclType())); return changes; case SHARED: // TODO support a list of children and casacade if given throw new IllegalArgumentException( "Shared acls should be replace by creating a definig ACL, wiring it up for inhertitance, and then applying inheritance to any children. It can not be done by magic "); case DEFINING: case LAYERED: default: if (!acl.getInherits()) { // Will remove from the cache getWritable(id, null, null, null, null, false, changes, WriteMode.COPY_ONLY); acl = aclCrudDAO.getAclForUpdate(changes.get(0).getAfter()); acl.setInherits(Boolean.TRUE); aclCrudDAO.updateAcl(acl); } else { // Will remove from the cache getWritable(id, null, null, null, null, false, changes, WriteMode.COPY_ONLY); } List merged = mergeInheritedAccessControlList(parent, changes.get(0).getAfter()); changes.addAll(merged); return changes; } } /* (non-Javadoc) * @see org.alfresco.repo.domain.permissions.AclDAO#disableInheritance(java.lang.Long, boolean) */ public List disableInheritance(Long id, boolean setInheritedOnAcl) { AclUpdateEntity acl = aclCrudDAO.getAclForUpdate(id); List changes = new ArrayList(1); switch (acl.getAclType()) { case FIXED: case GLOBAL: return Collections. singletonList(new AclChangeImpl(id, id, acl.getAclType(), acl.getAclType())); case OLD: acl.setInherits(Boolean.FALSE); aclCrudDAO.updateAcl(acl); aclCache.remove(id); readersCache.remove(id); changes.add(new AclChangeImpl(id, id, acl.getAclType(), acl.getAclType())); return changes; case SHARED: // TODO support a list of children and casacade if given throw new IllegalArgumentException("Shared ACL must inherit"); case DEFINING: case LAYERED: default: return disableInheritanceImpl(id, setInheritedOnAcl, acl); } } private Long getCopy(Long toCopy, Long toInheritFrom, ACLCopyMode mode) { AclUpdateEntity aclToCopy; Long inheritedId; Acl aclToInheritFrom; switch (mode) { case INHERIT: if (toCopy.equals(toInheritFrom)) { return getInheritedAccessControlList(toCopy); } else { throw new UnsupportedOperationException(); } case COW: aclToCopy = aclCrudDAO.getAclForUpdate(toCopy); aclToCopy.setRequiresVersion(true); aclCrudDAO.updateAcl(aclToCopy); aclCache.remove(toCopy); readersCache.remove(toCopy); inheritedId = getInheritedAccessControlList(toCopy); if ((inheritedId != null) && (!inheritedId.equals(toCopy))) { AclUpdateEntity inheritedAcl = aclCrudDAO.getAclForUpdate(inheritedId); inheritedAcl.setRequiresVersion(true); aclCrudDAO.updateAcl(inheritedAcl); aclCache.remove(inheritedId); readersCache.remove(inheritedId); } return toCopy; case REDIRECT: if ((toInheritFrom != null) && (toInheritFrom == toCopy)) { return getInheritedAccessControlList(toInheritFrom); } aclToCopy = aclCrudDAO.getAclForUpdate(toCopy); aclToInheritFrom = null; if (toInheritFrom != null) { aclToInheritFrom = aclCrudDAO.getAcl(toInheritFrom); } switch (aclToCopy.getAclType()) { case DEFINING: // This is not called on the redirecting node as only LAYERED change permissions when redirected // So this needs to make a copy in the same way layered does case LAYERED: if (toInheritFrom == null) { return toCopy; } // manages cache clearing beneath List changes = mergeInheritedAccessControlList(toInheritFrom, toCopy); for (AclChange change : changes) { if (change.getBefore().equals(toCopy)) { return change.getAfter(); } } throw new UnsupportedOperationException(); case SHARED: if (aclToInheritFrom != null) { return getInheritedAccessControlList(toInheritFrom); } else { throw new UnsupportedOperationException(); } case FIXED: case GLOBAL: case OLD: return toCopy; default: throw new UnsupportedOperationException(); } case COPY: aclToCopy = aclCrudDAO.getAclForUpdate(toCopy); aclToInheritFrom = null; if (toInheritFrom != null) { aclToInheritFrom = aclCrudDAO.getAcl(toInheritFrom); } switch (aclToCopy.getAclType()) { case DEFINING: SimpleAccessControlListProperties properties = new SimpleAccessControlListProperties(); properties.setAclType(ACLType.DEFINING); properties.setInherits(aclToCopy.getInherits()); properties.setVersioned(true); Long id = createAccessControlList(properties).getId(); AccessControlList indirectAcl = getAccessControlList(toCopy); for (AccessControlEntry entry : indirectAcl.getEntries()) { if (entry.getPosition() == 0) { setAccessControlEntry(id, entry); } } if (aclToInheritFrom != null) { mergeInheritedAccessControlList(toInheritFrom, id); } return id; case SHARED: if (aclToInheritFrom != null) { return getInheritedAccessControlList(toInheritFrom); } else { return null; } case FIXED: case GLOBAL: case LAYERED: case OLD: return toCopy; default: throw new UnsupportedOperationException(); } default: throw new UnsupportedOperationException(); } } /* (non-Javadoc) * @see org.alfresco.repo.domain.permissions.AclDAO#getDbAccessControlListCopy(java.lang.Long, java.lang.Long, org.alfresco.repo.security.permissions.ACLCopyMode) */ public Acl getAclCopy(Long toCopy, Long toInheritFrom, ACLCopyMode mode) { return getAclEntityCopy(toCopy, toInheritFrom, mode); } private Acl getAclEntityCopy(Long toCopy, Long toInheritFrom, ACLCopyMode mode) { Long id = getCopy(toCopy, toInheritFrom, mode); if (id == null) { return null; } return aclCrudDAO.getAcl(id); } public List getAVMNodesByAcl(long aclEntityId, int maxResults) { return aclCrudDAO.getAVMNodesByAcl(aclEntityId, maxResults); } public List getADMNodesByAcl(long aclEntityId, int maxResults) { return aclCrudDAO.getADMNodesByAcl(aclEntityId, maxResults); } public Acl createLayeredAcl(Long indirectedAcl) { SimpleAccessControlListProperties properties = new SimpleAccessControlListProperties(); properties.setAclType(ACLType.LAYERED); Acl acl = createAccessControlList(properties); long id = acl.getId(); if (indirectedAcl != null) { mergeInheritedAccessControlList(indirectedAcl, id); } return acl; } private List disableInheritanceImpl(Long id, boolean setInheritedOnAcl, AclEntity aclIn) { List changes = new ArrayList(); if (!aclIn.getInherits()) { return Collections. emptyList(); } // Manages caching getWritable(id, null, null, null, null, false, changes, WriteMode.COPY_ONLY); AclUpdateEntity acl = aclCrudDAO.getAclForUpdate(changes.get(0).getAfter()); final Long inheritsFrom = acl.getInheritsFrom(); acl.setInherits(Boolean.FALSE); aclCrudDAO.updateAcl(acl); // Keep inherits from so we can reinstate if required // acl.setInheritsFrom(-1l); // Manages caching getWritable(acl.getId(), null, null, null, null, true, changes, WriteMode.TRUNCATE_INHERITED); // set Inherited - TODO: UNTESTED if ((inheritsFrom != null) && (inheritsFrom != -1) && setInheritedOnAcl) { // get aces for acl (via acl member) List members = aclCrudDAO.getAclMembersByAcl(inheritsFrom); for (AclMember member : members) { // TODO optimise Ace ace = aclCrudDAO.getAce(member.getAceId()); Authority authority = aclCrudDAO.getAuthority(ace.getAuthorityId()); SimpleAccessControlEntry entry = new SimpleAccessControlEntry(); entry.setAccessStatus(ace.isAllowed() ? AccessStatus.ALLOWED : AccessStatus.DENIED); entry.setAceType(ace.getAceType()); entry.setAuthority(authority.getAuthority()); /* NOTE: currently unused - intended for possible future enhancement if (ace.getContextId() != null) { AceContext aceContext = aclCrudDAO.getAceContext(ace.getContextId()); SimpleAccessControlEntryContext context = new SimpleAccessControlEntryContext(); context.setClassContext(aceContext.getClassContext()); context.setKVPContext(aceContext.getKvpContext()); context.setPropertyContext(aceContext.getPropertyContext()); entry.setContext(context); } */ Permission perm = aclCrudDAO.getPermission(ace.getPermissionId()); QName permTypeQName = qnameDAO.getQName(perm.getTypeQNameId()).getSecond(); // Has an ID so must exist SimplePermissionReference permissionRefernce = SimplePermissionReference.getPermissionReference(permTypeQName, perm.getName()); entry.setPermission(permissionRefernce); entry.setPosition(Integer.valueOf(0)); setAccessControlEntry(id, entry); } } return changes; } private static final String RESOURCE_KEY_ACL_CHANGE_SET_ID = "acl.change.set.id"; /** * Support to get the current ACL change set and bind this to the transaction. So we only make one new version of an * ACL per change set. If something is in the current change set we can update it. */ private long getCurrentChangeSetId() { Long changeSetId = (Long)AlfrescoTransactionSupport.getResource(RESOURCE_KEY_ACL_CHANGE_SET_ID); if (changeSetId == null) { changeSetId = aclCrudDAO.createAclChangeSet(); // bind the id AlfrescoTransactionSupport.bindResource(RESOURCE_KEY_ACL_CHANGE_SET_ID, changeSetId); if (logger.isDebugEnabled()) { logger.debug("New change set = " + changeSetId); } } else { /* AclChangeSetEntity changeSet = aclCrudDAO.getAclChangeSet((Long)changeSetId); if (changeSet == null) { throw new AlfrescoRuntimeException("Unexpected: missing change set "+changeSetId); } if (logger.isDebugEnabled()) { logger.debug("Existing change set = " + changeSetId); } */ } return changeSetId; } private static class AcePatternMatcher { private List patterns; AcePatternMatcher(List patterns) { this.patterns = patterns; } boolean matches(AclCrudDAO aclCrudDAO, Map result, int position) { if (patterns == null) { return true; } for (AccessControlEntry pattern : patterns) { if (checkPattern(aclCrudDAO, result, position, pattern)) { return true; } } return false; } private boolean checkPattern(AclCrudDAO aclCrudDAO, Map result, int position, AccessControlEntry pattern) { Boolean result_aceIsAllowed = (Boolean) result.get("allowed"); Integer result_aceType = (Integer) result.get("applies"); String result_authority = (String) result.get("authority"); Long result_permissionId = (Long) result.get("permissionId"); Integer result_position = (Integer) result.get("pos"); //Long result_aclmemId = (Long) result.get("aclmemId"); // not used if (pattern.getAccessStatus() != null) { if (pattern.getAccessStatus() != (result_aceIsAllowed ? AccessStatus.ALLOWED : AccessStatus.DENIED)) { return false; } } if (pattern.getAceType() != null) { if (pattern.getAceType() != ACEType.getACETypeFromId(result_aceType)) { return false; } } if (pattern.getAuthority() != null) { if ((pattern.getAuthorityType() != AuthorityType.WILDCARD) && !pattern.getAuthority().equals(result_authority)) { return false; } } if (pattern.getContext() != null) { throw new IllegalArgumentException("Context not yet supported"); } if (pattern.getPermission() != null) { Long permId = aclCrudDAO.getPermission(pattern.getPermission()).getId(); if (!permId.equals(result_permissionId)) { return false; } } if (pattern.getPosition() != null) { if (pattern.getPosition().intValue() >= 0) { if (result_position != position) { return false; } } else if (pattern.getPosition().intValue() == -1) { if (result_position <= position) { return false; } } } return true; } } static class AclChangeImpl implements AclChange { private Long before; private Long after; private ACLType typeBefore; private ACLType typeAfter; public AclChangeImpl(Long before, Long after, ACLType typeBefore, ACLType typeAfter) { this.before = before; this.after = after; this.typeAfter = typeAfter; this.typeBefore = typeBefore; } public Long getAfter() { return after; } public Long getBefore() { return before; } /** * @param after */ public void setAfter(Long after) { this.after = after; } /** * @param before */ public void setBefore(Long before) { this.before = before; } public ACLType getTypeAfter() { return typeAfter; } /** * @param typeAfter */ public void setTypeAfter(ACLType typeAfter) { this.typeAfter = typeAfter; } public ACLType getTypeBefore() { return typeBefore; } /** * @param typeBefore */ public void setTypeBefore(ACLType typeBefore) { this.typeBefore = typeBefore; } @Override public String toString() { StringBuilder builder = new StringBuilder(); builder.append("(").append(getBefore()).append(",").append(getTypeBefore()).append(")"); builder.append(" - > "); builder.append("(").append(getAfter()).append(",").append(getTypeAfter()).append(")"); return builder.toString(); } } /* (non-Javadoc) * @see org.alfresco.repo.domain.permissions.AclDAO#renameAuthority(java.lang.String, java.lang.String) */ public void renameAuthority(String before, String after) { aclCrudDAO.renameAuthority(before, after); aclCache.clear(); } }