/* 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.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.security.acls.domain.AccessControlEntryImpl;
import org.springframework.security.acls.domain.AclAuthorizationStrategy;
import org.springframework.security.acls.domain.AclImpl;
import org.springframework.security.acls.domain.ObjectIdentityImpl;
import org.springframework.security.acls.domain.PermissionFactory;
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.AclService;
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.acls.model.Sid;
import org.springframework.security.util.FieldUtils;
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;
/**
* Implementation of {@link AclService} using the {@link AclRepository} to
* access ACLs stored in Cassandra.
*
* @author Rigas Grigoropoulos
*
*/
public class CassandraAclService implements AclService {
private static final Log LOG = LogFactory.getLog(CassandraAclService.class);
protected final AclRepository aclRepository;
protected final AclCache aclCache;
private final PermissionFactory permissionFactory;
private final AclAuthorizationStrategy aclAuthorizationStrategy;
private final PermissionGrantingStrategy grantingStrategy;
private final Field fieldAces = FieldUtils.getField(AclImpl.class, "aces");
/**
* Constructs a new <code>CassandraAclService</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 CassandraAclService(AclRepository aclRepository, AclCache aclCache, PermissionGrantingStrategy grantingStrategy,
AclAuthorizationStrategy aclAuthorizationStrategy, PermissionFactory permissionFactory) {
this.aclRepository = aclRepository;
this.aclCache = aclCache;
this.grantingStrategy = grantingStrategy;
this.aclAuthorizationStrategy = aclAuthorizationStrategy;
this.permissionFactory = permissionFactory;
this.fieldAces.setAccessible(true);
}
@Override
public List<ObjectIdentity> findChildren(ObjectIdentity parentIdentity) {
Assert.notNull(parentIdentity, "Object to lookup required");
if (LOG.isDebugEnabled()) {
LOG.debug("BEGIN findChildren: parentIdentity: " + parentIdentity);
}
List<ObjectIdentity> result = null;
List<AclObjectIdentity> children = aclRepository.findAclObjectIdentityChildren(new AclObjectIdentity(parentIdentity));
if (children != null && !children.isEmpty()) {
result = new ArrayList<>();
for (AclObjectIdentity entry : children) {
result.add(entry.toObjectIdentity());
}
}
if (LOG.isDebugEnabled()) {
LOG.debug("END findChildren: children: " + result);
}
return result;
}
@Override
public Acl readAclById(ObjectIdentity object) throws NotFoundException {
return readAclById(object, null);
}
@Override
public Acl readAclById(ObjectIdentity object, List<Sid> sids) throws NotFoundException {
Map<ObjectIdentity, Acl> map = readAclsById(Arrays.asList(object), sids);
Assert.isTrue(map.containsKey(object), "There should have been an Acl entry for ObjectIdentity " + object);
return (Acl) map.get(object);
}
@Override
public Map<ObjectIdentity, Acl> readAclsById(List<ObjectIdentity> objects) throws NotFoundException {
return readAclsById(objects, null);
}
@Override
public Map<ObjectIdentity, Acl> readAclsById(List<ObjectIdentity> objects, List<Sid> sids) throws NotFoundException {
Assert.notEmpty(objects, "Objects to lookup required");
if (LOG.isDebugEnabled()) {
LOG.debug("BEGIN readAclById: objectIdentities: " + objects + ", sids: " + sids);
}
// contains FULLY loaded Acl objects
Map<ObjectIdentity, Acl> result = new HashMap<>();
List<ObjectIdentity> objectsToLookup = new ArrayList<>(objects);
// Check for Acls in the cache
if (aclCache != null) {
for (ObjectIdentity oi : objects) {
boolean aclLoaded = false;
Acl acl = aclCache.getFromCache(oi);
if (acl != null && acl.isSidLoaded(sids)) {
// Ensure any cached element supports all the requested SIDs
result.put(oi, acl);
aclLoaded = true;
}
if (aclLoaded) {
objectsToLookup.remove(oi);
}
}
}
if (!objectsToLookup.isEmpty()) {
Map<ObjectIdentity, Acl> loadedAcls = doLookup(objectsToLookup);
result.putAll(loadedAcls);
// Put loaded Acls in the cache
if (aclCache != null) {
for (Acl loadedAcl : loadedAcls.values()) {
aclCache.putInCache((AclImpl) loadedAcl);
}
}
}
for (ObjectIdentity oid : objects) {
if (!result.containsKey(oid)) {
throw new NotFoundException("Unable to find ACL information for object identity '" + oid + "'");
}
}
if (LOG.isDebugEnabled()) {
LOG.debug("END readAclById: acls: " + result.values());
}
return result;
}
/**
* Request Acls from the {@link AclRepository} and convert results.
*
* @param objects a list of {@link ObjectIdentity} objects to lookup.
* @return a map with {@link ObjectIdentity} instances as keys and
* {@link Acl} instances as values.
*/
private Map<ObjectIdentity, Acl> doLookup(List<ObjectIdentity> objects) {
Map<ObjectIdentity, Acl> result = new HashMap<>();
if (objects != null && !objects.isEmpty()) {
List<AclObjectIdentity> objectIds = new ArrayList<>();
for (ObjectIdentity objId : objects) {
objectIds.add(new AclObjectIdentity(objId));
}
Map<AclObjectIdentity, Set<AclEntry>> aeList = aclRepository.findAcls(objectIds);
Map<ObjectIdentity, Acl> parentAcls = lookupParents(aeList.keySet());
for (Entry<AclObjectIdentity, Set<AclEntry>> entry : aeList.entrySet()) {
Acl parentAcl = parentAcls.get(entry.getKey().getParentObjectIdentity());
AclImpl loadedAcl = convert(entry.getKey(), entry.getValue(), parentAcl);
result.put(loadedAcl.getObjectIdentity(), loadedAcl);
}
}
return result;
}
/**
* Finds the parents of the provided {@link ObjectIdentity} objects.
*
* @param acls a set of {@link AclObjectIdentity} objects to find the
* parents of.
* @return a map of the parents, with {@link ObjectIdentity} instances as
* keys and {@link Acl} instances as values.
*/
private Map<ObjectIdentity, Acl> lookupParents(Set<AclObjectIdentity> acls) {
List<ObjectIdentity> objectsToLookup = new ArrayList<>();
for (AclObjectIdentity aoi : acls) {
if (aoi.getParentObjectId() != null && !aoi.getParentObjectId().isEmpty()
&& aoi.getParentObjectClass() != null && !aoi.getParentObjectClass().isEmpty()) {
objectsToLookup.add(new ObjectIdentityImpl(aoi.getParentObjectClass(), aoi.getParentObjectId()));
}
}
return doLookup(objectsToLookup);
}
/**
* Creates an {@link AclImpl} instance out of the provided data.
*
* @param aclObjectIdentity the {@link AclObjectIdentity} holding the basic
* Acl data.
* @param aclEntries a set of {@link AclEntry} objects to be converted to
* {@link AccessControlEntry} objects.
* @param parentAcl the parent {@link Acl}.
* @return an {@link AclImpl} instance.
*/
private AclImpl convert(AclObjectIdentity aclObjectIdentity, Set<AclEntry> aclEntries, Acl parentAcl) {
AclImpl acl = new AclImpl(aclObjectIdentity.toObjectIdentity(), aclObjectIdentity.getId(),
aclAuthorizationStrategy, grantingStrategy, parentAcl, null, aclObjectIdentity.isEntriesInheriting(), aclObjectIdentity.getOwnerSid());
List<AccessControlEntry> aces = new ArrayList<>(aclEntries.size());
for (AclEntry entry : aclEntries) {
AccessControlEntry ace = new AccessControlEntryImpl(entry.getId(), acl, entry.getSidObject(), permissionFactory.buildFromMask(entry.getMask()),
entry.isGranting(), entry.isAuditSuccess(), entry.isAuditFailure());
aces.add(entry.getOrder(), ace);
}
try {
fieldAces.set(acl, aces);
} catch (Exception e) {
LOG.error("Could not set AccessControlEntries in the ACL", e);
}
return acl;
}
}