/*
* 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.hibernate;
import java.io.Serializable;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.zip.CRC32;
import org.alfresco.error.AlfrescoRuntimeException;
import org.alfresco.repo.cache.SimpleCache;
import org.alfresco.repo.domain.DbAccessControlEntry;
import org.alfresco.repo.domain.DbAccessControlList;
import org.alfresco.repo.domain.DbAccessControlListChangeSet;
import org.alfresco.repo.domain.DbAccessControlListMember;
import org.alfresco.repo.domain.DbAuthority;
import org.alfresco.repo.domain.DbPermission;
import org.alfresco.repo.domain.Node;
import org.alfresco.repo.domain.avm.AVMNodeDAO;
import org.alfresco.repo.domain.avm.AVMNodeEntity;
import org.alfresco.repo.domain.patch.PatchDAO;
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.PermissionReference;
import org.alfresco.repo.security.permissions.SimpleAccessControlEntry;
import org.alfresco.repo.security.permissions.SimpleAccessControlEntryContext;
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.AclDaoComponent;
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.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hibernate.CacheMode;
import org.hibernate.Criteria;
import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.criterion.Criterion;
import org.hibernate.criterion.Restrictions;
import org.alfresco.util.Pair;
import org.springframework.orm.hibernate3.HibernateCallback;
import org.springframework.orm.hibernate3.support.HibernateDaoSupport;
/**
* Hibernate DAO to manage ACL persistence
*
* @author andyh
*/
public class AclDaoComponentImpl extends HibernateDaoSupport implements AclDaoComponent
{
private static Log logger = LogFactory.getLog(AclDaoComponentImpl.class);
static String QUERY_GET_PERMISSION = "permission.GetPermission";
static String QUERY_GET_AUTHORITY = "permission.GetAuthority";
static String QUERY_GET_ACE_WITH_NO_CONTEXT = "permission.GetAceWithNoContext";
// static String QUERY_GET_AUTHORITY_ALIAS = "permission.GetAuthorityAlias";
// static String QUERY_GET_AUTHORITY_ALIASES = "permission.GetAuthorityAliases";
static String QUERY_GET_ACES_AND_ACLS_BY_AUTHORITY = "permission.GetAcesAndAclsByAuthority";
static String QUERY_GET_ACES_BY_AUTHORITY = "permission.GetAcesByAuthority";
static String QUERY_GET_ACES_FOR_ACL = "permission.GetAcesForAcl";
static String QUERY_LOAD_ACL = "permission.LoadAcl";
static String QUERY_GET_ACLS_THAT_INHERIT_FROM_THIS_ACL = "permission.GetAclsThatInheritFromThisAcl";
static String QUERY_GET_AVM_NODES_BY_ACL = "permission.FindAvmNodesByACL";
static String QUERY_GET_LATEST_ACL_BY_ACLID = "permission.FindLatestAclByGuid";
/** Access to QName entities */
private QNameDAO qnameDAO;
/** Access to AVMNode queries */
private AVMNodeDAO avmNodeDAO;
/** Access to additional Patch queries */
private PatchDAO patchDAO;
/** a transactionally-safe cache to be injected */
private SimpleCache aclCache;
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,
/**
* Simlpe copy
*/
COPY_ONLY, CREATE_AND_INHERIT;
}
/**
*
*/
public AclDaoComponentImpl()
{
super();
// Wire up for annoying AVM hack to support copy and setting of ACLs as nodes are created
DbAccessControlListImpl.setAclDaoComponent(this);
}
/**
* Set the DAO for accessing QName entities
*
* @param qnameDAO
*/
public void setQnameDAO(QNameDAO qnameDAO)
{
this.qnameDAO = qnameDAO;
}
public void setPatchDAO(PatchDAO patchDAO)
{
this.patchDAO = patchDAO;
}
public void setAvmNodeDAO(AVMNodeDAO avmNodeDAO)
{
this.avmNodeDAO = avmNodeDAO;
}
/**
* Set the ACL cache
*
* @param aclCache
*/
public void setAclCache(SimpleCache aclCache)
{
this.aclCache = aclCache;
}
public DbAccessControlList getDbAccessControlList(Long id)
{
if (id == null)
{
return null;
}
DbAccessControlList acl = (DbAccessControlList) getHibernateTemplate().get(DbAccessControlListImpl.class, id);
return acl;
}
public Long createAccessControlList(AccessControlListProperties properties)
{
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 createAccessControlListImpl(properties, null, null);
}
private Long createAccessControlListImpl(AccessControlListProperties properties, List aces, Long inherited)
{
DbAccessControlListImpl acl = new DbAccessControlListImpl();
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 FIXED:
case GLOBAL:
case SHARED:
case DEFINING:
case LAYERED:
default:
if (properties.isVersioned() != null)
{
acl.setVersioned(properties.isVersioned());
}
else
{
acl.setVersioned(Boolean.TRUE);
}
break;
}
acl.setAclChangeSet(getCurrentChangeSet());
acl.setRequiresVersion(false);
Long created = (Long) getHibernateTemplate().save(acl);
DirtySessionMethodInterceptor.flushSession(getSession(), true);
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
DbAuthority authority = getAuthority(ace.getAuthority(), true);
DbPermission permission = getPermission(ace.getPermission(), true);
// Find context
if (ace.getContext() != null)
{
throw new UnsupportedOperationException();
}
// Find ACE
DbAccessControlEntry entry = getAccessControlEntry(permission, authority, ace, true);
// 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 created;
}
@SuppressWarnings("unchecked")
private void getWritable(final Long id, final Long parent, List extends AccessControlEntry> 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();
HibernateCallback callback = new HibernateCallback()
{
public Object doInHibernate(Session session)
{
Query query = session.getNamedQuery(QUERY_GET_ACES_FOR_ACL);
query.setParameter("id", parent);
DirtySessionMethodInterceptor.setQueryFlushMode(session, query);
return query.list();
}
};
List members = (List) getHibernateTemplate().execute(callback);
for (DbAccessControlListMember member : members)
{
if ((mode == WriteMode.INSERT_INHERITED) && (member.getPosition() == 0))
{
inherited.add(member.getAccessControlEntry());
positions.add(member.getPosition());
}
else
{
inherited.add(member.getAccessControlEntry());
positions.add(member.getPosition());
}
}
}
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 cna be optimised
* slighlty 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
*/
@SuppressWarnings("unchecked")
private void getWritable(final Long id, final Long parent, List extends AccessControlEntry> 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)
{
HibernateCallback callback = new HibernateCallback()
{
public Object doInHibernate(Session session)
{
Query query = session.getNamedQuery(QUERY_GET_ACLS_THAT_INHERIT_FROM_THIS_ACL);
query.setParameter("id", id);
DirtySessionMethodInterceptor.setQueryFlushMode(session, query);
return query.list();
}
};
List inheritors = (List) getHibernateTemplate().execute(callback);
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
*/
@SuppressWarnings("unchecked")
private AclChange getWritable(final Long id, final Long parent, List extends AccessControlEntry> exclude, List toAdd, Long inheritsFrom,
List inherited, List positions, int depth, WriteMode mode, boolean requiresVersion)
{
DbAccessControlList acl = (DbAccessControlList) getHibernateTemplate().get(DbAccessControlListImpl.class, id);
if (!acl.isLatest())
{
aclCache.remove(id);
return new AclChangeImpl(id, id, acl.getAclType(), acl.getAclType());
}
if (!acl.isVersioned())
{
switch (mode)
{
case COPY_UPDATE_AND_INHERIT:
removeAcesFromAcl(id, exclude, depth);
addAcesToAcl(acl, 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:
addAcesToAcl(acl, toAdd, depth);
addInherited(acl, inherited, positions, depth);
case COPY_ONLY:
default:
break;
}
if (inheritsFrom != null)
{
acl.setInheritsFrom(inheritsFrom);
}
aclCache.remove(id);
return new AclChangeImpl(id, id, acl.getAclType(), acl.getAclType());
}
else if ((acl.getAclChangeSet() == getCurrentChangeSet()) && (!requiresVersion) && (!acl.getRequiresVersion()))
{
switch (mode)
{
case COPY_UPDATE_AND_INHERIT:
removeAcesFromAcl(id, exclude, depth);
addAcesToAcl(acl, 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:
addAcesToAcl(acl, toAdd, depth);
addInherited(acl, inherited, positions, depth);
case COPY_ONLY:
default:
break;
}
if (inheritsFrom != null)
{
acl.setInheritsFrom(inheritsFrom);
}
aclCache.remove(id);
return new AclChangeImpl(id, id, acl.getAclType(), acl.getAclType());
}
else
{
DbAccessControlList newAcl = new DbAccessControlListImpl();
newAcl.setAclChangeSet(getCurrentChangeSet());
newAcl.setAclId(acl.getAclId());
newAcl.setAclType(acl.getAclType());
newAcl.setAclVersion(acl.getAclVersion() + 1);
newAcl.setInheritedAclId(-1l);
newAcl.setInherits(acl.getInherits());
newAcl.setInheritsFrom((inheritsFrom != null) ? inheritsFrom : acl.getInheritsFrom());
newAcl.setLatest(Boolean.TRUE);
newAcl.setVersioned(Boolean.TRUE);
newAcl.setRequiresVersion(Boolean.FALSE);
Long created = (Long) getHibernateTemplate().save(newAcl);
DirtySessionMethodInterceptor.flushSession(getSession(), true);
// Create new membership entries - excluding those in the given pattern
// AcePatternMatcher excluder = new AcePatternMatcher(exclude);
HibernateCallback callback = new HibernateCallback()
{
public Object doInHibernate(Session session)
{
Query query = session.getNamedQuery(QUERY_GET_ACES_FOR_ACL);
query.setParameter("id", id);
DirtySessionMethodInterceptor.setQueryFlushMode(session, query);
return query.list();
}
};
List members = (List) getHibernateTemplate().execute(callback);
for (DbAccessControlListMember member : members)
{
// if (mode == WriteMode.COPY_UPDATE_AND_INHERIT)
// {
// if ((member.getPosition() == depth) && ((excluder == null) || !excluder.matches(member.getACE(),
// member.getPosition())))
// {
// DbAccessControlListMemberImpl newMember = new DbAccessControlListMemberImpl();
// newMember.setACL(newAcl);
// newMember.setACE(member.getACE());
// newMember.setPosition(member.getPosition());
// getHibernateTemplate().save(newMember);
// }
// }
// TODO: optimise copy cases :-)
DbAccessControlListMemberImpl newMember = new DbAccessControlListMemberImpl();
newMember.setAccessControlList(newAcl);
newMember.setAccessControlEntry(member.getAccessControlEntry());
newMember.setPosition(member.getPosition());
getHibernateTemplate().save(newMember);
DirtySessionMethodInterceptor.flushSession(getSession(), true);
}
// add new
switch (mode)
{
case COPY_UPDATE_AND_INHERIT:
// Done above
removeAcesFromAcl(newAcl.getId(), exclude, depth);
addAcesToAcl(newAcl, 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:
addAcesToAcl(acl, 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();
DbAccessControlList parentAcl = (DbAccessControlList) getHibernateTemplate().get(DbAccessControlListImpl.class, writableParentAcl);
parentAcl.setInheritedAclId(created);
}
}
// fix up old version
acl.setLatest(Boolean.FALSE);
acl.setRequiresVersion(Boolean.FALSE);
aclCache.remove(id);
return new AclChangeImpl(id, created, acl.getAclType(), newAcl.getAclType());
}
}
/**
* Helper to remove ACEs from an ACL
*
* @param id
* @param exclude
* @param depth
*/
@SuppressWarnings("unchecked")
private void removeAcesFromAcl(final Long id, final List extends AccessControlEntry> exclude, final int depth)
{
AcePatternMatcher excluder = new AcePatternMatcher(exclude);
HibernateCallback callback = new HibernateCallback()
{
public Object doInHibernate(Session session)
{
if (exclude == null)
{
Criteria criteria = session.createCriteria(DbAccessControlListMemberImpl.class, "member");
criteria.createAlias("accessControlList", "acl");
criteria.add(Restrictions.eq("acl.id", id));
criteria.createAlias("accessControlEntry", "ace");
criteria.createAlias("ace.authority", "authority");
criteria.createAlias("ace.permission", "permission");
criteria.setResultTransformer(Criteria.ALIAS_TO_ENTITY_MAP);
DirtySessionMethodInterceptor.setCriteriaFlushMode(session, criteria);
return criteria.list();
}
else
{
Criteria criteria = session.createCriteria(DbAccessControlListMemberImpl.class, "member");
criteria.createAlias("accessControlList", "acl");
criteria.add(Restrictions.eq("acl.id", id));
// build or
if (exclude.size() == 1)
{
AccessControlEntry excluded = exclude.get(0);
if ((excluded.getPosition() != null) && excluded.getPosition() >= 0)
{
criteria.add(Restrictions.eq("position", Integer.valueOf(depth)));
}
if ((excluded.getAccessStatus() != null) || (excluded.getAceType() != null) || (excluded.getAuthority() != null) || (excluded.getPermission() != null))
{
criteria.createAlias("accessControlEntry", "ace");
if (excluded.getAccessStatus() != null)
{
criteria.add(Restrictions.eq("ace.allowed", excluded.getAccessStatus() == AccessStatus.ALLOWED ? Boolean.TRUE : Boolean.FALSE));
}
if (excluded.getAceType() != null)
{
criteria.add(Restrictions.eq("ace.applies", Integer.valueOf(excluded.getAceType().getId())));
}
if (excluded.getAuthority() != null)
{
criteria.createAlias("ace.authority", "authority");
criteria.add(Restrictions.eq("authority.authority", excluded.getAuthority()));
}
if (excluded.getPermission() != null)
{
criteria.createAlias("ace.permission", "permission");
criteria.add(Restrictions.eq("permission.name", excluded.getPermission().getName()));
// TODO: Add typeQname
}
}
}
else
{
criteria.createAlias("accessControlEntry", "ace");
criteria.createAlias("ace.authority", "authority");
criteria.createAlias("ace.permission", "permission");
List toOr = new LinkedList();
LOOP: for (AccessControlEntry excluded : exclude)
{
List toAnd = new LinkedList();
if ((excluded.getPosition() != null) && excluded.getPosition() >= 0)
{
toAnd.add(Restrictions.eq("position", Integer.valueOf(depth)));
}
if (excluded.getAccessStatus() != null)
{
toAnd.add(Restrictions.eq("ace.allowed", excluded.getAccessStatus() == AccessStatus.ALLOWED ? Boolean.TRUE : Boolean.FALSE));
}
if (excluded.getAceType() != null)
{
toAnd.add(Restrictions.eq("ace.applies", Integer.valueOf(excluded.getAceType().getId())));
}
if (excluded.getAuthority() != null)
{
toAnd.add(Restrictions.eq("authority.authority", excluded.getAuthority()));
}
if (excluded.getPermission() != null)
{
toAnd.add(Restrictions.eq("permission.name", excluded.getPermission().getName()));
// TODO: Add typeQname
}
Criterion accumulated = null;
for (Criterion current : toAnd)
{
if (accumulated == null)
{
accumulated = current;
}
else
{
accumulated = Restrictions.and(accumulated, current);
}
}
if (accumulated == null)
{
// matches all
toOr = null;
break LOOP;
}
else
{
toOr.add(accumulated);
}
}
Criterion accumulated = null;
for (Criterion current : toOr)
{
if (accumulated == null)
{
accumulated = current;
}
else
{
accumulated = Restrictions.or(accumulated, current);
}
}
if (accumulated == null)
{
// no action
}
else
{
criteria.add(accumulated);
}
}
criteria.setResultTransformer(Criteria.ALIAS_TO_ENTITY_MAP);
DirtySessionMethodInterceptor.setCriteriaFlushMode(session, criteria);
return criteria.list();
}
}
};
List