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