001/* 002 * (C) Copyright 2006-2018 Nuxeo (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.lang3.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<>(); 051 cache = new HashMap<>(); 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 String localName = name == null ? ACL.LOCAL_ACL : name; 136 return acls.stream().filter(acl -> acl.getName().equals(localName)).findFirst().orElse(null); 137 } 138 139 @Override 140 public ACL[] getACLs() { 141 return acls.toArray(new ACL[acls.size()]); 142 } 143 144 @Override 145 public ACL getMergedACLs(String name) { 146 ACL mergedAcl = new ACLImpl(name, true); 147 for (ACL acl : acls) { 148 mergedAcl.addAll(acl); 149 } 150 return mergedAcl; 151 } 152 153 public static ACL newACL(String name) { 154 return new ACLImpl(name); 155 } 156 157 @Override 158 public ACL removeACL(String name) { 159 for (int i = 0, len = acls.size(); i < len; i++) { 160 ACL acl = acls.get(i); 161 if (acl.getName().equals(name)) { 162 cache.clear(); 163 return acls.remove(i); 164 } 165 } 166 return null; 167 } 168 169 @Override 170 public Access getAccess(String principal, String permission) { 171 // check first the cache 172 String key = principal + ':' + permission; 173 Access access = cache.get(key); 174 if (access == null) { 175 access = Access.UNKNOWN; 176 FOUND_ACE: for (ACL acl : acls) { 177 for (ACE ace : acl) { 178 if (permissionsMatch(ace, permission) && principalsMatch(ace, principal)) { 179 access = ace.isGranted() ? Access.GRANT : Access.DENY; 180 break FOUND_ACE; 181 } 182 } 183 } 184 cache.put(key, access); 185 } 186 return access; 187 } 188 189 @Override 190 public Access getAccess(String[] principals, String[] permissions) { 191 for (ACL acl : acls) { 192 for (ACE ace : acl) { 193 // only check for effective ACEs 194 if (ace.isEffective()) { 195 // fully check ACE in turn against username/permissions 196 // and usergroups/permgroups 197 Access access = getAccess(ace, principals, permissions); 198 if (access != Access.UNKNOWN) { 199 return access; 200 } 201 } 202 } 203 } 204 return Access.UNKNOWN; 205 } 206 207 public static Access getAccess(ACE ace, String[] principals, String[] permissions) { 208 String acePerm = ace.getPermission(); 209 String aceUser = ace.getUsername(); 210 211 for (String principal : principals) { 212 if (principalsMatch(aceUser, principal)) { 213 // check permission match only if principal is matching 214 for (String permission : permissions) { 215 if (permissionsMatch(acePerm, permission)) { 216 return ace.isGranted() ? Access.GRANT : Access.DENY; 217 } // end permissionMatch 218 } // end perm for 219 } // end principalMatch 220 } // end princ for 221 return Access.UNKNOWN; 222 } 223 224 private static boolean permissionsMatch(ACE ace, String permission) { 225 String acePerm = ace.getPermission(); 226 227 // RESTRICTED_READ needs special handling, is not implied by EVERYTHING. 228 if (!SecurityConstants.RESTRICTED_READ.equals(permission)) { 229 if (SecurityConstants.EVERYTHING.equals(acePerm)) { 230 return true; 231 } 232 } 233 return StringUtils.equals(acePerm, permission); 234 } 235 236 private static boolean permissionsMatch(String acePerm, String permission) { 237 // RESTRICTED_READ needs special handling, is not implied by EVERYTHING. 238 if (SecurityConstants.EVERYTHING.equals(acePerm)) { 239 if (!SecurityConstants.RESTRICTED_READ.equals(permission)) { 240 return true; 241 } 242 } 243 return StringUtils.equals(acePerm, permission); 244 } 245 246 private static boolean principalsMatch(ACE ace, String principal) { 247 String acePrincipal = ace.getUsername(); 248 if (SecurityConstants.EVERYONE.equals(acePrincipal)) { 249 return true; 250 } 251 return StringUtils.equals(acePrincipal, principal); 252 } 253 254 private static boolean principalsMatch(String acePrincipal, String principal) { 255 if (SecurityConstants.EVERYONE.equals(acePrincipal)) { 256 return true; 257 } 258 return StringUtils.equals(acePrincipal, principal); 259 } 260 261 public void addAccessRule(String aclName, ACE ace) { 262 ACL acl = getACL(aclName); 263 if (acl == null) { 264 acl = new ACLImpl(aclName); 265 addACL(acl); 266 } 267 acl.add(ace); 268 } 269 270 @Override 271 public ACL getOrCreateACL(String name) { 272 ACL acl = getACL(name); 273 if (acl == null) { 274 acl = new ACLImpl(name); 275 addACL(acl); 276 } 277 return acl; 278 } 279 280 @Override 281 public ACL getOrCreateACL() { 282 return getOrCreateACL(ACL.LOCAL_ACL); 283 } 284 285 // Rules. 286 287 @Override 288 public void setRules(String aclName, UserEntry[] userEntries) { 289 setRules(aclName, userEntries, true); 290 } 291 292 @Override 293 public void setRules(String aclName, UserEntry[] userEntries, boolean overwrite) { 294 295 ACL acl = getACL(aclName); 296 if (acl == null) { // create the loca ACL 297 acl = new ACLImpl(aclName); 298 addACL(acl); 299 } else if (overwrite) { 300 // :XXX: Should not overwrite entries not given as parameters here. 301 acl.clear(); 302 } 303 for (UserEntry entry : userEntries) { 304 String username = entry.getUserName(); 305 for (String permission : entry.getGrantedPermissions()) { 306 acl.add(new ACE(username, permission, true)); 307 } 308 for (String permission : entry.getDeniedPermissions()) { 309 acl.add(new ACE(username, permission, false)); 310 } 311 } 312 cache.clear(); 313 } 314 315 @Override 316 public void setRules(UserEntry[] userEntries) { 317 setRules(ACL.LOCAL_ACL, userEntries); 318 } 319 320 @Override 321 public void setRules(UserEntry[] userEntries, boolean overwrite) { 322 setRules(ACL.LOCAL_ACL, userEntries, overwrite); 323 } 324 325 // Serialization. 326 327 private void readObject(ObjectInputStream in) throws ClassNotFoundException, IOException { 328 // always perform the default de-serialization first 329 in.defaultReadObject(); 330 // initialize cache to avoid NPE 331 cache = new HashMap<>(); 332 } 333 334 /* 335 * NXP-1822 Rux: method for validating in one shot the users allowed to perform an oration. It gets the list of 336 * individual permissions which supposedly all grant. 337 */ 338 @Override 339 public String[] listUsernamesForAnyPermission(Set<String> perms) { 340 List<String> usernames = new ArrayList<>(); 341 ACL merged = getMergedACLs("merged"); 342 for (ACE ace : merged.getACEs()) { 343 if (perms.contains(ace.getPermission()) && ace.isGranted()) { 344 String username = ace.getUsername(); 345 if (!usernames.contains(username)) { 346 usernames.add(username); 347 } 348 } 349 } 350 return usernames.toArray(new String[usernames.size()]); 351 } 352 353 @Override 354 public ACPImpl clone() { 355 ACPImpl copy = new ACPImpl(); 356 for (ACL acl : acls) { 357 copy.acls.add((ACL) acl.clone()); 358 } 359 return copy; 360 } 361 362 @Override 363 public boolean blockInheritance(String aclName, String username) { 364 if (aclName == null) { 365 throw new NullPointerException("'aclName' cannot be null"); 366 } 367 if (username == null) { 368 throw new NullPointerException("'username' cannot be null"); 369 } 370 371 ACL acl = getOrCreateACL(aclName); 372 boolean aclChanged = acl.blockInheritance(username); 373 if (aclChanged) { 374 addACL(acl); 375 } 376 return aclChanged; 377 } 378 379 @Override 380 public boolean unblockInheritance(String aclName) { 381 if (aclName == null) { 382 throw new NullPointerException("'aclName' cannot be null"); 383 } 384 385 ACL acl = getOrCreateACL(aclName); 386 boolean aclChanged = acl.unblockInheritance(); 387 if (aclChanged) { 388 addACL(acl); 389 } 390 return aclChanged; 391 } 392 393 @Override 394 public boolean addACE(String aclName, ACE ace) { 395 if (aclName == null) { 396 throw new NullPointerException("'aclName' cannot be null"); 397 } 398 399 ACL acl = getOrCreateACL(aclName); 400 boolean aclChanged = acl.add(ace); 401 if (aclChanged) { 402 addACL(acl); 403 } 404 return aclChanged; 405 } 406 407 @Override 408 public boolean replaceACE(String aclName, ACE oldACE, ACE newACE) { 409 if (aclName == null) { 410 throw new NullPointerException("'aclName' cannot be null"); 411 } 412 413 ACL acl = getOrCreateACL(aclName); 414 boolean aclChanged = acl.replace(oldACE, newACE); 415 if (aclChanged) { 416 addACL(acl); 417 } 418 return aclChanged; 419 } 420 421 @Override 422 public boolean removeACE(String aclName, ACE ace) { 423 if (aclName == null) { 424 throw new NullPointerException("'aclName' cannot be null"); 425 } 426 427 ACL acl = getOrCreateACL(aclName); 428 boolean aclChanged = acl.remove(ace); 429 if (aclChanged) { 430 addACL(acl); 431 } 432 return aclChanged; 433 } 434 435 @Override 436 public boolean removeACEsByUsername(String aclName, String username) { 437 if (aclName == null) { 438 throw new NullPointerException("'aclName' cannot be null"); 439 } 440 441 ACL acl = getOrCreateACL(aclName); 442 boolean aclChanged = acl.removeByUsername(username); 443 if (aclChanged) { 444 addACL(acl); 445 } 446 return aclChanged; 447 } 448 449 @Override 450 public boolean removeACEsByUsername(String username) { 451 boolean changed = false; 452 for (ACL acl : acls) { 453 boolean aclChanged = acl.removeByUsername(username); 454 if (aclChanged) { 455 addACL(acl); 456 changed = true; 457 } 458 } 459 return changed; 460 } 461}