blob: f093629f28e0b211fbdfb7d41d877c5c5aedc83a [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.sling.jcr.jackrabbit.accessmanager;
import java.security.Principal;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Locale;
import java.util.Map;
import java.util.ResourceBundle;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
import javax.jcr.Node;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
import javax.jcr.Value;
import javax.jcr.ValueFactory;
import javax.jcr.ValueFormatException;
import javax.jcr.security.AccessControlManager;
import javax.jcr.security.Privilege;
import jakarta.json.JsonArray;
import jakarta.json.JsonObject;
import jakarta.json.JsonString;
import jakarta.json.JsonValue;
import jakarta.json.JsonValue.ValueType;
import org.apache.jackrabbit.api.JackrabbitSession;
import org.apache.jackrabbit.api.security.principal.PrincipalManager;
import org.apache.jackrabbit.oak.spi.security.authorization.restriction.RestrictionDefinition;
import org.apache.jackrabbit.oak.spi.security.authorization.restriction.RestrictionProvider;
import org.apache.jackrabbit.oak.spi.security.privilege.PrivilegeConstants;
import org.apache.sling.jcr.jackrabbit.accessmanager.impl.JsonConvert;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.FrameworkUtil;
import org.osgi.framework.ServiceReference;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Helper class to assist in the usage of access control from scripts.
*/
public class PrivilegesInfo {
private Logger logger = LoggerFactory.getLogger(getClass());
/**
* Return the supported Privileges for the specified node.
*
* @param node the node to check
* @return array of Privileges
* @throws RepositoryException if any errors reading the information
*/
public Privilege [] getSupportedPrivileges(Node node) throws RepositoryException {
return getSupportedPrivileges(node.getSession(), node.getPath());
}
/**
* Returns the supported privileges for the specified path.
*
* @param session the session for the current user
* @param absPath the path to get the privileges for
* @return array of Privileges
* @throws RepositoryException if any errors reading the information
*/
public Privilege [] getSupportedPrivileges(Session session, String absPath) throws RepositoryException {
AccessControlManager accessControlManager = session.getAccessControlManager();
return accessControlManager.getSupportedPrivileges(absPath);
}
/**
* Wrapper class that holds the set of Privileges that are granted
* and/or denied for a specific principal.
*/
public static class AccessRights {
private Set<Privilege> granted = new HashSet<>();
private Set<Privilege> denied = new HashSet<>();
private ResourceBundle resBundle = null;
private ResourceBundle getResourceBundle(Locale locale) {
if (resBundle == null || !resBundle.getLocale().equals(locale)) {
resBundle = ResourceBundle.getBundle(getClass().getPackage().getName() + ".PrivilegesResources", locale);
}
return resBundle;
}
public Set<Privilege> getGranted() {
return granted;
}
public Set<Privilege> getDenied() {
return denied;
}
public String getPrivilegeSetDisplayName(Locale locale) {
if (denied != null && !denied.isEmpty()) {
//if there are any denied privileges, then this is a custom privilege set
return getResourceBundle(locale).getString("privilegeset.custom");
} else {
if (granted.isEmpty()) {
//appears to have an empty privilege set
return getResourceBundle(locale).getString("privilegeset.none");
}
if (granted.size() == 1) {
//check if the single privilege is jcr:all or jcr:read
Iterator<Privilege> iterator = granted.iterator();
Privilege next = iterator.next();
if (PrivilegeConstants.JCR_ALL.equals(next.getName())) {
//full control privilege set
return getResourceBundle(locale).getString("privilegeset.all");
} else if (PrivilegeConstants.JCR_READ.equals(next.getName())) {
//readonly privilege set
return getResourceBundle(locale).getString("privilegeset.readonly");
}
} else if (granted.size() == 2) {
//check if the two privileges are jcr:read and jcr:write
Iterator<Privilege> iterator = granted.iterator();
Privilege next = iterator.next();
Privilege next2 = iterator.next();
if ( (PrivilegeConstants.JCR_READ.equals(next.getName()) && PrivilegeConstants.JCR_WRITE.equals(next2.getName())) ||
(PrivilegeConstants.JCR_READ.equals(next2.getName()) && PrivilegeConstants.JCR_WRITE.equals(next.getName())) ) {
//read/write privileges
return getResourceBundle(locale).getString("privilegeset.readwrite");
}
}
//some other set of privileges
return getResourceBundle(locale).getString("privilegeset.custom");
}
}
}
/**
* Returns the mapping of declared access rights that have been set for the resource at
* the given path.
*
* @param node the node to get the access rights for
* @return map of access rights. Key is the user/group principal, value contains the granted/denied privileges
* @throws RepositoryException if any errors reading the information
*/
public Map<Principal, AccessRights> getDeclaredAccessRights(Node node) throws RepositoryException {
return getDeclaredAccessRights(node.getSession(), node.getPath());
}
/**
* Returns the mapping of declared access rights that have been set for the resource at
* the given path.
*
* @param session the current user session.
* @param absPath the path of the resource to get the access rights for
* @return map of access rights. Key is the user/group principal, value contains the granted/denied privileges
* @throws RepositoryException if any errors reading the information
*/
public Map<Principal, AccessRights> getDeclaredAccessRights(Session session, String absPath) throws RepositoryException {
return toMap(session, useGetAcl(json -> {
try {
return json.getAcl(session, absPath);
} catch (RepositoryException e) {
logger.warn("Failed to load Acl", e);
}
return null;
}));
}
/**
* Convert the JSON acl to a map of Principal to AccessRights
* @param session the jcr session
* @param aclJson the acl JSON object
* @return map of Principal to AccessRights
*/
protected Map<Principal, AccessRights> toMap(Session session, JsonObject aclJson)
throws RepositoryException {
if (aclJson == null) {
return Collections.emptyMap();
}
Map<Principal, AccessRights> map;
AccessControlManager acm = session.getAccessControlManager();
PrincipalManager principalManager = ((JackrabbitSession)session).getPrincipalManager();
Function<? super JsonValue, ? extends Principal> keyMapper = val -> {
String principalId = ((JsonObject)val).getString(JsonConvert.KEY_PRINCIPAL);
return principalManager.getPrincipal(principalId);
};
Function<? super JsonValue, ? extends AccessRights> valueMapper = val -> {
AccessRights rights = new AccessRights();
JsonObject privilegesObj = ((JsonObject)val).getJsonObject(JsonConvert.KEY_PRIVILEGES);
if (privilegesObj != null) {
privilegesObj.entrySet().stream()
.forEach(entry -> {
Privilege privilege = null;
try {
privilege = acm.privilegeFromName(entry.getKey());
} catch (RepositoryException e) {
logger.warn("Failed to resolve privilege", e);
}
if (privilege != null) {
JsonValue value = entry.getValue();
if (value instanceof JsonObject) {
JsonObject privilegeObj = (JsonObject)value;
if (privilegeObj.containsKey(JsonConvert.KEY_ALLOW)) {
rights.granted.add(privilege);
}
if (privilegeObj.containsKey(JsonConvert.KEY_DENY)) {
rights.denied.add(privilege);
}
}
}
});
}
return rights;
};
map = aclJson.values().stream()
.collect(Collectors.toMap(keyMapper, valueMapper));
return map;
}
/**
* Returns the declared access rights for the specified Node for the given
* principalId.
*
* @param node the JCR node to retrieve the access rights for
* @param principalId the principalId to get the access rights for
* @return access rights for the specified principal
* @throws RepositoryException if any errors reading the information
*/
public AccessRights getDeclaredAccessRightsForPrincipal(Node node, String principalId) throws RepositoryException {
return getDeclaredAccessRightsForPrincipal(node.getSession(), node.getPath(), principalId);
}
/**
* Returns the declared access rights for the resource at the specified path for the given
* principalId.
*
* @param session the current JCR session
* @param absPath the path of the resource to retrieve the rights for
* @param principalId the principalId to get the access rights for
* @return access rights for the specified principal
* @throws RepositoryException if any errors reading the information
*/
public AccessRights getDeclaredAccessRightsForPrincipal(Session session, String absPath, String principalId) throws RepositoryException {
Map<Principal, AccessRights> declaredAccessRights = getDeclaredAccessRights(session, absPath);
PrincipalManager principalManager = ((JackrabbitSession)session).getPrincipalManager();
Principal principal = principalManager.getPrincipal(principalId);
return declaredAccessRights.get(principal);
}
/**
* Returns the restrictions for the specified path.
*
* @param node the node to inspect
* @param principalId the principalId to get the access rights for
* @return map of restrictions (key is restriction name, value is Value or Value[])
* @throws RepositoryException if any errors reading the information
* @deprecated don't use this as it assumes that all the privileges have the same restrictions which may not be true
*/
@Deprecated
public Map<String, Object> getDeclaredRestrictionsForPrincipal(Node node, String principalId) throws RepositoryException {
return getDeclaredRestrictionsForPrincipal(node.getSession(), node.getPath(), principalId);
}
/**
* Returns the restrictions for the specified path.
*
* @param session the session for the current user
* @param absPath the path to get the privileges for
* @param principalId the principalId to get the access rights for
* @return map of restrictions (key is restriction name, value is Value or Value[])
* @throws RepositoryException if any errors reading the information
* @deprecated don't use this as it assumes that all the privileges have the same restrictions which may not be true
*/
@Deprecated
public Map<String, Object> getDeclaredRestrictionsForPrincipal(Session session, String absPath, String principalId) throws RepositoryException {
JsonObject aclJson = useGetAcl(json -> {
try {
return json.getAcl(session, absPath);
} catch (RepositoryException e) {
logger.warn("Failed to load Acl", e);
}
return null;
});
Map<String, Object> map;
if (aclJson == null) {
map = Collections.emptyMap();
} else {
Map<String, RestrictionDefinition> srMap = new HashMap<>();
useRestrictionProvider(restrictionProvider -> {
Set<RestrictionDefinition> supportedRestrictions = restrictionProvider.getSupportedRestrictions(absPath);
for (RestrictionDefinition restrictionDefinition : supportedRestrictions) {
srMap.put(restrictionDefinition.getName(), restrictionDefinition);
}
return null;
});
ValueFactory valueFactory = session.getValueFactory();
map = new HashMap<>();
aclJson.values().stream()
.filter(val -> val instanceof JsonObject && ((JsonObject)val).getString(JsonConvert.KEY_PRINCIPAL).equals(principalId))
.forEach(item -> {
JsonObject privilegesObj = ((JsonObject)item).getJsonObject(JsonConvert.KEY_PRIVILEGES);
if (privilegesObj != null) {
privilegesObj.values()
.forEach(privItem -> {
if (privItem instanceof JsonObject) {
JsonObject privilegeObj = (JsonObject)privItem;
JsonValue jsonValue = privilegeObj.get(JsonConvert.KEY_ALLOW);
if (jsonValue instanceof JsonObject) {
JsonObject restriction = (JsonObject)jsonValue;
restriction.entrySet().stream()
.forEach(restrictionItem -> {
String restrictionName = restrictionItem.getKey();
int type = srMap.get(restrictionName).getRequiredType().tag();
JsonValue value = restrictionItem.getValue();
if (ValueType.ARRAY.equals(value.getValueType())) {
JsonArray jsonArray = ((JsonArray)value);
Value [] restrictionValues = new Value[jsonArray.size()];
for (int i=0; i < jsonArray.size(); i++) {
try {
restrictionValues[i] = valueFactory.createValue(jsonArray.getString(i), type);
} catch (ValueFormatException e) {
logger.warn("Failed to create restriction value", e);
}
}
map.put(restrictionName, restrictionValues);
} else if (value instanceof JsonString){
try {
Value restrictionValue = valueFactory.createValue(((JsonString)value).getString(), type);
map.put(restrictionName, restrictionValue);
} catch (ValueFormatException e) {
logger.warn("Failed to create restriction value", e);
}
}
});
}
}
});
}
});
}
return map;
}
/**
* Returns the mapping of effective access rights that have been set for the resource at
* the given path.
*
* @param node the node to get the access rights for
* @return map of access rights. Key is the user/group principal, value contains the granted/denied privileges
* @throws RepositoryException if any errors reading the information
*/
public Map<Principal, AccessRights> getEffectiveAccessRights(Node node) throws RepositoryException {
return getEffectiveAccessRights(node.getSession(), node.getPath());
}
/**
* Returns the mapping of effective access rights that have been set for the resource at
* the given path.
*
* @param session the current user session.
* @param absPath the path of the resource to get the access rights for
* @return map of access rights. Key is the user/group principal, value contains the granted/denied privileges
* @throws RepositoryException if any errors reading the information
*/
public Map<Principal, AccessRights> getEffectiveAccessRights(Session session, String absPath) throws RepositoryException {
return toMap(session, useGetEffectiveAcl(json -> {
try {
return json.getEffectiveAcl(session, absPath);
} catch (RepositoryException e) {
logger.warn("Failed to load EffectiveAcl", e);
}
return null;
}));
}
/**
* Returns the effective access rights for the specified Node for the given
* principalId.
*
* @param node the JCR node to retrieve the access rights for
* @param principalId the principalId to get the access rights for
* @return access rights for the specified principal
* @throws RepositoryException if any errors reading the information
*/
public AccessRights getEffectiveAccessRightsForPrincipal(Node node, String principalId) throws RepositoryException {
return getEffectiveAccessRightsForPrincipal(node.getSession(), node.getPath(), principalId);
}
/**
* Returns the effective access rights for the resource at the specified path for the given
* principalId.
*
* @param session the current JCR session
* @param absPath the path of the resource to retrieve the rights for
* @param principalId the principalId to get the access rights for
* @return access rights for the specified principal
* @throws RepositoryException if any errors reading the information
*/
public AccessRights getEffectiveAccessRightsForPrincipal(Session session, String absPath, String principalId) throws RepositoryException {
Map<Principal, AccessRights> effectiveAccessRights = getEffectiveAccessRights(session, absPath);
PrincipalManager principalManager = ((JackrabbitSession)session).getPrincipalManager();
Principal principal = principalManager.getPrincipal(principalId);
return effectiveAccessRights.get(principal);
}
/**
* Checks whether the current user has been granted privileges
* to add children to the specified node.
*
* @param node the node to check
* @return true if the current user has the privileges, false otherwise
*/
public boolean canAddChildren(Node node) {
try {
return canAddChildren(node.getSession(), node.getPath());
} catch (RepositoryException e) {
return false;
}
}
/**
* Checks whether the current user has been granted privileges
* to add children to the specified path.
*
* @param session the JCR session of the current user
* @param absPath the path of the resource to check
* @return true if the current user has the privileges, false otherwise
*/
public boolean canAddChildren(Session session, String absPath) {
try {
AccessControlManager accessControlManager = session.getAccessControlManager();
return accessControlManager.hasPrivileges(absPath, new Privilege[] {
accessControlManager.privilegeFromName(Privilege.JCR_ADD_CHILD_NODES)
});
} catch (RepositoryException e) {
return false;
}
}
/**
* Checks whether the current user has been granted privileges
* to delete children to the specified node.
*
* @param node the node to check
* @return true if the current user has the privileges, false otherwise
*/
public boolean canDeleteChildren(Node node) {
try {
return canDeleteChildren(node.getSession(), node.getPath());
} catch (RepositoryException e) {
return false;
}
}
/**
* Checks whether the current user has been granted privileges
* to delete children of the specified path.
*
* @param session the JCR session of the current user
* @param absPath the path of the resource to check
* @return true if the current user has the privileges, false otherwise
*/
public boolean canDeleteChildren(Session session, String absPath) {
try {
AccessControlManager accessControlManager = session.getAccessControlManager();
return accessControlManager.hasPrivileges(absPath, new Privilege[] {
accessControlManager.privilegeFromName(Privilege.JCR_REMOVE_CHILD_NODES)
});
} catch (RepositoryException e) {
return false;
}
}
/**
* Checks whether the current user has been granted privileges
* to delete the specified node.
*
* @param node the node to check
* @return true if the current user has the privileges, false otherwise
*/
public boolean canDelete(Node node) {
try {
return canDelete(node.getSession(), node.getPath());
} catch (RepositoryException e) {
return false;
}
}
/**
* Checks whether the current user has been granted privileges
* to delete the specified path.
*
* @param session the JCR session of the current user
* @param absPath the path of the resource to check
* @return true if the current user has the privileges, false otherwise
*/
public boolean canDelete(Session session, String absPath) {
try {
AccessControlManager accessControlManager = session.getAccessControlManager();
String parentPath;
int lastSlash = absPath.lastIndexOf('/');
if (lastSlash == 0) {
//the parent is the root folder.
parentPath = "/";
} else {
//strip the last segment
parentPath = absPath.substring(0, lastSlash);
}
return accessControlManager.hasPrivileges(absPath, new Privilege[] {
accessControlManager.privilegeFromName(Privilege.JCR_REMOVE_NODE)
}) && canDeleteChildren(session, parentPath);
} catch (RepositoryException e) {
return false;
}
}
/**
* Checks whether the current user has been granted privileges
* to modify properties of the specified node.
*
* @param node the node to check
* @return true if the current user has the privileges, false otherwise
*/
public boolean canModifyProperties(Node node) {
try {
return canModifyProperties(node.getSession(), node.getPath());
} catch (RepositoryException e) {
return false;
}
}
/**
* Checks whether the current user has been granted privileges
* to modify properties of the specified path.
*
* @param session the JCR session of the current user
* @param absPath the path of the resource to check
* @return true if the current user has the privileges, false otherwise
*/
public boolean canModifyProperties(Session session, String absPath) {
try {
AccessControlManager accessControlManager = session.getAccessControlManager();
return accessControlManager.hasPrivileges(absPath, new Privilege[] {
accessControlManager.privilegeFromName(Privilege.JCR_MODIFY_PROPERTIES)
});
} catch (RepositoryException e) {
return false;
}
}
/**
* Checks whether the current user has been granted privileges
* to read the access control of the specified node.
*
* @param node the node to check
* @return true if the current user has the privileges, false otherwise
*/
public boolean canReadAccessControl(Node node) {
try {
return canReadAccessControl(node.getSession(), node.getPath());
} catch (RepositoryException e) {
return false;
}
}
/**
* Checks whether the current user has been granted privileges
* to read the access control of the specified path.
*
* @param session the JCR session of the current user
* @param absPath the path of the resource to check
* @return true if the current user has the privileges, false otherwise
*/
public boolean canReadAccessControl(Session session, String absPath) {
try {
AccessControlManager accessControlManager = session.getAccessControlManager();
return accessControlManager.hasPrivileges(absPath, new Privilege[] {
accessControlManager.privilegeFromName(Privilege.JCR_READ_ACCESS_CONTROL)
});
} catch (RepositoryException e) {
return false;
}
}
/**
* Checks whether the current user has been granted privileges
* to modify the access control of the specified node.
*
* @param node the node to check
* @return true if the current user has the privileges, false otherwise
*/
public boolean canModifyAccessControl(Node node) {
try {
return canModifyAccessControl(node.getSession(), node.getPath());
} catch (RepositoryException e) {
return false;
}
}
/**
* Checks whether the current user has been granted privileges
* to modify the access control of the specified path.
*
* @param session the JCR session of the current user
* @param absPath the path of the resource to check
* @return true if the current user has the privileges, false otherwise
*/
public boolean canModifyAccessControl(Session session, String absPath) {
try {
AccessControlManager accessControlManager = session.getAccessControlManager();
return accessControlManager.hasPrivileges(absPath, new Privilege[] {
accessControlManager.privilegeFromName(Privilege.JCR_MODIFY_ACCESS_CONTROL)
});
} catch (RepositoryException e) {
return false;
}
}
/**
* Utility to lookup a service and then run a function
*
* @param <S> the service interface type
* @param <T> the return type of the fun
* @param svc the service class
* @param fn the function to invoke
* @return the value of invoking the fn
*/
private static <S, T> T useSvc(Class<S> svc, Function<S, T> fn) {
T value = null;
Bundle bundle = FrameworkUtil.getBundle(PrivilegesInfo.class);
if (bundle != null) {
BundleContext bundleContext = bundle.getBundleContext();
if (bundleContext != null) {
ServiceReference<S> serviceReference = bundleContext.getServiceReference(svc);
if (serviceReference != null) {
S service = null;
try {
service = bundleContext.getService(serviceReference);
if (service != null) {
value = fn.apply(service);
}
} finally {
if (service != null) {
bundleContext.ungetService(serviceReference);
}
}
}
}
}
return value;
}
private static <T> T useGetAcl(Function<GetAcl, T> fn) {
return useSvc(GetAcl.class, fn);
}
private static <T> T useGetEffectiveAcl(Function<GetEffectiveAcl, T> fn) {
return useSvc(GetEffectiveAcl.class, fn);
}
private static <T> T useRestrictionProvider(Function<RestrictionProvider, T> fn) {
return useSvc(RestrictionProvider.class, fn);
}
}