/*
* (C) Copyright 2015 Netcentric AG.
*
* 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
*/
package biz.netcentric.cq.tools.actool.helper;
import java.security.Principal;
import java.util.HashSet;
import java.util.Set;
import javax.jcr.AccessDeniedException;
import javax.jcr.PathNotFoundException;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
import javax.jcr.UnsupportedRepositoryOperationException;
import javax.jcr.security.AccessControlEntry;
import javax.jcr.security.AccessControlException;
import javax.jcr.security.AccessControlManager;
import javax.jcr.security.AccessControlPolicy;
import javax.jcr.security.AccessControlPolicyIterator;
import javax.jcr.security.Privilege;
import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.jackrabbit.api.JackrabbitSession;
import org.apache.jackrabbit.api.security.JackrabbitAccessControlEntry;
import org.apache.jackrabbit.api.security.JackrabbitAccessControlList;
import org.apache.jackrabbit.api.security.JackrabbitAccessControlManager;
import org.apache.jackrabbit.api.security.user.UserManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/** This class provides common access control related utilities. Mostly a copy of org.apache.jackrabbit.commons.AccessControlUtils. */
public class AccessControlUtils {
private static final Logger LOG = LoggerFactory.getLogger(AccessControlUtils.class);
private AccessControlUtils() {
}
/** Retrieves the {@link Privilege}s from the specified privilege names.
*
* @param session The editing session.
* @param privilegeNames The privilege names.
* @return An array of privileges.
* @throws RepositoryException If an error occurs or if {@code privilegeNames} contains an unknown/invalid privilege name. */
public static Privilege[] privilegesFromNames(Session session,
String... privilegeNames) throws RepositoryException {
return privilegesFromNames(session.getAccessControlManager(),
privilegeNames);
}
/** Retrieves the {@link Privilege}s from the specified privilege names.
*
* @param accessControlManager The access control manager.
* @param privilegeNames The privilege names.
* @return An array of privileges.
* @throws RepositoryException If an error occurs or if {@code privilegeNames} contains an unknown/invalid privilege name. */
public static Privilege[] privilegesFromNames(
AccessControlManager accessControlManager, String... privilegeNames)
throws RepositoryException {
final Set<Privilege> privileges = new HashSet<Privilege>(
privilegeNames.length);
for (final String privName : privilegeNames) {
privileges.add(accessControlManager.privilegeFromName(privName));
}
return privileges.toArray(new Privilege[privileges.size()]);
}
/** Retrieves the names of the specified privileges.
*
* @param privileges One or more privileges.
* @return The names of the specified privileges. */
public static String[] namesFromPrivileges(Privilege... privileges) {
if ((privileges == null) || (privileges.length == 0)) {
return new String[0];
} else {
final String[] names = new String[privileges.length];
for (int i = 0; i < privileges.length; i++) {
names[i] = privileges[i].getName();
}
return names;
}
}
/** Utility that combines {@link AccessControlManager#getApplicablePolicies(String)} and
* {@link AccessControlManager#getPolicies(String)} to retrieve a modifiable {@code JackrabbitAccessControlList} for the given path.<br>
*
* Note that the policy must be {@link AccessControlManager#setPolicy(String, javax.jcr.security.AccessControlPolicy) reapplied} and the
* changes must be saved in order to make the AC modifications take effect.
*
* @param session The editing session.
* @param absPath The absolute path of the target node.
* @return A modifiable access control list or null if there is none.
* @throws RepositoryException If an error occurs. */
public static JackrabbitAccessControlList getAccessControlList(
Session session, String absPath) throws RepositoryException {
final AccessControlManager acMgr = session.getAccessControlManager();
return getAccessControlList(acMgr, absPath);
}
public static JackrabbitAccessControlList getAccessControlPolicies(
Session session, Principal principal)
throws UnsupportedRepositoryOperationException, RepositoryException {
final JackrabbitAccessControlManager acMgr = (JackrabbitAccessControlManager) session
.getAccessControlManager();
if (acMgr.getPolicies(principal).length > 0) {
final JackrabbitAccessControlList jACL = (JackrabbitAccessControlList) acMgr
.getPolicies(principal)[0];
return jACL;
}
return null;
}
/** Utility that combines {@link AccessControlManager#getApplicablePolicies(String)} and
* {@link AccessControlManager#getPolicies(String)} to retrieve a modifiable {@code JackrabbitAccessControlList} for the given path.<br>
*
* Note that the policy must be {@link AccessControlManager#setPolicy(String, javax.jcr.security.AccessControlPolicy) reapplied} and the
* changes must be saved in order to make the AC modifications take effect.
*
* @param accessControlManager The {@code AccessControlManager} .
* @param absPath The absolute path of the target node.
* @return A modifiable access control list or null if there is none.
* @throws RepositoryException If an error occurs. */
public static JackrabbitAccessControlList getAccessControlList(
AccessControlManager accessControlManager, String absPath)
throws RepositoryException {
// try applicable (new) ACLs
final AccessControlPolicyIterator itr = accessControlManager
.getApplicablePolicies(absPath);
while (itr.hasNext()) {
final AccessControlPolicy policy = itr.nextAccessControlPolicy();
if (policy instanceof JackrabbitAccessControlList) {
return (JackrabbitAccessControlList) policy;
}
}
// try if there is an acl that has been set before
final AccessControlPolicy[] pcls = accessControlManager.getPolicies(absPath);
for (final AccessControlPolicy policy : pcls) {
if (policy instanceof JackrabbitAccessControlList) {
return (JackrabbitAccessControlList) policy;
}
}
// no policy found
LOG.warn("no policy found for path: {}", absPath);
return null;
}
/** @param session admin session
* @param path valid node path in CRX
* @param principalNames principal names of authorizables to be deleted from ACL of node specified by path
* @return count ACEs that were removed */
public static int deleteAllEntriesForPrincipalsFromACL(final Session session,
String path, String[] principalNames)
throws UnsupportedRepositoryOperationException, RepositoryException {
final AccessControlManager accessControlManager = session.getAccessControlManager();
if (StringUtils.isBlank(path)) {
path = null; // for repository permissions null needs to be used
}
final JackrabbitAccessControlList acl = AccessControlUtils.getModifiableAcl(accessControlManager, path);
if (acl == null) {
// do nothing, if there is no content node at the given path
return 0;
}
// get ACEs of the node
final AccessControlEntry[] aces = acl.getAccessControlEntries();
int countRemoved = 0;
// loop thorough ACEs and find the one of the given principal
for (final AccessControlEntry ace : aces) {
final JackrabbitAccessControlEntry jace = (JackrabbitAccessControlEntry) ace;
String principalNameInCurrentAce = jace.getPrincipal().getName();
if (ArrayUtils.contains(principalNames, principalNameInCurrentAce)) {
acl.removeAccessControlEntry(jace);
countRemoved++;
}
}
if (countRemoved > 0) {
// bind new policy
if (!acl.isEmpty()) {
accessControlManager.setPolicy(path, acl);
} else {
accessControlManager.removePolicy(path, acl);
}
}
return countRemoved;
}
/** Retrieves JackrabbitAccessControlList for path.
*
* @param acMgr
* @param path
* @return
* @throws RepositoryException
* @throws AccessDeniedException */
public static JackrabbitAccessControlList getModifiableAcl(
AccessControlManager acMgr, String path) throws RepositoryException, AccessDeniedException {
if (StringUtils.isBlank(path)) {
path = null; // repository level permission
}
AccessControlPolicy[] existing = null;
try {
existing = acMgr.getPolicies(path);
} catch (final PathNotFoundException e) {
LOG.debug("No node could be found under: {}. Application of ACL for that node cancelled!", path);
}
if (existing != null) {
for (final AccessControlPolicy p : existing) {
if (p instanceof JackrabbitAccessControlList) {
return ((JackrabbitAccessControlList) p);
}
}
final AccessControlPolicyIterator it = acMgr.getApplicablePolicies(path);
while (it.hasNext()) {
final AccessControlPolicy p = it.nextAccessControlPolicy();
if (p instanceof JackrabbitAccessControlList) {
return ((JackrabbitAccessControlList) p);
}
}
throw new AccessControlException("No modifiable ACL at " + path);
}
return null;
}
/** Returns user manager for session disabling autoSave if applicable.
*
* @param session
* @return
* @throws AccessDeniedException
* @throws UnsupportedRepositoryOperationException
* @throws RepositoryException */
public static UserManager getUserManagerAutoSaveDisabled(Session session)
throws AccessDeniedException, UnsupportedRepositoryOperationException, RepositoryException {
JackrabbitSession js = (JackrabbitSession) session;
UserManager userManager = js.getUserManager();
// Since the persistence of the installation should only take place if
// no error occured and certain test were successful
// the autosave gets disabled. Therefore an explicit session.save() is
// necessary to persist the changes.
// Try do disable the autosave only in case if changes are automatically persisted
if (userManager.isAutoSave()) {
try {
userManager.autoSave(false);
} catch (UnsupportedRepositoryOperationException e) {
// check added for AEM 6.0
LOG.warn("disabling autoSave not possible with this user manager!");
}
}
return userManager;
}
}