001/* 002 * Copyright (c) 2006-2014 Nuxeo SA (http://nuxeo.com/) and others. 003 * 004 * All rights reserved. This program and the accompanying materials 005 * are made available under the terms of the Eclipse Public License v1.0 006 * which accompanies this distribution, and is available at 007 * http://www.eclipse.org/legal/epl-v10.html 008 * 009 * Contributors: 010 * Bogdan Stefanescu 011 * Florent Guillaume 012 */ 013package org.nuxeo.ecm.core.api.security.impl; 014 015import java.io.IOException; 016import java.io.ObjectInputStream; 017import java.util.ArrayList; 018import java.util.HashMap; 019import java.util.List; 020import java.util.Map; 021import java.util.Set; 022 023import org.apache.commons.lang.StringUtils; 024import org.nuxeo.ecm.core.api.security.ACE; 025import org.nuxeo.ecm.core.api.security.ACL; 026import org.nuxeo.ecm.core.api.security.ACP; 027import org.nuxeo.ecm.core.api.security.Access; 028import org.nuxeo.ecm.core.api.security.SecurityConstants; 029import org.nuxeo.ecm.core.api.security.UserEntry; 030 031/** 032 * The ACP implementation uses a cache used when calling getAccess(). 033 */ 034public class ACPImpl implements ACP { 035 036 private static final long serialVersionUID = 1L; 037 038 private final List<ACL> acls; 039 040 private transient Map<String, Access> cache; 041 042 public ACPImpl() { 043 acls = new ArrayList<ACL>(); 044 cache = new HashMap<String, Access>(); 045 } 046 047 /** 048 * This method must append the ACL and not insert it since it is used to append the inherited ACL which is the less 049 * significant ACL. 050 */ 051 @Override 052 public void addACL(ACL acl) { 053 assert acl != null; 054 ACL oldACL = getACL(acl.getName()); 055 if (!acl.equals(oldACL)) { 056 // replace existing ACL instance different from acl having the same 057 // name, if any 058 if (oldACL != null) { 059 oldACL.clear(); 060 oldACL.addAll(acl); 061 } else { 062 String name = acl.getName(); 063 switch (name) { 064 case ACL.INHERITED_ACL: 065 // add the inherited ACL always at the end 066 acls.add(acl); 067 break; 068 case ACL.LOCAL_ACL: 069 // add the local ACL before the inherited if any 070 ACL inherited = getACL(ACL.INHERITED_ACL); 071 if (inherited != null) { 072 int i = acls.indexOf(inherited); 073 acls.add(i, acl); 074 } else { 075 acls.add(acl); 076 } 077 break; 078 default: 079 ACL local = getACL(ACL.LOCAL_ACL); 080 if (local != null) { 081 int i = acls.indexOf(local); 082 acls.add(i, acl); 083 } else { 084 inherited = getACL(ACL.INHERITED_ACL); 085 if (inherited != null) { 086 int i = acls.indexOf(inherited); 087 acls.add(i, acl); 088 } else { 089 acls.add(acl); 090 } 091 } 092 } 093 } 094 } 095 // if oldACL and ACL are the same instance, we just need to clear 096 // the cache 097 cache.clear(); 098 } 099 100 @Override 101 public void addACL(int pos, ACL acl) { 102 ACL oldACL = getACL(acl.getName()); 103 if (oldACL != null) { 104 acls.remove(oldACL); 105 } 106 acls.add(pos, acl); 107 cache.clear(); 108 } 109 110 @Override 111 public void addACL(String afterMe, ACL acl) { 112 if (afterMe == null) { 113 addACL(0, acl); 114 } else { 115 int i; 116 int len = acls.size(); 117 for (i = 0; i < len; i++) { 118 if (acls.get(i).getName().equals(afterMe)) { 119 break; 120 } 121 } 122 addACL(i + 1, acl); 123 } 124 } 125 126 @Override 127 public ACL getACL(String name) { 128 if (name == null) { 129 name = ACL.LOCAL_ACL; 130 } 131 int len = acls.size(); 132 for (int i = 0; i < len; i++) { 133 ACL acl = acls.get(i); 134 if (acl.getName().equals(name)) { 135 return acl; 136 } 137 } 138 return null; 139 } 140 141 @Override 142 public ACL[] getACLs() { 143 return acls.toArray(new ACL[acls.size()]); 144 } 145 146 @Override 147 public ACL getMergedACLs(String name) { 148 ACL mergedAcl = new ACLImpl(name, true); 149 for (ACL acl : acls) { 150 mergedAcl.addAll(acl); 151 } 152 return mergedAcl; 153 } 154 155 public static ACL newACL(String name) { 156 return new ACLImpl(name); 157 } 158 159 @Override 160 public ACL removeACL(String name) { 161 for (int i = 0, len = acls.size(); i < len; i++) { 162 ACL acl = acls.get(i); 163 if (acl.getName().equals(name)) { 164 cache.clear(); 165 return acls.remove(i); 166 } 167 } 168 return null; 169 } 170 171 @Override 172 public Access getAccess(String principal, String permission) { 173 // check first the cache 174 String key = principal + ':' + permission; 175 Access access = cache.get(key); 176 if (access == null) { 177 access = Access.UNKNOWN; 178 FOUND_ACE: for (ACL acl : acls) { 179 for (ACE ace : acl) { 180 if (permissionsMatch(ace, permission) && principalsMatch(ace, principal)) { 181 access = ace.isGranted() ? Access.GRANT : Access.DENY; 182 break FOUND_ACE; 183 } 184 } 185 } 186 cache.put(key, access); 187 } 188 return access; 189 } 190 191 @Override 192 public Access getAccess(String[] principals, String[] permissions) { 193 for (ACL acl : acls) { 194 for (ACE ace : acl) { 195 // only check for effective ACEs 196 if (ace.isEffective()) { 197 // fully check ACE in turn against username/permissions 198 // and usergroups/permgroups 199 Access access = getAccess(ace, principals, permissions); 200 if (access != Access.UNKNOWN) { 201 return access; 202 } 203 } 204 } 205 } 206 return Access.UNKNOWN; 207 } 208 209 public static Access getAccess(ACE ace, String[] principals, String[] permissions) { 210 String acePerm = ace.getPermission(); 211 String aceUser = ace.getUsername(); 212 213 for (String principal : principals) { 214 if (principalsMatch(aceUser, principal)) { 215 // check permission match only if principal is matching 216 for (String permission : permissions) { 217 if (permissionsMatch(acePerm, permission)) { 218 return ace.isGranted() ? Access.GRANT : Access.DENY; 219 } // end permissionMatch 220 } // end perm for 221 } // end principalMatch 222 } // end princ for 223 return Access.UNKNOWN; 224 } 225 226 private static boolean permissionsMatch(ACE ace, String permission) { 227 String acePerm = ace.getPermission(); 228 229 // RESTRICTED_READ needs special handling, is not implied by EVERYTHING. 230 if (!SecurityConstants.RESTRICTED_READ.equals(permission)) { 231 if (SecurityConstants.EVERYTHING.equals(acePerm)) { 232 return true; 233 } 234 } 235 return StringUtils.equals(acePerm, permission); 236 } 237 238 private static boolean permissionsMatch(String acePerm, String permission) { 239 // RESTRICTED_READ needs special handling, is not implied by EVERYTHING. 240 if (SecurityConstants.EVERYTHING.equals(acePerm)) { 241 if (!SecurityConstants.RESTRICTED_READ.equals(permission)) { 242 return true; 243 } 244 } 245 return StringUtils.equals(acePerm, permission); 246 } 247 248 private static boolean principalsMatch(ACE ace, String principal) { 249 String acePrincipal = ace.getUsername(); 250 if (SecurityConstants.EVERYONE.equals(acePrincipal)) { 251 return true; 252 } 253 return StringUtils.equals(acePrincipal, principal); 254 } 255 256 private static boolean principalsMatch(String acePrincipal, String principal) { 257 if (SecurityConstants.EVERYONE.equals(acePrincipal)) { 258 return true; 259 } 260 return StringUtils.equals(acePrincipal, principal); 261 } 262 263 public void addAccessRule(String aclName, ACE ace) { 264 ACL acl = getACL(aclName); 265 if (acl == null) { 266 acl = new ACLImpl(aclName); 267 addACL(acl); 268 } 269 acl.add(ace); 270 } 271 272 @Override 273 public ACL getOrCreateACL(String name) { 274 ACL acl = getACL(name); 275 if (acl == null) { 276 acl = new ACLImpl(name); 277 addACL(acl); 278 } 279 return acl; 280 } 281 282 @Override 283 public ACL getOrCreateACL() { 284 return getOrCreateACL(ACL.LOCAL_ACL); 285 } 286 287 // Rules. 288 289 @Override 290 public void setRules(String aclName, UserEntry[] userEntries) { 291 setRules(aclName, userEntries, true); 292 } 293 294 @Override 295 public void setRules(String aclName, UserEntry[] userEntries, boolean overwrite) { 296 297 ACL acl = getACL(aclName); 298 if (acl == null) { // create the loca ACL 299 acl = new ACLImpl(aclName); 300 addACL(acl); 301 } else if (overwrite) { 302 // :XXX: Should not overwrite entries not given as parameters here. 303 acl.clear(); 304 } 305 for (UserEntry entry : userEntries) { 306 String username = entry.getUserName(); 307 for (String permission : entry.getGrantedPermissions()) { 308 acl.add(new ACE(username, permission, true)); 309 } 310 for (String permission : entry.getDeniedPermissions()) { 311 acl.add(new ACE(username, permission, false)); 312 } 313 } 314 cache.clear(); 315 } 316 317 @Override 318 public void setRules(UserEntry[] userEntries) { 319 setRules(ACL.LOCAL_ACL, userEntries); 320 } 321 322 @Override 323 public void setRules(UserEntry[] userEntries, boolean overwrite) { 324 setRules(ACL.LOCAL_ACL, userEntries, overwrite); 325 } 326 327 // Serialization. 328 329 private void readObject(ObjectInputStream in) throws ClassNotFoundException, IOException { 330 // always perform the default de-serialization first 331 in.defaultReadObject(); 332 // initialize cache to avoid NPE 333 cache = new HashMap<String, Access>(); 334 } 335 336 /* 337 * NXP-1822 Rux: method for validating in one shot the users allowed to perform an oration. It gets the list of 338 * individual permissions which supposedly all grant. 339 */ 340 @Override 341 public String[] listUsernamesForAnyPermission(Set<String> perms) { 342 List<String> usernames = new ArrayList<String>(); 343 ACL merged = getMergedACLs("merged"); 344 for (ACE ace : merged.getACEs()) { 345 if (perms.contains(ace.getPermission()) && ace.isGranted()) { 346 String username = ace.getUsername(); 347 if (!usernames.contains(username)) { 348 usernames.add(username); 349 } 350 } 351 } 352 return usernames.toArray(new String[usernames.size()]); 353 } 354 355 @Override 356 public ACPImpl clone() { 357 ACPImpl copy = new ACPImpl(); 358 for (ACL acl : acls) { 359 copy.acls.add((ACL) acl.clone()); 360 } 361 return copy; 362 } 363 364 @Override 365 public boolean blockInheritance(String aclName, String username) { 366 if (aclName == null) { 367 throw new NullPointerException("'aclName' cannot be null"); 368 } 369 if (username == null) { 370 throw new NullPointerException("'username' cannot be null"); 371 } 372 373 ACL acl = getOrCreateACL(aclName); 374 boolean aclChanged = acl.blockInheritance(username); 375 if (aclChanged) { 376 addACL(acl); 377 } 378 return aclChanged; 379 } 380 381 @Override 382 public boolean unblockInheritance(String aclName) { 383 if (aclName == null) { 384 throw new NullPointerException("'aclName' cannot be null"); 385 } 386 387 ACL acl = getOrCreateACL(aclName); 388 boolean aclChanged = acl.unblockInheritance(); 389 if (aclChanged) { 390 addACL(acl); 391 } 392 return aclChanged; 393 } 394 395 @Override 396 public boolean addACE(String aclName, ACE ace) { 397 if (aclName == null) { 398 throw new NullPointerException("'aclName' cannot be null"); 399 } 400 401 ACL acl = getOrCreateACL(aclName); 402 boolean aclChanged = acl.add(ace); 403 if (aclChanged) { 404 addACL(acl); 405 } 406 return aclChanged; 407 } 408 409 @Override 410 public boolean replaceACE(String aclName, ACE oldACE, ACE newACE) { 411 if (aclName == null) { 412 throw new NullPointerException("'aclName' cannot be null"); 413 } 414 415 ACL acl = getOrCreateACL(aclName); 416 boolean aclChanged = acl.replace(oldACE, newACE); 417 if (aclChanged) { 418 addACL(acl); 419 } 420 return aclChanged; 421 } 422 423 @Override 424 public boolean removeACE(String aclName, ACE ace) { 425 if (aclName == null) { 426 throw new NullPointerException("'aclName' cannot be null"); 427 } 428 429 ACL acl = getOrCreateACL(aclName); 430 boolean aclChanged = acl.remove(ace); 431 if (aclChanged) { 432 addACL(acl); 433 } 434 return aclChanged; 435 } 436 437 @Override 438 public boolean removeACEsByUsername(String aclName, String username) { 439 if (aclName == null) { 440 throw new NullPointerException("'aclName' cannot be null"); 441 } 442 443 ACL acl = getOrCreateACL(aclName); 444 boolean aclChanged = acl.removeByUsername(username); 445 if (aclChanged) { 446 addACL(acl); 447 } 448 return aclChanged; 449 } 450 451 @Override 452 public boolean removeACEsByUsername(String username) { 453 boolean changed = false; 454 for (ACL acl : acls) { 455 boolean aclChanged = acl.removeByUsername(username); 456 if (aclChanged) { 457 addACL(acl); 458 changed = true; 459 } 460 } 461 return changed; 462 } 463}