| /* |
| * 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.post; |
| |
| import java.security.Principal; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.Comparator; |
| import java.util.EnumMap; |
| import java.util.Enumeration; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Map.Entry; |
| import java.util.Set; |
| import java.util.regex.Matcher; |
| import java.util.regex.Pattern; |
| import java.util.stream.Collectors; |
| import java.util.stream.Stream; |
| |
| import javax.jcr.RepositoryException; |
| import javax.jcr.Session; |
| import javax.jcr.UnsupportedRepositoryOperationException; |
| import javax.jcr.Value; |
| import javax.jcr.ValueFactory; |
| import javax.jcr.security.AccessControlEntry; |
| import javax.jcr.security.AccessControlException; |
| import javax.jcr.security.AccessControlList; |
| import javax.jcr.security.AccessControlManager; |
| import javax.jcr.security.AccessControlPolicy; |
| import javax.jcr.security.AccessControlPolicyIterator; |
| import javax.jcr.security.Privilege; |
| import javax.servlet.Servlet; |
| |
| import org.apache.jackrabbit.api.security.JackrabbitAccessControlEntry; |
| import org.apache.jackrabbit.api.security.JackrabbitAccessControlList; |
| import org.apache.jackrabbit.api.security.authorization.PrincipalAccessControlList; |
| 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.api.SlingHttpServletRequest; |
| import org.apache.sling.jcr.base.util.AccessControlUtil; |
| import org.apache.sling.jcr.jackrabbit.accessmanager.LocalPrivilege; |
| import org.apache.sling.jcr.jackrabbit.accessmanager.LocalRestriction; |
| import org.apache.sling.jcr.jackrabbit.accessmanager.ModifyAce; |
| import org.apache.sling.jcr.jackrabbit.accessmanager.impl.PrivilegesHelper; |
| import org.apache.sling.servlets.post.Modification; |
| import org.apache.sling.servlets.post.PostResponse; |
| import org.apache.sling.servlets.post.PostResponseCreator; |
| import org.apache.sling.servlets.post.SlingPostConstants; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| import org.osgi.service.component.annotations.Component; |
| import org.osgi.service.component.annotations.Reference; |
| import org.osgi.service.component.annotations.ReferenceCardinality; |
| import org.osgi.service.component.annotations.ReferencePolicy; |
| import org.osgi.service.component.annotations.ReferencePolicyOption; |
| |
| /** |
| * <p> |
| * Sling Post Servlet implementation for modifying the ACEs for a principal on a JCR |
| * resource. |
| * </p> |
| * <h2>Rest Service Description</h2> |
| * <p> |
| * Modify a principal's ACEs for the node identified as a resource by the request |
| * URL >resource<.modifyAce.html |
| * </p> |
| * <h3>Transport Details:</h3> |
| * <h4>Methods</h4> |
| * <ul> |
| * <li>POST</li> |
| * </ul> |
| * <h4>Post Parameters</h4> |
| * <dl> |
| * <dt>principalId</dt> |
| * <dd>The principal of the ACEs to modify in the ACL specified by the path.</dd> |
| * <dt>privilege@[privilege_name]</dt> |
| * <dd>One or more privileges which will be applied to the ACE. Any permissions that are present in an |
| * existing ACE for the principal but not in the request are left untouched. The parameter value must be either 'allow', 'deny' or 'all'. |
| * For backward compatibility, 'granted' or 'denied' may also be used for the parameter value as an alias for 'allow' or 'deny'.</dd> |
| * <dt>restriction@[restriction_name]</dt> |
| * <dd>One or more restrictions which will be applied to the ACE. The value is the target value of the restriction to be set.</dd> |
| * <dt>restriction@[restriction_name]@Delete</dt> |
| * <dd>One or more restrictions which will be removed from the ACE</dd> |
| * <dt>privilege@[privilege_name]@Delete</dt> |
| * <dd>One param for each privilege to delete. The parameter value must be either 'allow', 'deny' or 'all' to specify which state to delete from</dd> |
| * <dt>restriction@[privilege_name]@[restriction_name]@Allow</dt> |
| * <dt>restriction@[privilege_name]@[restriction_name]@Deny</dt> |
| * <dd>One param for each restriction value. The same parameter name may be used again for multi-value restrictions. The @Allow or @Deny suffix |
| * specifies whether to apply the restriction to the 'allow' or 'deny' privilege. The value is the target value of the restriction to be set.</dd> |
| * <dt>restriction@[privilege_name]@[restriction_name]@Delete</dt> |
| * <dd>One param for each restriction to delete. The parameter value must be either 'allow', 'deny' or 'all' to specify which state to delete from.</dd> |
| * </dl> |
| * |
| * <h4>Response</h4> |
| * <dl> |
| * <dt>200</dt> |
| * <dd>Success.</dd> |
| * <dt>404</dt> |
| * <dd>The resource was not found.</dd> |
| * <dt>500</dt> |
| * <dd>Failure. HTML explains the failure.</dd> |
| * </dl> |
| * |
| * <h4>Notes</h4> |
| * <p> |
| * The principalId is assumed to refer directly to an Authorizable, that comes direct from |
| * the UserManager. This can be a group or a user, but if its a group, denied permissions |
| * will not be added to the group. The group will only contain granted privileges. |
| * </p> |
| */ |
| |
| @Component(service = {Servlet.class, ModifyAce.class}, |
| property= { |
| "sling.servlet.resourceTypes=sling/servlet/default", |
| "sling.servlet.methods=POST", |
| "sling.servlet.selectors=modifyAce", |
| "sling.servlet.prefix:Integer=-1" |
| }) |
| public class ModifyAceServlet extends AbstractAccessPostServlet implements ModifyAce { |
| private static final long serialVersionUID = -9182485466670280437L; |
| private static final String INVALID_OR_NOT_SUPPORTED_RESTRICTION_NAME_WAS_SUPPLIED = "Invalid restriction name was supplied"; |
| |
| /** |
| * Possible values for a privilege parameter |
| */ |
| private enum PrivilegeValues { |
| ALLOW("allow"), |
| GRANTED("granted"), |
| NONE("none"), |
| DENIED("denied"), |
| DENY("deny"), |
| INVALID("*"); |
| |
| private String paramValue; |
| |
| private PrivilegeValues(String paramValue) { |
| this.paramValue = paramValue; |
| } |
| |
| public static PrivilegeValues valueOfParam(String value) { |
| return Stream.of(PrivilegeValues.values()) |
| .filter(item -> item.paramValue.equalsIgnoreCase(value)) |
| .findFirst() |
| .orElse(INVALID); |
| } |
| } |
| |
| /** |
| * Possible values for a delete privilege or restriction parameter |
| */ |
| private enum DeleteValues { |
| ALL("all"), |
| ALLOW("allow"), |
| DENY("deny"), |
| INVALID("*"); |
| |
| private String paramValue; |
| |
| private DeleteValues(String paramValue) { |
| this.paramValue = paramValue; |
| } |
| |
| public static DeleteValues valueOfParam(String value) { |
| return Stream.of(DeleteValues.values()) |
| .filter(item -> item.paramValue.equalsIgnoreCase(value)) |
| .findFirst() |
| .orElse(INVALID); |
| } |
| } |
| |
| private static final Pattern PRIVILEGE_PATTERN = Pattern.compile(String.format("^privilege@(.+)(?<!%s)$", |
| SlingPostConstants.SUFFIX_DELETE)); |
| private static final Pattern PRIVILEGE_PATTERN_DELETE = Pattern.compile(String.format("^privilege@(.+)%s$", |
| SlingPostConstants.SUFFIX_DELETE)); |
| private static final Pattern RESTRICTION_PATTERN = Pattern.compile("^restriction@([^@]+)(@([^@]+)@(Allow|Deny))?$"); |
| private static final Pattern RESTRICTION_PATTERN_DELETE = Pattern.compile(String.format("^restriction@([^@]+)(@([^@]+))?%s$", |
| SlingPostConstants.SUFFIX_DELETE)); |
| |
| private transient RestrictionProvider restrictionProvider = null; |
| |
| // NOTE: the @Reference annotation is not inherited, so subclasses will need to override the #bindRestrictionProvider |
| // and #unbindRestrictionProvider methods to provide the @Reference annotation. |
| // |
| @Reference(cardinality=ReferenceCardinality.OPTIONAL, policy = ReferencePolicy.DYNAMIC, policyOption=ReferencePolicyOption.GREEDY) |
| protected void bindRestrictionProvider(RestrictionProvider rp) { |
| this.restrictionProvider = rp; |
| } |
| protected void unbindRestrictionProvider(RestrictionProvider rp) { //NOSONAR |
| this.restrictionProvider = null; |
| } |
| |
| /** |
| * Overridden since the @Reference annotation is not inherited from the super method |
| */ |
| @Override |
| @Reference(service = PostResponseCreator.class, |
| cardinality = ReferenceCardinality.MULTIPLE, |
| policy = ReferencePolicy.DYNAMIC) |
| protected void bindPostResponseCreator(PostResponseCreator creator, Map<String, Object> properties) { |
| super.bindPostResponseCreator(creator, properties); |
| } |
| |
| /* (non-Javadoc) |
| * @see org.apache.sling.jackrabbit.usermanager.impl.post.AbstractPostServlet#unbindPostResponseCreator(org.apache.sling.servlets.post.PostResponseCreator, java.util.Map) |
| */ |
| @Override |
| protected void unbindPostResponseCreator(PostResponseCreator creator, Map<String, Object> properties) { //NOSONAR |
| super.unbindPostResponseCreator(creator, properties); |
| } |
| |
| /* (non-Javadoc) |
| * @see org.apache.sling.jackrabbit.accessmanager.post.AbstractAccessPostServlet#handleOperation(org.apache.sling.api.SlingHttpServletRequest, org.apache.sling.servlets.post.PostResponse, java.util.List) |
| */ |
| @Override |
| protected void handleOperation(SlingHttpServletRequest request, |
| PostResponse response, List<Modification> changes) |
| throws RepositoryException { |
| Session session = request.getResourceResolver().adaptTo(Session.class); |
| String resourcePath = getItemPath(request); |
| String principalId = request.getParameter("principalId"); |
| String order = request.getParameter("order"); |
| |
| Principal principal = validateArgs(session, resourcePath, principalId); |
| |
| // Calculate a map of restriction names to the restriction definition. |
| // Use for fast lookup during the calls below. |
| Map<String, RestrictionDefinition> srMap = buildRestrictionNameToDefinitionMap(resourcePath); |
| AccessControlManager acm = AccessControlUtil.getAccessControlManager(session); |
| Map<Privilege, Integer> privilegeLongestDepthMap = PrivilegesHelper.buildPrivilegeLongestDepthMap(acm.privilegeFromName(PrivilegeConstants.JCR_ALL)); |
| |
| // first calculate what is currently stored in the ace |
| Map<Privilege, LocalPrivilege> privilegeToLocalPrivilegesMap = loadStoredAce(acm, resourcePath, principal, srMap); |
| |
| // and now merge the changes from the request parameters |
| processPostedPrivilegeDeleteParams(acm, request, privilegeToLocalPrivilegesMap); |
| processPostedRestrictionDeleteParams(acm, request, srMap, privilegeToLocalPrivilegesMap); |
| processPostedPrivilegeParams(acm, request, privilegeToLocalPrivilegesMap, privilegeLongestDepthMap); |
| processPostedRestrictionParams(acm, request, srMap, privilegeToLocalPrivilegesMap, privilegeLongestDepthMap); |
| |
| // consolidate any aggregates that are still valid |
| PrivilegesHelper.consolidateAggregates(session, resourcePath, privilegeToLocalPrivilegesMap, privilegeLongestDepthMap); |
| |
| // and then store it |
| modifyAce(session, resourcePath, principalId, privilegeToLocalPrivilegesMap.values(), order, false, changes); |
| } |
| |
| /** |
| * Verify that the user supplied arguments are valid |
| * |
| * @param jcrSession the JCR session |
| * @param resourcePath the resource path |
| * @param principalId the principal id |
| * @return the principal for the requested principalId |
| */ |
| protected @NotNull Principal validateArgs(Session jcrSession, String resourcePath, String principalId) throws RepositoryException { |
| if (jcrSession == null) { |
| throw new RepositoryException("JCR Session not found"); |
| } |
| |
| if (restrictionProvider == null) { |
| throw new IllegalStateException("No restriction provider is available so unable to process POSTed restriction values"); |
| } |
| |
| if (principalId == null) { |
| throw new RepositoryException("principalId was not submitted."); |
| } |
| |
| // validate that the submitted name is valid |
| PrincipalManager principalManager = AccessControlUtil.getPrincipalManager(jcrSession); |
| Principal principal = principalManager.getPrincipal(principalId); |
| if (principal == null) { |
| throw new RepositoryException("Invalid principalId was submitted."); |
| } |
| |
| validateResourcePath(jcrSession, resourcePath); |
| |
| AccessControlManager acm = AccessControlUtil.getAccessControlManager(jcrSession); |
| JackrabbitAccessControlList acl = getAcl(acm, resourcePath, principal); |
| if (acl == null) { |
| throw new IllegalStateException("No access control list is available so unable to process"); |
| } |
| |
| return principal; |
| } |
| |
| /** |
| * Calculate a map of restriction names to the restriction definition |
| * |
| * @param resourcePath the path of the resource |
| * @return map of restriction names to definition |
| */ |
| protected @NotNull Map<String, RestrictionDefinition> buildRestrictionNameToDefinitionMap(@NotNull String resourcePath) { |
| Set<RestrictionDefinition> supportedRestrictions = restrictionProvider.getSupportedRestrictions(resourcePath); |
| Map<String, RestrictionDefinition> srMap = new HashMap<>(); |
| for (RestrictionDefinition restrictionDefinition : supportedRestrictions) { |
| srMap.put(restrictionDefinition.getName(), restrictionDefinition); |
| } |
| return srMap; |
| } |
| |
| /** |
| * Loads the state for the currently stored ACE for the specified principal. |
| * The state for any aggregate privilege is expanded to make it easier to merge changes. |
| * |
| * @param acm the access control manager |
| * @param resourcePath the resource path |
| * @param forPrincipal the principal to load the ace for |
| * @param srMap map of restriction names to the restriction definition |
| * @return the privileges from the ace as a map where the key is the privilege |
| * and the value is the LocalPrivilege that encapsulates the state |
| */ |
| protected @NotNull Map<Privilege, LocalPrivilege> loadStoredAce(@NotNull AccessControlManager acm, @NotNull String resourcePath, |
| @NotNull Principal forPrincipal, @NotNull Map<String, RestrictionDefinition> srMap) throws RepositoryException { |
| Map<Privilege, LocalPrivilege> privilegeToLocalPrivilegesMap = new HashMap<>(); |
| JackrabbitAccessControlList acl = getAcl(acm, resourcePath, forPrincipal); |
| AccessControlEntry[] accessControlEntries = acl.getAccessControlEntries(); |
| //evaluate these in reverse order so the entries with highest specificity are processed last |
| for (int i = accessControlEntries.length - 1; i >= 0; i--) { |
| AccessControlEntry accessControlEntry = accessControlEntries[i]; |
| JackrabbitAccessControlEntry jrAccessControlEntry = getJackrabbitAccessControlEntry(accessControlEntry, resourcePath, forPrincipal); |
| if (jrAccessControlEntry != null) { |
| Privilege[] privileges = jrAccessControlEntry.getPrivileges(); |
| if (privileges != null) { |
| boolean isAllow = jrAccessControlEntry.isAllow(); |
| // populate the declared restrictions |
| @NotNull |
| String[] restrictionNames = jrAccessControlEntry.getRestrictionNames(); |
| Set<LocalRestriction> restrictionItems = new HashSet<>(); |
| for (String restrictionName : restrictionNames) { |
| RestrictionDefinition rd = srMap.get(restrictionName); |
| if (rd != null) { // should never get null value here |
| boolean isMulti = rd.getRequiredType().isArray(); |
| if (isMulti) { |
| restrictionItems.add(new LocalRestriction(rd, jrAccessControlEntry.getRestrictions(restrictionName))); |
| } else { |
| restrictionItems.add(new LocalRestriction(rd, jrAccessControlEntry.getRestriction(restrictionName))); |
| } |
| } |
| } |
| |
| if (isAllow) { |
| PrivilegesHelper.allow(privilegeToLocalPrivilegesMap, restrictionItems, Arrays.asList(privileges)); |
| } else { |
| PrivilegesHelper.deny(privilegeToLocalPrivilegesMap, restrictionItems, Arrays.asList(privileges)); |
| } |
| } |
| } |
| } |
| return privilegeToLocalPrivilegesMap; |
| } |
| |
| protected @Nullable JackrabbitAccessControlEntry getJackrabbitAccessControlEntry(@NotNull AccessControlEntry entry, @NotNull String resourcePath, |
| @NotNull Principal forPrincipal) { |
| JackrabbitAccessControlEntry jrEntry = null; |
| if (entry instanceof JackrabbitAccessControlEntry && |
| entry.getPrincipal().equals(forPrincipal)) { |
| jrEntry = (JackrabbitAccessControlEntry)entry; |
| } |
| return jrEntry; |
| } |
| |
| /** |
| * Helper to return a filtered list of parameter names that match the pattern |
| * @param request the current request |
| * @param pattern the regex pattern to match |
| * @return map of parameter names to Matcher that match the pattern |
| */ |
| protected @NotNull Map<String, Matcher> getMatchedRequestParameterNames(@NotNull SlingHttpServletRequest request, @NotNull Pattern pattern) { |
| Map<String, Matcher> keys = new HashMap<>(); |
| Enumeration<String> parameterNames = request.getParameterNames(); |
| while (parameterNames.hasMoreElements()) { |
| String key = parameterNames.nextElement(); |
| Matcher matcher = pattern.matcher(key); |
| if (matcher.matches()) { |
| keys.put(key, matcher); |
| } |
| } |
| return keys; |
| } |
| |
| /** |
| * Merge into the privilegeToLocalPrivilegesMap the changes requested in privilege |
| * delete request parameters. |
| * |
| * @param acm the access control manager |
| * @param request the current request |
| * @param privilegeToLocalPrivilegesMap the map containing the declared LocalPrivilege items |
| */ |
| protected void processPostedPrivilegeDeleteParams(@NotNull AccessControlManager acm, |
| @NotNull SlingHttpServletRequest request, |
| @NotNull Map<Privilege, LocalPrivilege> privilegeToLocalPrivilegesMap) throws RepositoryException { |
| @NotNull |
| Map<String, Matcher> postedPrivilegeDeleteNames = getMatchedRequestParameterNames(request, PRIVILEGE_PATTERN_DELETE); |
| for (Entry<String, Matcher> entry : postedPrivilegeDeleteNames.entrySet()) { |
| String paramName = entry.getKey(); |
| Matcher matcher = entry.getValue(); |
| String privilegeName = matcher.group(1); |
| Privilege privilege = acm.privilegeFromName(privilegeName); |
| String paramValue = request.getParameter(paramName); |
| DeleteValues value = DeleteValues.valueOfParam(paramValue); |
| if (DeleteValues.ALL.equals(value) || DeleteValues.ALLOW.equals(value)) { |
| PrivilegesHelper.unallow(privilegeToLocalPrivilegesMap, |
| Collections.singleton(privilege)); |
| } |
| if (DeleteValues.ALL.equals(value) || DeleteValues.DENY.equals(value)) { |
| PrivilegesHelper.undeny(privilegeToLocalPrivilegesMap, |
| Collections.singleton(privilege)); |
| } |
| } |
| } |
| |
| /** |
| * Merge into the privilegeToLocalPrivilegesMap the changes requested in restriction |
| * delete request parameters. |
| * |
| * @param acm the access control manager |
| * @param request the current request |
| * @param srMap map of restriction names to the restriction definition |
| * @param privilegeToLocalPrivilegesMap the map containing the declared LocalPrivilege items |
| */ |
| protected void processPostedRestrictionDeleteParams(@NotNull AccessControlManager acm, |
| @NotNull SlingHttpServletRequest request, |
| @NotNull Map<String, RestrictionDefinition> srMap, |
| @NotNull Map<Privilege, LocalPrivilege> privilegeToLocalPrivilegesMap) throws RepositoryException { |
| @NotNull |
| Map<String, Matcher> postedRestrictionDeleteNames = getMatchedRequestParameterNames(request, RESTRICTION_PATTERN_DELETE); |
| for (Entry<String, Matcher> entry : postedRestrictionDeleteNames.entrySet()) { |
| String paramName = entry.getKey(); |
| Matcher matcher = entry.getValue(); |
| String privilegeName; |
| String restrictionName; |
| if (matcher.group(2) != null) { |
| privilegeName = matcher.group(1); |
| restrictionName = matcher.group(3); |
| } else { |
| privilegeName = null; |
| restrictionName = matcher.group(1); |
| } |
| RestrictionDefinition rd = srMap.get(restrictionName); |
| if (rd == null) { |
| //illegal restriction name? |
| throw new AccessControlException(INVALID_OR_NOT_SUPPORTED_RESTRICTION_NAME_WAS_SUPPLIED); |
| } |
| Collection<Privilege> privileges; |
| if (privilegeName == null) { |
| // process for every privilege |
| privileges = privilegeToLocalPrivilegesMap.keySet(); |
| } else { |
| // process for the specific privilege only |
| Privilege privilege = acm.privilegeFromName(privilegeName); |
| privileges = Collections.singletonList(privilege); |
| } |
| String[] parameterValues; |
| if (privilegeName == null) { |
| // for backward compatibility, the restriction@[restriction_name]@Delete syntax |
| // deletes from both 'allow' and 'deny' |
| parameterValues = new String[] { "all" }; |
| } else { |
| parameterValues = request.getParameterValues(paramName); |
| } |
| for (String allowOrDeny : parameterValues) { |
| DeleteValues value = DeleteValues.valueOfParam(allowOrDeny); |
| switch (value) { |
| case ALL: |
| // not specified try both the deny and allow sets |
| PrivilegesHelper.unallowOrUndenyRestriction(privilegeToLocalPrivilegesMap, |
| restrictionName, privileges); |
| break; |
| case ALLOW: |
| PrivilegesHelper.unallowRestriction(privilegeToLocalPrivilegesMap, |
| restrictionName, privileges); |
| break; |
| case DENY: |
| PrivilegesHelper.undenyRestriction(privilegeToLocalPrivilegesMap, |
| restrictionName, privileges); |
| break; |
| default: |
| break; |
| } |
| } |
| } |
| } |
| |
| /** |
| * Merge into the privilegeToLocalPrivilegesMap the changes requested in restriction |
| * request parameters. |
| * |
| * @param acm the access control manager |
| * @param request the current request |
| * @param srMap map of restriction names to the restriction definition |
| * @param privilegeToLocalPrivilegesMap the map containing the declared LocalPrivilege items |
| */ |
| protected void processPostedRestrictionParams(@NotNull AccessControlManager acm, |
| @NotNull SlingHttpServletRequest request, |
| @NotNull Map<String, RestrictionDefinition> srMap, |
| @NotNull Map<Privilege, LocalPrivilege> privilegeToLocalPrivilegesMap, |
| @NotNull Map<Privilege, Integer> privilegeLongestDepthMap) throws RepositoryException { |
| Session session = request.getResourceResolver().adaptTo(Session.class); |
| ValueFactory vf = session.getValueFactory(); |
| |
| // first pass to collect all the restrictions so we can |
| // process them in the right order |
| Map<Privilege, Map<LocalRestriction, Set<String>>> privilegeToLocalRestrictionMap = new HashMap<>(); |
| @NotNull |
| Map<String, Matcher> postedRestrictionParams = getMatchedRequestParameterNames(request, RESTRICTION_PATTERN); |
| for (Entry<String, Matcher> entry : postedRestrictionParams.entrySet()) { |
| String paramName = entry.getKey(); |
| Matcher matcher = entry.getValue(); |
| String privilegeName; |
| String restrictionName; |
| String allowOrDeny; |
| if (matcher.group(2) != null) { |
| privilegeName = matcher.group(1); |
| restrictionName = matcher.group(3); |
| allowOrDeny = matcher.group(4); |
| } else { |
| privilegeName = null; |
| restrictionName = matcher.group(1); |
| allowOrDeny = null; |
| } |
| |
| Privilege privilege = privilegeName == null ? null : acm.privilegeFromName(privilegeName); |
| |
| RestrictionDefinition rd = srMap.get(restrictionName); |
| if (rd == null) { |
| //illegal restriction name? |
| throw new AccessControlException(INVALID_OR_NOT_SUPPORTED_RESTRICTION_NAME_WAS_SUPPLIED); |
| } |
| LocalRestriction localRestriction; |
| int restrictionType = rd.getRequiredType().tag(); |
| if (rd.getRequiredType().isArray()) { |
| // multi-value |
| String[] parameterValues = request.getParameterValues(paramName); |
| Value[] restrictionValue = new Value[parameterValues.length]; |
| for (int i = 0; i < parameterValues.length; i++) { |
| restrictionValue[i] = vf.createValue(parameterValues[i], restrictionType); |
| } |
| localRestriction = new LocalRestriction(rd, restrictionValue); |
| } else { |
| // single value |
| Value restrictionValue = vf.createValue(request.getParameter(paramName), restrictionType); |
| localRestriction = new LocalRestriction(rd, restrictionValue); |
| } |
| |
| Map<LocalRestriction, Set<String>> lrMap = privilegeToLocalRestrictionMap.computeIfAbsent(privilege, k -> new HashMap<>()); |
| Set<String> valuesSet = lrMap.computeIfAbsent(localRestriction, k -> new HashSet<>()); |
| valuesSet.add(allowOrDeny); |
| } |
| |
| List<Entry<Privilege, Map<LocalRestriction, Set<String>>>> sortedEntries = new ArrayList<>(privilegeToLocalRestrictionMap.entrySet()); |
| // sort the entries to process the most shallow last |
| Collections.sort(sortedEntries, Comparator.nullsFirst(Comparator.comparing(entry -> privilegeLongestDepthMap.get(entry.getKey())))); |
| for (Entry<Privilege, Map<LocalRestriction, Set<String>>> entry : sortedEntries) { |
| Privilege privilege = entry.getKey(); |
| |
| Collection<Privilege> privileges; |
| if (privilege == null) { |
| // process for every privilege |
| privileges = privilegeToLocalPrivilegesMap.keySet(); |
| } else { |
| // process for the specific privilege only |
| privileges = Collections.singletonList(privilege); |
| } |
| |
| Map<LocalRestriction, Set<String>> lrMap = entry.getValue(); |
| for (Entry<LocalRestriction, Set<String>> lrEntry : lrMap.entrySet()) { |
| LocalRestriction localRestriction = lrEntry.getKey(); |
| // sort the values to ensure it processes the Allow entry last when |
| // there is a conflict |
| List<PrivilegeValues> privilegeValues = lrEntry.getValue().stream() |
| .map(item -> item == null ? null : PrivilegeValues.valueOfParam(item)) |
| .sorted(Comparator.nullsLast(Comparator.comparing(PrivilegeValues::ordinal).reversed())) |
| .collect(Collectors.toList()); |
| for (PrivilegeValues allowOrDeny : privilegeValues) { |
| if (allowOrDeny == null) { |
| // not specified try both the deny and allow sets |
| PrivilegesHelper.allowOrDenyRestriction(privilegeToLocalPrivilegesMap, localRestriction, privileges); |
| } else { |
| switch (allowOrDeny) { |
| case DENY: |
| PrivilegesHelper.denyRestriction(privilegeToLocalPrivilegesMap, localRestriction, privileges); |
| break; |
| case ALLOW: |
| PrivilegesHelper.allowRestriction(privilegeToLocalPrivilegesMap, localRestriction, privileges); |
| break; |
| default: |
| break; |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| /** |
| * Merge into the privilegeToLocalPrivilegesMap the changes requested in privilege |
| * request parameters. |
| * |
| * @param acm the access control manager |
| * @param request the current request |
| * @param privilegeToLocalPrivilegesMap the map containing the declared LocalPrivilege items |
| */ |
| protected void processPostedPrivilegeParams(@NotNull AccessControlManager acm, |
| @NotNull SlingHttpServletRequest request, |
| @NotNull Map<Privilege, LocalPrivilege> privilegeToLocalPrivilegesMap, |
| @NotNull Map<Privilege, Integer> privilegeLongestDepthMap) throws RepositoryException { |
| @NotNull |
| Map<String, Matcher> postedPrivilegeNameKeys = getMatchedRequestParameterNames(request, PRIVILEGE_PATTERN); |
| |
| // first pass to collect all the privileges so we can |
| // process them in the right order |
| Map<Privilege, String> privilegeToParamNameMap = new HashMap<>(); |
| for (Entry<String, Matcher> entry : postedPrivilegeNameKeys.entrySet()) { |
| String paramName = entry.getKey(); |
| Matcher matcher = entry.getValue(); |
| String privilegeName = matcher.group(1); |
| Privilege privilege = acm.privilegeFromName(privilegeName); |
| privilegeToParamNameMap.put(privilege, paramName); |
| } |
| List<Entry<Privilege, String>> sortedEntries = new ArrayList<>(privilegeToParamNameMap.entrySet()); |
| // sort the entries to process the most shallow last |
| Collections.sort(sortedEntries, (e1, e2) -> privilegeLongestDepthMap.get(e1.getKey()).compareTo(privilegeLongestDepthMap.get(e2.getKey()))); |
| for (Entry<Privilege, String> entry : sortedEntries) { |
| String paramName = entry.getValue(); |
| Privilege privilege = entry.getKey(); |
| |
| String [] paramValues = request.getParameterValues(paramName); |
| // convert and sort the values to ensure that allow goes after |
| // deny or none when there is a conflict |
| List<PrivilegeValues> privilegeValues = Stream.of(paramValues) |
| .map(PrivilegeValues::valueOfParam) |
| .sorted((v1, v2) -> Integer.compare(v2.ordinal(), v1.ordinal())) |
| .collect(Collectors.toList()); |
| for (PrivilegeValues value : privilegeValues) { |
| switch (value) { |
| case DENY: |
| case DENIED: |
| PrivilegesHelper.deny(privilegeToLocalPrivilegesMap, Collections.emptySet(), Collections.singleton(privilege)); |
| break; |
| case ALLOW: |
| case GRANTED: |
| PrivilegesHelper.allow(privilegeToLocalPrivilegesMap, Collections.emptySet(), Collections.singleton(privilege)); |
| break; |
| case NONE: |
| PrivilegesHelper.none(privilegeToLocalPrivilegesMap, Collections.singleton(privilege)); |
| break; |
| default: |
| break; |
| } |
| } |
| } |
| } |
| |
| /** |
| * Lookup the ACL for the given resource |
| * |
| * @param acm the access control manager |
| * @param resourcePath the resource path |
| * @param principal the principal for principalbased ACL |
| * @return the found ACL object |
| */ |
| protected JackrabbitAccessControlList getAcl(@NotNull AccessControlManager acm, String resourcePath, Principal principal) |
| throws RepositoryException { |
| AccessControlPolicy[] policies = acm.getPolicies(resourcePath); |
| JackrabbitAccessControlList acl = null; |
| for (AccessControlPolicy policy : policies) { |
| if (policy instanceof JackrabbitAccessControlList) { |
| acl = (JackrabbitAccessControlList) policy; |
| break; |
| } |
| } |
| if (acl == null) { |
| AccessControlPolicyIterator applicablePolicies = acm.getApplicablePolicies(resourcePath); |
| while (applicablePolicies.hasNext()) { |
| AccessControlPolicy policy = applicablePolicies.nextAccessControlPolicy(); |
| if (policy instanceof JackrabbitAccessControlList) { |
| acl = (JackrabbitAccessControlList) policy; |
| break; |
| } |
| } |
| } |
| return acl; |
| } |
| |
| /** |
| * Remove all of the ACEs for the specified principal from the ACL |
| * |
| * @param order the requested order (may be null) |
| * @param principal the principal whose aces should be removed |
| * @param acl the access control list to update |
| * @return the original order if it was supplied, otherwise the order of the first ACE |
| */ |
| protected String removeAces(@NotNull String resourcePath, @Nullable String order, @NotNull Principal principal, @NotNull JackrabbitAccessControlList acl) // NOSONAR |
| throws RepositoryException { |
| AccessControlEntry[] existingAccessControlEntries = acl.getAccessControlEntries(); |
| for (int j = 0; j < existingAccessControlEntries.length; j++) { |
| AccessControlEntry ace = existingAccessControlEntries[j]; |
| if (ace.getPrincipal().equals(principal)) { |
| if (order == null || order.length() == 0) { |
| //order not specified, so keep track of the original ACE position. |
| order = String.valueOf(j); |
| } |
| |
| acl.removeAccessControlEntry(ace); |
| } |
| } |
| return order; |
| } |
| |
| /** |
| * Add ACEs for the specified principal to the ACL. One ACE is added for each unique |
| * restriction set. |
| * |
| * @param resourcePath the path of the resource |
| * @param principal the principal whose aces should be added |
| * @param restrictionsToLocalPrivilegesMap the map containing the restrictions mapped to the LocalPrivlege items with those resrictions |
| * @param isAllow true for 'allow' ACE, false for 'deny' ACE |
| * @param acl the access control list to update |
| */ |
| protected void addAces(@NotNull String resourcePath, @NotNull Principal principal, |
| @NotNull Map<Set<LocalRestriction>, List<LocalPrivilege>> restrictionsToLocalPrivilegesMap, |
| boolean isAllow, |
| @NotNull JackrabbitAccessControlList acl) throws RepositoryException { |
| |
| List<Entry<Set<LocalRestriction>, List<LocalPrivilege>>> sortedEntries = new ArrayList<>(restrictionsToLocalPrivilegesMap.entrySet()); |
| // sort the entries to so the ACE without restrictions is last |
| Collections.sort(sortedEntries, (e1, e2) -> Integer.compare(e2.getKey().size(), e1.getKey().size())); |
| |
| for (Entry<Set<LocalRestriction>, List<LocalPrivilege>> entry: sortedEntries) { |
| Set<Privilege> privilegesSet = new HashSet<>(); |
| Map<String, Value> restrictions = new HashMap<>(); |
| Map<String, Value[]> mvRestrictions = new HashMap<>(); |
| |
| Set<LocalRestriction> localRestrictions = entry.getKey(); |
| for (LocalRestriction localRestriction : localRestrictions) { |
| if (localRestriction.isMultiValue()) { |
| mvRestrictions.put(localRestriction.getName(), localRestriction.getValues()); |
| } else { |
| restrictions.put(localRestriction.getName(), localRestriction.getValue()); |
| } |
| } |
| |
| for (LocalPrivilege localPrivilege : entry.getValue()) { |
| privilegesSet.add(localPrivilege.getPrivilege()); |
| } |
| |
| if (!privilegesSet.isEmpty()) { |
| if (acl instanceof PrincipalAccessControlList) { |
| ((PrincipalAccessControlList)acl).addEntry(resourcePath, privilegesSet.toArray(new Privilege[privilegesSet.size()]), restrictions, mvRestrictions); |
| } else { |
| acl.addEntry(principal, privilegesSet.toArray(new Privilege[privilegesSet.size()]), isAllow, restrictions, mvRestrictions); |
| } |
| } |
| } |
| } |
| |
| /** |
| * Move the ACE(s) for the specified principal to the position specified by the 'order' |
| * parameter. This is a copy of the private AccessControlUtil.reorderAccessControlEntries method. |
| * |
| * @param acl the acl of the node containing the ACE to position |
| * @param principal the user or group of the ACE to position |
| * @param order where the access control entry should go in the list. |
| * Value should be one of these: |
| * <table> |
| * <caption>Values</caption> |
| * <tr><td>first</td><td>Place the target ACE as the first amongst its siblings</td></tr> |
| * <tr><td>last</td><td>Place the target ACE as the last amongst its siblings</td></tr> |
| * <tr><td>before xyz</td><td>Place the target ACE immediately before the sibling whose name is xyz</td></tr> |
| * <tr><td>after xyz</td><td>Place the target ACE immediately after the sibling whose name is xyz</td></tr> |
| * <tr><td>numeric</td><td>Place the target ACE at the specified index</td></tr> |
| * </table> |
| * @throws RepositoryException |
| * @throws UnsupportedRepositoryOperationException |
| * @throws AccessControlException |
| */ |
| private static void reorderAccessControlEntries(AccessControlList acl, |
| Principal principal, String order) throws RepositoryException { |
| if (order == null || order.length() == 0) { |
| return; //nothing to do |
| } |
| if (acl instanceof JackrabbitAccessControlList) { |
| JackrabbitAccessControlList jacl = (JackrabbitAccessControlList)acl; |
| |
| AccessControlEntry[] accessControlEntries = jacl.getAccessControlEntries(); |
| if (accessControlEntries.length <= 1) { |
| return; //only one ACE, so nothing to reorder. |
| } |
| |
| AccessControlEntry beforeEntry = null; |
| if ("first".equals(order)) { |
| beforeEntry = accessControlEntries[0]; |
| } else if ("last".equals(order)) { |
| // add to the end is the same as default |
| } else if (order.startsWith("before ")) { |
| String beforePrincipalName = order.substring(7); |
| |
| //find the index of the ACE of the 'before' principal |
| for (int i=0; i < accessControlEntries.length; i++) { |
| if (beforePrincipalName.equals(accessControlEntries[i].getPrincipal().getName())) { |
| //found it! |
| beforeEntry = accessControlEntries[i]; |
| break; |
| } |
| } |
| |
| if (beforeEntry == null) { |
| //didn't find an ACE that matched the 'before' principal |
| throw new IllegalArgumentException("No ACE was found for the specified principal: " + beforePrincipalName); |
| } |
| } else if (order.startsWith("after ")) { |
| String afterPrincipalName = order.substring(6); |
| |
| //find the index of the ACE of the 'after' principal |
| for (int i = accessControlEntries.length - 1; i >= 0; i--) { |
| if (afterPrincipalName.equals(accessControlEntries[i].getPrincipal().getName())) { |
| //found it! |
| |
| // the 'before' ACE is the next one after the 'after' ACE |
| if (i >= accessControlEntries.length - 1) { |
| //the after is the last one in the list |
| beforeEntry = null; |
| } else { |
| beforeEntry = accessControlEntries[i + 1]; |
| } |
| break; |
| } |
| } |
| |
| if (beforeEntry == null) { |
| //didn't find an ACE that matched the 'after' principal |
| throw new IllegalArgumentException("No ACE was found for the specified principal: " + afterPrincipalName); |
| } |
| } else { |
| try { |
| int index = Integer.parseInt(order); |
| if (index > accessControlEntries.length) { |
| //invalid index |
| throw new IndexOutOfBoundsException("Index value is too large: " + index); |
| } |
| |
| if (index == 0) { |
| beforeEntry = accessControlEntries[0]; |
| } else { |
| //the index value is the index of the principal. A principal may have more |
| // than one ACEs (deny + grant), so we need to compensate. |
| Set<Principal> processedPrincipals = new HashSet<>(); |
| for (int i = 0; i < accessControlEntries.length; i++) { |
| Principal principal2 = accessControlEntries[i].getPrincipal(); |
| if (processedPrincipals.size() == index && |
| !processedPrincipals.contains(principal2)) { |
| //we are now at the correct position in the list |
| beforeEntry = accessControlEntries[i]; |
| break; |
| } |
| |
| processedPrincipals.add(principal2); |
| } |
| } |
| } catch (NumberFormatException nfe) { |
| //not a number. |
| throw new IllegalArgumentException("Illegal value for the order parameter: " + order); |
| } |
| } |
| |
| //now loop through the entries to move the affected ACEs to the specified |
| // position. |
| for (int i = accessControlEntries.length - 1; i >= 0; i--) { |
| AccessControlEntry ace = accessControlEntries[i]; |
| if (principal.equals(ace.getPrincipal())) { |
| //this ACE is for the specified principal. |
| jacl.orderBefore(ace, beforeEntry); |
| } |
| } |
| } else { |
| throw new IllegalArgumentException("The acl must be an instance of JackrabbitAccessControlList"); |
| } |
| } |
| |
| /* (non-Javadoc) |
| * @see org.apache.sling.jcr.jackrabbit.accessmanager.ModifyAce#modifyAce(javax.jcr.Session, java.lang.String, java.lang.String, java.util.Map, java.lang.String, boolean) |
| */ |
| @Override |
| public void modifyAce(Session jcrSession, String resourcePath, String principalId, Map<String, String> privileges, |
| String order, boolean autoSave) throws RepositoryException { |
| modifyAce(jcrSession, resourcePath, principalId, privileges, order, |
| null, null, null, autoSave); |
| } |
| |
| /* (non-Javadoc) |
| * @see org.apache.sling.jcr.jackrabbit.accessmanager.ModifyAce#modifyAce(javax.jcr.Session, java.lang.String, java.lang.String, java.util.Map, java.lang.String) |
| */ |
| public void modifyAce(Session jcrSession, String resourcePath, |
| String principalId, Map<String, String> privileges, String order) |
| throws RepositoryException { |
| modifyAce(jcrSession, resourcePath, principalId, privileges, order, true); |
| } |
| |
| /* (non-Javadoc) |
| * @see org.apache.sling.jcr.jackrabbit.accessmanager.ModifyAce#modifyAce(javax.jcr.Session, java.lang.String, java.lang.String, java.util.Map, java.lang.String, java.util.Map, java.util.Map, java.util.Set) |
| */ |
| @Override |
| public void modifyAce(Session jcrSession, String resourcePath, String principalId, Map<String, String> privileges, |
| String order, Map<String, Value> restrictions, Map<String, Value[]> mvRestrictions, |
| Set<String> removeRestrictionNames) throws RepositoryException { |
| modifyAce(jcrSession, resourcePath, principalId, privileges, order, |
| restrictions, mvRestrictions, removeRestrictionNames, true); |
| } |
| |
| /* (non-Javadoc) |
| * @see org.apache.sling.jcr.jackrabbit.accessmanager.ModifyAce#modifyAce(javax.jcr.Session, java.lang.String, java.lang.String, java.util.Map, java.lang.String, java.util.Map, java.util.Map, java.util.Set, boolean) |
| */ |
| @Override |
| public void modifyAce(Session jcrSession, String resourcePath, String principalId, Map<String, String> privileges, |
| String order, Map<String, Value> restrictions, Map<String, Value[]> mvRestrictions, |
| Set<String> removeRestrictionNames, boolean autoSave) throws RepositoryException { |
| modifyAce(jcrSession, resourcePath, principalId, privileges, order, |
| restrictions, mvRestrictions, removeRestrictionNames, autoSave, null); |
| } |
| |
| protected void modifyAce( // NOSONAR |
| Session jcrSession, String resourcePath, String principalId, Map<String, String> privileges, |
| String order, Map<String, Value> restrictions, Map<String, Value[]> mvRestrictions, |
| Set<String> removeRestrictionNames, boolean autoSave, List<Modification> changes) throws RepositoryException { |
| |
| Principal principal = validateArgs(jcrSession, resourcePath, principalId); |
| |
| // Calculate a map of restriction names to the restriction definition. |
| // Use for fast lookup during the calls below. |
| AccessControlManager acm = AccessControlUtil.getAccessControlManager(jcrSession); |
| Map<String, RestrictionDefinition> srMap = buildRestrictionNameToDefinitionMap(resourcePath); |
| Map<Privilege, Integer> privilegeLongestDepthMap = PrivilegesHelper.buildPrivilegeLongestDepthMap(acm.privilegeFromName(PrivilegeConstants.JCR_ALL)); |
| |
| // first calculate what is currently stored in the ace |
| Map<Privilege, LocalPrivilege> privilegeToLocalPrivilegesMap = loadStoredAce(acm, resourcePath, principal, srMap); |
| |
| //process the restrictions to remove |
| for (LocalPrivilege lp : privilegeToLocalPrivilegesMap.values()) { |
| if (lp.isAllow()) { |
| PrivilegesHelper.unallowRestrictions(privilegeToLocalPrivilegesMap, removeRestrictionNames, Collections.singleton(lp.getPrivilege())); |
| } |
| if (lp.isDeny()) { |
| PrivilegesHelper.undenyRestrictions(privilegeToLocalPrivilegesMap, removeRestrictionNames, Collections.singleton(lp.getPrivilege())); |
| } |
| } |
| |
| // process the new restrictions |
| Set<LocalRestriction> localRestrictions = new HashSet<>(); |
| if (restrictions != null) { |
| for (Entry<String, Value> entry : restrictions.entrySet()) { |
| RestrictionDefinition rd = srMap.get(entry.getKey()); |
| if (rd == null) { |
| //illegal restriction name? |
| throw new AccessControlException(INVALID_OR_NOT_SUPPORTED_RESTRICTION_NAME_WAS_SUPPLIED); |
| } |
| localRestrictions.add(new LocalRestriction(rd, entry.getValue())); |
| } |
| } |
| if (mvRestrictions != null) { |
| for (Entry<String, Value[]> entry : mvRestrictions.entrySet()) { |
| RestrictionDefinition rd = srMap.get(entry.getKey()); |
| if (rd == null) { |
| //illegal restriction name? |
| throw new AccessControlException(INVALID_OR_NOT_SUPPORTED_RESTRICTION_NAME_WAS_SUPPLIED); |
| } |
| localRestrictions.add(new LocalRestriction(rd, entry.getValue())); |
| } |
| } |
| |
| // map the values to the privileges with that value |
| Map<PrivilegeValues, Set<Privilege>> privilegeValueToPrivilegesMap = new EnumMap<>(PrivilegeValues.class); |
| for (Entry<String, String> entry : privileges.entrySet()) { |
| String privilegeName = entry.getKey(); |
| // for backward compatibility, deal with a prefixed value |
| if (privilegeName.startsWith("privilege@")) { |
| privilegeName = privilegeName.substring(10); |
| } |
| Privilege privilege = acm.privilegeFromName(privilegeName); |
| PrivilegeValues value = PrivilegeValues.valueOfParam(entry.getValue()); |
| Set<Privilege> privilegesSet = privilegeValueToPrivilegesMap.computeIfAbsent(value, k -> new HashSet<>()); |
| privilegesSet.add(privilege); |
| } |
| |
| // process the new privileges |
| for (Entry<PrivilegeValues, Set<Privilege>> entry : privilegeValueToPrivilegesMap.entrySet()) { |
| switch (entry.getKey()) { |
| case GRANTED: |
| case ALLOW: |
| PrivilegesHelper.allow(privilegeToLocalPrivilegesMap, localRestrictions, entry.getValue()); |
| break; |
| case DENIED: |
| case DENY: |
| PrivilegesHelper.deny(privilegeToLocalPrivilegesMap, localRestrictions, entry.getValue()); |
| break; |
| case NONE: |
| PrivilegesHelper.none(privilegeToLocalPrivilegesMap, entry.getValue()); |
| break; |
| default: |
| break; |
| } |
| } |
| |
| // combine any aggregates that are still valid |
| PrivilegesHelper.consolidateAggregates(jcrSession, resourcePath, privilegeToLocalPrivilegesMap, privilegeLongestDepthMap); |
| |
| modifyAce(jcrSession, resourcePath, principalId, |
| privilegeToLocalPrivilegesMap.values(), order, |
| autoSave, changes); |
| } |
| |
| /* (non-Javadoc) |
| * @see org.apache.sling.jcr.jackrabbit.accessmanager.ModifyAce#modifyAce(javax.jcr.Session, java.lang.String, java.lang.String, java.util.Collection, java.lang.String, boolean) |
| */ |
| @Override |
| public void modifyAce( |
| Session jcrSession, String resourcePath, String principalId, |
| Collection<LocalPrivilege> localPrivileges, String order, |
| boolean autoSave) throws RepositoryException { |
| modifyAce(jcrSession, resourcePath, principalId, |
| localPrivileges, order, |
| autoSave, null); |
| } |
| |
| protected void modifyAce( |
| Session jcrSession, String resourcePath, String principalId, |
| Collection<LocalPrivilege> localPrivileges, String order, |
| boolean autoSave, List<Modification> changes) throws RepositoryException { |
| @NotNull |
| Principal principal = validateArgs(jcrSession, resourcePath, principalId); |
| |
| // build a list of each of the LocalPrivileges that have the same restrictions |
| Map<Set<LocalRestriction>, List<LocalPrivilege>> allowRestrictionsToLocalPrivilegesMap = new HashMap<>(); |
| Map<Set<LocalRestriction>, List<LocalPrivilege>> denyRestrictionsToLocalPrivilegesMap = new HashMap<>(); |
| for (LocalPrivilege localPrivilege: localPrivileges) { |
| if (localPrivilege.isAllow()) { |
| List<LocalPrivilege> list = allowRestrictionsToLocalPrivilegesMap.computeIfAbsent(localPrivilege.getAllowRestrictions(), key -> new ArrayList<>()); |
| list.add(localPrivilege); |
| } |
| if (localPrivilege.isDeny()) { |
| List<LocalPrivilege> list = denyRestrictionsToLocalPrivilegesMap.computeIfAbsent(localPrivilege.getDenyRestrictions(), key -> new ArrayList<>()); |
| list.add(localPrivilege); |
| } |
| } |
| |
| try { |
| // Get or create the ACL for the node. |
| AccessControlManager acm = AccessControlUtil.getAccessControlManager(jcrSession); |
| JackrabbitAccessControlList acl = getAcl(acm, resourcePath, principal); |
| |
| // remove all the old aces for the principal |
| order = removeAces(resourcePath, order, principal, acl); |
| |
| // now add all the new aces that we have collected |
| addAces(resourcePath, principal, denyRestrictionsToLocalPrivilegesMap, false, acl); |
| addAces(resourcePath, principal, allowRestrictionsToLocalPrivilegesMap, true, acl); |
| |
| // reorder the aces |
| reorderAccessControlEntries(acl, principal, order); |
| |
| // Store the actual changes. |
| acm.setPolicy(acl.getPath(), acl); |
| |
| if (changes != null) { |
| changes.add(Modification.onModified(principal.getName())); |
| } |
| |
| if (autoSave && jcrSession.hasPendingChanges()) { |
| jcrSession.save(); |
| } |
| } catch (RepositoryException re) { |
| throw new RepositoryException("Failed to create ace.", re); |
| } |
| } |
| |
| } |