/* * Copyright (c) 2006-2011 Nuxeo SA (http://nuxeo.com/) and others. * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Nuxeo - initial API and implementation * * $Id$ */ package org.eclipse.ecr.core.api.security.impl; import java.io.IOException; import java.io.ObjectInputStream; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import org.eclipse.ecr.core.api.security.ACE; import org.eclipse.ecr.core.api.security.ACL; import org.eclipse.ecr.core.api.security.ACP; import org.eclipse.ecr.core.api.security.Access; import org.eclipse.ecr.core.api.security.SecurityConstants; import org.eclipse.ecr.core.api.security.UserAccess; import org.eclipse.ecr.core.api.security.UserEntry; /** * The ACP implementation uses a cache used when calling getAccess(). * * @author <a href="mailto:bs@nuxeo.com">Bogdan Stefanescu</a> */ public class ACPImpl implements ACP { private static final long serialVersionUID = -2640696060701197284L; private final ArrayList<String> owners; private final List<ACL> acls; private transient Map<String, Access> cache; public ACPImpl() { owners = new ArrayList<String>(); acls = new ArrayList<ACL>(); cache = new HashMap<String, Access>(); } // Owners. @Override public String[] getOwners() { return owners.toArray(new String[owners.size()]); } @Override public boolean isOwner(String username) { return owners.contains(username); } @Override public void addOwner(String owner) { owners.add(owner); cache.clear(); } @Override public void removeOwner(String owner) { owners.remove(owner); cache.clear(); } @Override public void setOwners(String[] owners) { this.owners.clear(); this.owners.addAll(Arrays.asList(owners)); cache.clear(); } // ACLs. /** * 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 { 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) { 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) { // TODO: process owners 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) { // 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 (!permission.equals(SecurityConstants.RESTRICTED_READ)) { if (acePerm.equals(SecurityConstants.EVERYTHING)) { return true; } } return acePerm.equals(permission); } private static boolean permissionsMatch(String acePerm, String permission) { // RESTRICTED_READ needs special handling, is not implied by EVERYTHING. if (acePerm.equals(SecurityConstants.EVERYTHING)) { if (!permission.equals(SecurityConstants.RESTRICTED_READ)) { return true; } } return acePerm.equals(permission); } private static boolean principalsMatch(ACE ace, String principal) { String acePrincipal = ace.getUsername(); if (acePrincipal.equals(SecurityConstants.EVERYONE)) { return true; } return acePrincipal.equals(principal); } private static boolean principalsMatch(String aceUser, String principal) { if (aceUser.equals(SecurityConstants.EVERYONE)) { return true; } return aceUser.equals(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) { for (String permission : entry.getPermissions()) { UserAccess userAccess = entry.getAccess(permission); if (userAccess.isReadOnly()) { continue; // avoid setting read only rules } ACE ace = new ACE(entry.getUserName(), permission, userAccess.isGranted()); acl.add(ace); } } 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>(); } @Override public String[] listUsernamesForPermission(String perm) { List<String> usernames = new ArrayList<String>(); ACL merged = getMergedACLs("merged"); for (ACE ace : merged.getACEs()) { if (ace.getPermission().equals(perm) && ace.isGranted()) { String username = ace.getUsername(); if (!usernames.contains(username)) { usernames.add(ace.getUsername()); } } } return usernames.toArray(new String[usernames.size()]); } /* * 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()]); } @SuppressWarnings("unchecked") @Override public Object clone() { ACPImpl copy = new ACPImpl(); for (ACL acl : acls) { copy.acls.add((ACL) acl.clone()); } copy.owners.addAll((Collection<String>) owners.clone()); return copy; } }