blob: 368596956074b19e067dc4bbb6391275c356ae31 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.
*/
package org.apache.jackrabbit.oak.security.authorization.accesscontrol;
import java.security.Principal;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.jcr.RepositoryException;
import javax.jcr.Value;
import javax.jcr.security.AccessControlEntry;
import javax.jcr.security.AccessControlException;
import javax.jcr.security.Privilege;
import com.google.common.base.Predicate;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import org.apache.jackrabbit.api.security.authorization.PrivilegeManager;
import org.apache.jackrabbit.oak.namepath.NamePathMapper;
import org.apache.jackrabbit.oak.spi.security.authorization.accesscontrol.ACE;
import org.apache.jackrabbit.oak.spi.security.authorization.accesscontrol.AbstractAccessControlList;
import org.apache.jackrabbit.oak.spi.security.authorization.restriction.Restriction;
import org.apache.jackrabbit.oak.spi.security.authorization.restriction.RestrictionDefinition;
import org.apache.jackrabbit.oak.spi.security.privilege.PrivilegeBits;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
abstract class ACL extends AbstractAccessControlList {
private static final Logger log = LoggerFactory.getLogger(ACL.class);
private final List<ACE> entries = new ArrayList<ACE>();
ACL(@Nullable String oakPath, @Nullable List<ACE> entries,
@NotNull NamePathMapper namePathMapper) {
super(oakPath, namePathMapper);
if (entries != null) {
this.entries.addAll(entries);
}
}
abstract ACE createACE(Principal principal, PrivilegeBits privilegeBits, boolean isAllow, Set<Restriction> restrictions) throws RepositoryException;
abstract boolean checkValidPrincipal(Principal principal) throws AccessControlException;
abstract PrivilegeManager getPrivilegeManager();
abstract PrivilegeBits getPrivilegeBits(Privilege[] privileges);
//------------------------------------------< AbstractAccessControlList >---
@NotNull
@Override
public List<ACE> getEntries() {
return entries;
}
//--------------------------------------------------< AccessControlList >---
@Override
public void removeAccessControlEntry(AccessControlEntry ace) throws RepositoryException {
ACE entry = checkACE(ace);
if (!entries.remove(entry)) {
throw new AccessControlException("Cannot remove AccessControlEntry " + ace);
}
}
//----------------------------------------< JackrabbitAccessControlList >---
@Override
public boolean addEntry(Principal principal, Privilege[] privileges,
boolean isAllow, Map<String, Value> restrictions,
Map<String, Value[]> mvRestrictions) throws RepositoryException {
if (privileges == null || privileges.length == 0) {
throw new AccessControlException("Privileges may not be null nor an empty array");
}
for (Privilege p : privileges) {
Privilege pv = getPrivilegeManager().getPrivilege(p.getName());
if (pv.isAbstract()) {
throw new AccessControlException("Privilege " + p + " is abstract.");
}
}
if (!checkValidPrincipal(principal)) {
return false;
}
for (RestrictionDefinition def : getRestrictionProvider().getSupportedRestrictions(getOakPath())) {
String jcrName = getNamePathMapper().getJcrName(def.getName());
if (def.isMandatory() && (restrictions == null || !restrictions.containsKey(jcrName))) {
throw new AccessControlException("Mandatory restriction " + jcrName + " is missing.");
}
}
Set<Restriction> rs;
if (restrictions == null && mvRestrictions == null) {
rs = Collections.emptySet();
} else {
rs = new HashSet<Restriction>();
if (restrictions != null) {
for (String jcrName : restrictions.keySet()) {
String oakName = getNamePathMapper().getOakName(jcrName);
rs.add(getRestrictionProvider().createRestriction(getOakPath(), oakName, restrictions.get(oakName)));
}
}
if (mvRestrictions != null) {
for (String jcrName : mvRestrictions.keySet()) {
String oakName = getNamePathMapper().getOakName(jcrName);
rs.add(getRestrictionProvider().createRestriction(getOakPath(), oakName, mvRestrictions.get(oakName)));
}
}
}
ACE entry = createACE(principal, getPrivilegeBits(privileges), isAllow, rs);
if (entries.contains(entry)) {
log.debug("Entry is already contained in policy -> no modification.");
return false;
} else {
return internalAddEntry(entry);
}
}
@Override
public void orderBefore(AccessControlEntry srcEntry, AccessControlEntry destEntry) throws RepositoryException {
ACE src = checkACE(srcEntry);
ACE dest = (destEntry == null) ? null : checkACE(destEntry);
if (src.equals(dest)) {
log.debug("'srcEntry' equals 'destEntry' -> no reordering required.");
return;
}
int index = (dest == null) ? entries.size() - 1 : entries.indexOf(dest);
if (index < 0) {
throw new AccessControlException("'destEntry' not contained in this AccessControlList.");
} else {
if (entries.remove(src)) {
// re-insert the srcEntry at the new position.
entries.add(index, src);
} else {
// src entry not contained in this list.
throw new AccessControlException("srcEntry not contained in this AccessControlList");
}
}
}
//-------------------------------------------------------------< Object >---
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("ACL: ").append(getPath()).append("; ACEs: ");
for (AccessControlEntry ace : entries) {
sb.append(ace.toString()).append(';');
}
return sb.toString();
}
//------------------------------------------------------------< private >---
/**
* Check validity of the specified access control entry.
*
* @param entry The access control entry to test.
* @return The validated {@code ACE}.
* @throws AccessControlException If the specified entry is invalid.
*/
private static ACE checkACE(AccessControlEntry entry) throws AccessControlException {
if (!(entry instanceof ACE)) {
throw new AccessControlException("Invalid access control entry.");
}
return (ACE) entry;
}
private boolean internalAddEntry(@NotNull ACE entry) throws RepositoryException {
final Principal principal = entry.getPrincipal();
List<ACE> subList = Lists.newArrayList(Iterables.filter(entries, new Predicate<ACE>() {
@Override
public boolean apply(@Nullable ACE ace) {
return (ace != null) && ace.getPrincipal().getName().equals(principal.getName());
}
}));
boolean addEntry = true;
for (ACE existing : subList) {
PrivilegeBits existingBits = PrivilegeBits.getInstance(existing.getPrivilegeBits());
PrivilegeBits entryBits = entry.getPrivilegeBits();
if (entry.getRestrictions().equals(existing.getRestrictions())) {
if (entry.isAllow() == existing.isAllow()) {
if (existingBits.includes(entryBits)) {
// no changes
return false;
} else {
// merge existing and new ace
existingBits.add(entryBits);
int index = entries.indexOf(existing);
entries.remove(existing);
entries.add(index, createACE(existing, existingBits));
addEntry = false;
}
} else {
// existing is complementary entry -> clean up redundant
// privileges defined by the existing entry
PrivilegeBits updated = PrivilegeBits.getInstance(existingBits).diff(entryBits);
if (updated.isEmpty()) {
// remove the existing entry as the new entry covers all privileges
entries.remove(existing);
} else if (!updated.includes(existingBits)) {
// replace the existing entry having it's privileges adjusted
int index = entries.indexOf(existing);
entries.remove(existing);
entries.add(index, createACE(existing, updated));
} /* else: no collision that requires adjusting the existing entry.*/
}
}
}
// finally add the new entry at the end of the list
if (addEntry) {
entries.add(entry);
}
return true;
}
private ACE createACE(@NotNull ACE existing, @NotNull PrivilegeBits newPrivilegeBits) throws RepositoryException {
return createACE(existing.getPrincipal(), newPrivilegeBits, existing.isAllow(), existing.getRestrictions());
}
}