/*
* (C) Copyright 2006-2014 Nuxeo SA (http://nuxeo.com/) and others.
*
* 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.
*
* Contributors:
* Bogdan Stefanescu
* Florent Guillaume
*/
package org.nuxeo.ecm.core.api.security.impl;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.commons.lang.StringUtils;
import org.nuxeo.ecm.core.api.security.ACE;
import org.nuxeo.ecm.core.api.security.ACL;
import org.nuxeo.ecm.core.api.security.ACP;
import org.nuxeo.ecm.core.api.security.Access;
import org.nuxeo.ecm.core.api.security.SecurityConstants;
import org.nuxeo.ecm.core.api.security.UserEntry;
/**
* The ACP implementation uses a cache used when calling getAccess().
*/
public class ACPImpl implements ACP {
private static final long serialVersionUID = 1L;
private final List<ACL> acls;
private transient Map<String, Access> cache;
public ACPImpl() {
acls = new ArrayList<ACL>();
cache = new HashMap<String, Access>();
}
/**
* This method must append the ACL and not insert it since it is used to append the inherited ACL which is the less
* significant ACL.
*/
@Override
public void addACL(ACL acl) {
assert acl != null;
ACL oldACL = getACL(acl.getName());
if (!acl.equals(oldACL)) {
// replace existing ACL instance different from acl having the same
// name, if any
if (oldACL != null) {
oldACL.clear();
oldACL.addAll(acl);
} else {
String name = acl.getName();
switch (name) {
case ACL.INHERITED_ACL:
// add the inherited ACL always at the end
acls.add(acl);
break;
case ACL.LOCAL_ACL:
// add the local ACL before the inherited if any
ACL inherited = getACL(ACL.INHERITED_ACL);
if (inherited != null) {
int i = acls.indexOf(inherited);
acls.add(i, acl);
} else {
acls.add(acl);
}
break;
default:
ACL local = getACL(ACL.LOCAL_ACL);
if (local != null) {
int i = acls.indexOf(local);
acls.add(i, acl);
} else {
inherited = getACL(ACL.INHERITED_ACL);
if (inherited != null) {
int i = acls.indexOf(inherited);
acls.add(i, acl);
} else {
acls.add(acl);
}
}
}
}
}
// if oldACL and ACL are the same instance, we just need to clear
// the cache
cache.clear();
}
@Override
public void addACL(int pos, ACL acl) {
ACL oldACL = getACL(acl.getName());
if (oldACL != null) {
acls.remove(oldACL);
}
acls.add(pos, acl);
cache.clear();
}
@Override
public void addACL(String afterMe, ACL acl) {
if (afterMe == null) {
addACL(0, acl);
} else {
int i;
int len = acls.size();
for (i = 0; i < len; i++) {
if (acls.get(i).getName().equals(afterMe)) {
break;
}
}
addACL(i + 1, acl);
}
}
@Override
public ACL getACL(String name) {
if (name == null) {
name = ACL.LOCAL_ACL;
}
int len = acls.size();
for (int i = 0; i < len; i++) {
ACL acl = acls.get(i);
if (acl.getName().equals(name)) {
return acl;
}
}
return null;
}
@Override
public ACL[] getACLs() {
return acls.toArray(new ACL[acls.size()]);
}
@Override
public ACL getMergedACLs(String name) {
ACL mergedAcl = new ACLImpl(name, true);
for (ACL acl : acls) {
mergedAcl.addAll(acl);
}
return mergedAcl;
}
public static ACL newACL(String name) {
return new ACLImpl(name);
}
@Override
public ACL removeACL(String name) {
for (int i = 0, len = acls.size(); i < len; i++) {
ACL acl = acls.get(i);
if (acl.getName().equals(name)) {
cache.clear();
return acls.remove(i);
}
}
return null;
}
@Override
public Access getAccess(String principal, String permission) {
// check first the cache
String key = principal + ':' + permission;
Access access = cache.get(key);
if (access == null) {
access = Access.UNKNOWN;
FOUND_ACE: for (ACL acl : acls) {
for (ACE ace : acl) {
if (permissionsMatch(ace, permission) && principalsMatch(ace, principal)) {
access = ace.isGranted() ? Access.GRANT : Access.DENY;
break FOUND_ACE;
}
}
}
cache.put(key, access);
}
return access;
}
@Override
public Access getAccess(String[] principals, String[] permissions) {
for (ACL acl : acls) {
for (ACE ace : acl) {
// only check for effective ACEs
if (ace.isEffective()) {
// fully check ACE in turn against username/permissions
// and usergroups/permgroups
Access access = getAccess(ace, principals, permissions);
if (access != Access.UNKNOWN) {
return access;
}
}
}
}
return Access.UNKNOWN;
}
public static Access getAccess(ACE ace, String[] principals, String[] permissions) {
String acePerm = ace.getPermission();
String aceUser = ace.getUsername();
for (String principal : principals) {
if (principalsMatch(aceUser, principal)) {
// check permission match only if principal is matching
for (String permission : permissions) {
if (permissionsMatch(acePerm, permission)) {
return ace.isGranted() ? Access.GRANT : Access.DENY;
} // end permissionMatch
} // end perm for
} // end principalMatch
} // end princ for
return Access.UNKNOWN;
}
private static boolean permissionsMatch(ACE ace, String permission) {
String acePerm = ace.getPermission();
// RESTRICTED_READ needs special handling, is not implied by EVERYTHING.
if (!SecurityConstants.RESTRICTED_READ.equals(permission)) {
if (SecurityConstants.EVERYTHING.equals(acePerm)) {
return true;
}
}
return StringUtils.equals(acePerm, permission);
}
private static boolean permissionsMatch(String acePerm, String permission) {
// RESTRICTED_READ needs special handling, is not implied by EVERYTHING.
if (SecurityConstants.EVERYTHING.equals(acePerm)) {
if (!SecurityConstants.RESTRICTED_READ.equals(permission)) {
return true;
}
}
return StringUtils.equals(acePerm, permission);
}
private static boolean principalsMatch(ACE ace, String principal) {
String acePrincipal = ace.getUsername();
if (SecurityConstants.EVERYONE.equals(acePrincipal)) {
return true;
}
return StringUtils.equals(acePrincipal, principal);
}
private static boolean principalsMatch(String acePrincipal, String principal) {
if (SecurityConstants.EVERYONE.equals(acePrincipal)) {
return true;
}
return StringUtils.equals(acePrincipal, principal);
}
public void addAccessRule(String aclName, ACE ace) {
ACL acl = getACL(aclName);
if (acl == null) {
acl = new ACLImpl(aclName);
addACL(acl);
}
acl.add(ace);
}
@Override
public ACL getOrCreateACL(String name) {
ACL acl = getACL(name);
if (acl == null) {
acl = new ACLImpl(name);
addACL(acl);
}
return acl;
}
@Override
public ACL getOrCreateACL() {
return getOrCreateACL(ACL.LOCAL_ACL);
}
// Rules.
@Override
public void setRules(String aclName, UserEntry[] userEntries) {
setRules(aclName, userEntries, true);
}
@Override
public void setRules(String aclName, UserEntry[] userEntries, boolean overwrite) {
ACL acl = getACL(aclName);
if (acl == null) { // create the loca ACL
acl = new ACLImpl(aclName);
addACL(acl);
} else if (overwrite) {
// :XXX: Should not overwrite entries not given as parameters here.
acl.clear();
}
for (UserEntry entry : userEntries) {
String username = entry.getUserName();
for (String permission : entry.getGrantedPermissions()) {
acl.add(new ACE(username, permission, true));
}
for (String permission : entry.getDeniedPermissions()) {
acl.add(new ACE(username, permission, false));
}
}
cache.clear();
}
@Override
public void setRules(UserEntry[] userEntries) {
setRules(ACL.LOCAL_ACL, userEntries);
}
@Override
public void setRules(UserEntry[] userEntries, boolean overwrite) {
setRules(ACL.LOCAL_ACL, userEntries, overwrite);
}
// Serialization.
private void readObject(ObjectInputStream in) throws ClassNotFoundException, IOException {
// always perform the default de-serialization first
in.defaultReadObject();
// initialize cache to avoid NPE
cache = new HashMap<String, Access>();
}
/*
* NXP-1822 Rux: method for validating in one shot the users allowed to perform an oration. It gets the list of
* individual permissions which supposedly all grant.
*/
@Override
public String[] listUsernamesForAnyPermission(Set<String> perms) {
List<String> usernames = new ArrayList<String>();
ACL merged = getMergedACLs("merged");
for (ACE ace : merged.getACEs()) {
if (perms.contains(ace.getPermission()) && ace.isGranted()) {
String username = ace.getUsername();
if (!usernames.contains(username)) {
usernames.add(username);
}
}
}
return usernames.toArray(new String[usernames.size()]);
}
@Override
public ACPImpl clone() {
ACPImpl copy = new ACPImpl();
for (ACL acl : acls) {
copy.acls.add((ACL) acl.clone());
}
return copy;
}
@Override
public boolean blockInheritance(String aclName, String username) {
if (aclName == null) {
throw new NullPointerException("'aclName' cannot be null");
}
if (username == null) {
throw new NullPointerException("'username' cannot be null");
}
ACL acl = getOrCreateACL(aclName);
boolean aclChanged = acl.blockInheritance(username);
if (aclChanged) {
addACL(acl);
}
return aclChanged;
}
@Override
public boolean unblockInheritance(String aclName) {
if (aclName == null) {
throw new NullPointerException("'aclName' cannot be null");
}
ACL acl = getOrCreateACL(aclName);
boolean aclChanged = acl.unblockInheritance();
if (aclChanged) {
addACL(acl);
}
return aclChanged;
}
@Override
public boolean addACE(String aclName, ACE ace) {
if (aclName == null) {
throw new NullPointerException("'aclName' cannot be null");
}
ACL acl = getOrCreateACL(aclName);
boolean aclChanged = acl.add(ace);
if (aclChanged) {
addACL(acl);
}
return aclChanged;
}
@Override
public boolean replaceACE(String aclName, ACE oldACE, ACE newACE) {
if (aclName == null) {
throw new NullPointerException("'aclName' cannot be null");
}
ACL acl = getOrCreateACL(aclName);
boolean aclChanged = acl.replace(oldACE, newACE);
if (aclChanged) {
addACL(acl);
}
return aclChanged;
}
@Override
public boolean removeACE(String aclName, ACE ace) {
if (aclName == null) {
throw new NullPointerException("'aclName' cannot be null");
}
ACL acl = getOrCreateACL(aclName);
boolean aclChanged = acl.remove(ace);
if (aclChanged) {
addACL(acl);
}
return aclChanged;
}
@Override
public boolean removeACEsByUsername(String aclName, String username) {
if (aclName == null) {
throw new NullPointerException("'aclName' cannot be null");
}
ACL acl = getOrCreateACL(aclName);
boolean aclChanged = acl.removeByUsername(username);
if (aclChanged) {
addACL(acl);
}
return aclChanged;
}
@Override
public boolean removeACEsByUsername(String username) {
boolean changed = false;
for (ACL acl : acls) {
boolean aclChanged = acl.removeByUsername(username);
if (aclChanged) {
addACL(acl);
changed = true;
}
}
return changed;
}
}