/* Copyright 2013 Rigas Grigoropoulos * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package eu.europeana.aas.acl; import java.util.ArrayList; import java.util.List; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.security.acls.domain.AclAuthorizationStrategy; import org.springframework.security.acls.domain.PermissionFactory; import org.springframework.security.acls.domain.PrincipalSid; import org.springframework.security.acls.model.AccessControlEntry; import org.springframework.security.acls.model.Acl; import org.springframework.security.acls.model.AclCache; import org.springframework.security.acls.model.AlreadyExistsException; import org.springframework.security.acls.model.ChildrenExistException; import org.springframework.security.acls.model.MutableAcl; import org.springframework.security.acls.model.MutableAclService; import org.springframework.security.acls.model.NotFoundException; import org.springframework.security.acls.model.ObjectIdentity; import org.springframework.security.acls.model.PermissionGrantingStrategy; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.util.Assert; import eu.europeana.aas.acl.model.AclEntry; import eu.europeana.aas.acl.model.AclObjectIdentity; import eu.europeana.aas.acl.repository.AclRepository; import eu.europeana.aas.acl.repository.exceptions.AclAlreadyExistsException; import eu.europeana.aas.acl.repository.exceptions.AclNotFoundException; /** * Provides support for creating and storing {@link Acl} instances in Cassandra, * using the {@link AclRepository}. * * @author Rigas Grigoropoulos * */ public class CassandraMutableAclService extends CassandraAclService implements MutableAclService { private static final Log LOG = LogFactory.getLog(CassandraMutableAclService.class); /** * Constructs a new <code>CassandraMutableAclService</code> object. * * @param aclRepository the {@link AclRepository} to use for access to the * database. * @param aclCache the {@link AclCache} to use (can be <code>null</code>). * @param grantingStrategy the {@link PermissionGrantingStrategy} to use * when creating {@link Acl} objects. * @param aclAuthorizationStrategy the {@link AclAuthorizationStrategy} to * use when creating {@link Acl} objects. * @param permissionFactory the {@link PermissionFactory} to use when * creating {@link AccessControlEntry} objects. */ public CassandraMutableAclService(AclRepository aclRepository, AclCache aclCache, PermissionGrantingStrategy grantingStrategy, AclAuthorizationStrategy aclAuthorizationStrategy, PermissionFactory permissionFactory) { super(aclRepository, aclCache, grantingStrategy, aclAuthorizationStrategy, permissionFactory); } @Override public MutableAcl createAcl(ObjectIdentity objectIdentity) throws AlreadyExistsException { Assert.notNull(objectIdentity, "Object Identity required"); if (LOG.isDebugEnabled()) { LOG.debug("BEGIN createAcl: objectIdentity: " + objectIdentity); } // Need to retrieve the current principal, in order to know who "owns" // this ACL (can be changed later on) Authentication auth = SecurityContextHolder.getContext().getAuthentication(); PrincipalSid sid = new PrincipalSid(auth); AclObjectIdentity newAoi = new AclObjectIdentity(objectIdentity); newAoi.setOwnerId(sid.getPrincipal()); newAoi.setOwnerPrincipal(true); newAoi.setEntriesInheriting(false); try { aclRepository.saveAcl(newAoi); } catch (AclAlreadyExistsException e) { throw new AlreadyExistsException(e.getMessage(), e); } // Retrieve the ACL via superclass (ensures cache registration, proper retrieval etc) Acl acl = readAclById(objectIdentity); Assert.isInstanceOf(MutableAcl.class, acl, "MutableAcl should be been returned"); if (LOG.isDebugEnabled()) { LOG.debug("END createAcl: acl: " + acl); } return (MutableAcl) acl; } @Override public void deleteAcl(ObjectIdentity objectIdentity, boolean deleteChildren) throws ChildrenExistException { Assert.notNull(objectIdentity, "Object Identity required"); Assert.notNull(objectIdentity.getIdentifier(), "Object Identity doesn't provide an identifier"); if (LOG.isDebugEnabled()) { LOG.debug("BEGIN deleteAcl: objectIdentity: " + objectIdentity + ", deleteChildren: " + deleteChildren); } List<AclObjectIdentity> objIdsToDelete = new ArrayList<>(); List<ObjectIdentity> objectsToDelete = new ArrayList<>(); objectsToDelete.add(objectIdentity); List<ObjectIdentity> children = findChildren(objectIdentity); if (deleteChildren) { for (ObjectIdentity child : children) { objectsToDelete.addAll(calculateChildrenReccursively(child)); } } else if (children != null && !children.isEmpty()) { throw new ChildrenExistException("Cannot delete '" + objectIdentity + "' (has " + children.size() + " children)"); } for (ObjectIdentity objId : objectsToDelete) { objIdsToDelete.add(new AclObjectIdentity(objId)); } aclRepository.deleteAcls(objIdsToDelete); // Clear the cache if (aclCache != null) { for (ObjectIdentity obj : objectsToDelete) { aclCache.evictFromCache(obj); } } if (LOG.isDebugEnabled()) { LOG.debug("END deleteAcl"); } } @Override public MutableAcl updateAcl(MutableAcl acl) throws NotFoundException { Assert.notNull(acl, "MutableAcl required"); Assert.notNull(acl.getObjectIdentity(), "Object Identity required"); Assert.notNull(acl.getObjectIdentity().getIdentifier(), "Object Identity doesn't provide an identifier"); if (LOG.isDebugEnabled()) { LOG.debug("BEGIN updateAcl: acl: " + acl); } try { aclRepository.updateAcl(new AclObjectIdentity(acl), convertToAclEntries(acl)); } catch (AclNotFoundException e) { throw new NotFoundException(e.getMessage(), e); } // Clear the cache, including children clearCacheIncludingChildren(acl.getObjectIdentity()); // Retrieve the ACL via superclass (ensures cache registration, proper retrieval etc) MutableAcl result = (MutableAcl) readAclById(acl.getObjectIdentity()); if (LOG.isDebugEnabled()) { LOG.debug("END updateAcl: acl: " + result); } return result; } /** * Finds the complete children hierarchy starting from the provided * {@link ObjectIdentity}. * * @param rootChild the root {@link ObjectIdentity} to start looking for * children. * @return a list of all child {@link ObjectIdentity} objects, including the * provided root object. */ private List<ObjectIdentity> calculateChildrenReccursively(ObjectIdentity rootChild) { List<ObjectIdentity> result = new ArrayList<>(); result.add(rootChild); List<ObjectIdentity> children = findChildren(rootChild); if (children != null) { for (ObjectIdentity child : children) { result.addAll(calculateChildrenReccursively(child)); } } return result; } /** * Converts an {@link Acl} to a list of {@link AclEntry} objects. * * @param acl the {@link Acl} to convert. * @return the list of derived {@link AclEntry} objects. */ private List<AclEntry> convertToAclEntries(Acl acl) { List<AclEntry> result = new ArrayList<>(); for (AccessControlEntry entry : acl.getEntries()) { result.add(new AclEntry(entry)); } return result; } /** * Evicts the provided {@link ObjectIdentity} and the complete children * hierarchy from the cache. * * @param objectIdentity the parent {@link ObjectIdentity} to evict. */ private void clearCacheIncludingChildren(ObjectIdentity objectIdentity) { Assert.notNull(objectIdentity, "ObjectIdentity required"); List<ObjectIdentity> children = findChildren(objectIdentity); if (children != null) { for (ObjectIdentity child : children) { clearCacheIncludingChildren(child); } } if (aclCache != null) { aclCache.evictFromCache(objectIdentity); } } }