blob: f364a84a39befbfe5018bd38d512174329a4306d [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.post;
import java.security.Principal;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
import javax.jcr.Value;
import javax.jcr.security.AccessControlEntry;
import javax.jcr.security.AccessControlManager;
import javax.jcr.security.AccessControlPolicy;
import javax.servlet.Servlet;
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.authorization.PrincipalAccessControlList;
import org.apache.jackrabbit.oak.spi.security.authorization.restriction.RestrictionProvider;
import org.apache.sling.jcr.jackrabbit.accessmanager.LocalPrivilege;
import org.apache.sling.jcr.jackrabbit.accessmanager.LocalRestriction;
import org.apache.sling.jcr.jackrabbit.accessmanager.ModifyPrincipalAce;
import org.apache.sling.jcr.jackrabbit.accessmanager.impl.PrincipalAceHelper;
import org.apache.sling.servlets.post.PostResponseCreator;
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.ReferencePolicyOption;
/**
* <p>
* Sling Post Servlet implementation for modifying the principalbased ACE 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 &gt;resource&lt;.modifyPAce.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' or 'all'.
* For backward compatibility, 'granted' may also be used for the parameter value as an alias for 'allow'.</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' 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 suffix
* specifies whether to apply the restriction to the 'allow' 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' 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, ModifyPrincipalAce.class},
property= {
"sling.servlet.resourceTypes=sling/servlet/default",
"sling.servlet.methods=POST",
"sling.servlet.selectors=modifyPAce",
"sling.servlet.prefix:Integer=-1"
},
reference = {
@Reference(name="RestrictionProvider",
bind = "bindRestrictionProvider",
service = RestrictionProvider.class),
@Reference(name = "PostResponseCreator",
bind = "bindPostResponseCreator",
cardinality = ReferenceCardinality.MULTIPLE,
policyOption = ReferencePolicyOption.GREEDY,
service = PostResponseCreator.class)
})
@SuppressWarnings("java:S110")
public class ModifyPrincipalAceServlet extends ModifyAceServlet implements ModifyPrincipalAce {
private static final long serialVersionUID = -4152308935573740745L;
@Override
protected boolean allowNonExistingPaths() {
return true;
}
@Override
public void modifyPrincipalAce(Session jcrSession, String resourcePath, String principalId,
Map<String, String> privileges, boolean autoSave) throws RepositoryException {
modifyPrincipalAce(jcrSession, resourcePath, principalId, privileges,
null, null, null, autoSave);
}
@Override
public void modifyPrincipalAce(Session jcrSession, String resourcePath, String principalId,
Map<String, String> privileges, Map<String, Value> restrictions,
Map<String, Value[]> mvRestrictions, Set<String> removeRestrictionNames, boolean autoSave)
throws RepositoryException {
modifyAce(jcrSession, resourcePath, principalId, privileges, null,
restrictions, mvRestrictions, removeRestrictionNames, autoSave, null);
}
@Override
public void modifyPrincipalAce(Session jcrSession, String resourcePath, String principalId,
Collection<LocalPrivilege> localPrivileges, boolean autoSave) throws RepositoryException {
modifyAce(jcrSession, resourcePath, principalId,
localPrivileges, null,
autoSave, null);
}
/**
* Override to ensure that we get the policy that implements {@link PrincipalAccessControlList}
*/
@Override
protected JackrabbitAccessControlList getAcl(@NotNull AccessControlManager acm, String resourcePath, Principal principal)
throws RepositoryException {
JackrabbitAccessControlList acl = null;
if (acm instanceof JackrabbitAccessControlManager) {
JackrabbitAccessControlManager jacm = (JackrabbitAccessControlManager)acm;
AccessControlPolicy[] policies = jacm.getPolicies(principal);
for (AccessControlPolicy policy : policies) {
if (policy instanceof PrincipalAccessControlList) {
acl = (PrincipalAccessControlList) policy;
break;
}
}
if (acl == null) {
AccessControlPolicy[] applicablePolicies = jacm.getApplicablePolicies(principal);
for (AccessControlPolicy policy : applicablePolicies) {
if (policy instanceof PrincipalAccessControlList) {
acl = (PrincipalAccessControlList) policy;
break;
}
}
}
}
return acl;
}
/**
* Override to ensure that we only remove the entries that have an effectivePath that matches
* the current resourcePath
*/
@Override
protected String removeAces(@NotNull String resourcePath, @Nullable String order, @NotNull Principal principal,
@NotNull JackrabbitAccessControlList acl) throws RepositoryException {
AccessControlEntry[] existingAccessControlEntries = acl.getAccessControlEntries();
for (int j = 0; j < existingAccessControlEntries.length; j++) {
AccessControlEntry ace = existingAccessControlEntries[j];
@Nullable
JackrabbitAccessControlEntry jrEntry = getJackrabbitAccessControlEntry(ace, resourcePath, principal);
if (jrEntry != null) {
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;
}
/**
* Override to ensure we do not add enty that denies privileges which is not allowed in a principal ACE
*/
@Override
protected void addAces(@NotNull String resourcePath, @NotNull Principal principal,
@NotNull Map<Set<LocalRestriction>, List<LocalPrivilege>> restrictionsToLocalPrivilegesMap, boolean isAllow,
@NotNull JackrabbitAccessControlList acl) throws RepositoryException {
if (!isAllow && !restrictionsToLocalPrivilegesMap.isEmpty()) {
// deny privileges not allowed in a principal ACE
throw new IllegalArgumentException("Deny privileges are not allowed in a principal ACE");
}
super.addAces(resourcePath, principal, restrictionsToLocalPrivilegesMap, isAllow, acl);
}
/**
* Override to ensure that we only return the entries that have an effectivePath that matches
* the current resourcePath
*/
@Override
protected @Nullable JackrabbitAccessControlEntry getJackrabbitAccessControlEntry(@NotNull AccessControlEntry entry, @NotNull String resourcePath,
@NotNull Principal forPrincipal) {
JackrabbitAccessControlEntry jrEntry = null;
if (entry instanceof PrincipalAccessControlList.Entry &&
entry.getPrincipal().equals(forPrincipal) &&
PrincipalAceHelper.matchesResourcePath(resourcePath, entry)) {
jrEntry = (JackrabbitAccessControlEntry)entry;
}
return jrEntry;
}
}