/*
* 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.io.IOException;
import java.security.Principal;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import javax.jcr.AccessDeniedException;
import javax.jcr.Item;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
import javax.jcr.security.AccessControlEntry;
import javax.jcr.security.AccessControlManager;
import javax.jcr.security.Privilege;
import javax.json.Json;
import javax.json.JsonArrayBuilder;
import javax.json.JsonObject;
import javax.json.JsonObjectBuilder;
import javax.json.JsonValue;
import javax.json.stream.JsonGenerator;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletResponse;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.SlingHttpServletResponse;
import org.apache.sling.api.resource.ResourceNotFoundException;
import org.apache.sling.api.servlets.SlingAllMethodsServlet;
import org.apache.sling.jcr.base.util.AccessControlUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@SuppressWarnings("serial")
public abstract class AbstractGetAclServlet extends SlingAllMethodsServlet {
/**
* default log
*/
private final Logger log = LoggerFactory.getLogger(getClass());
/* (non-Javadoc)
* @see org.apache.sling.api.servlets.SlingSafeMethodsServlet#doGet(org.apache.sling.api.SlingHttpServletRequest, org.apache.sling.api.SlingHttpServletResponse)
*/
@Override
protected void doGet(SlingHttpServletRequest request,
SlingHttpServletResponse response) throws ServletException,
IOException {
try {
Session session = request.getResourceResolver().adaptTo(Session.class);
String resourcePath = request.getResource().getPath();
JsonObject acl = internalGetAcl(session, resourcePath);
response.setContentType("application/json");
response.setCharacterEncoding("UTF-8");
boolean isTidy = false;
final String[] selectors = request.getRequestPathInfo().getSelectors();
if (selectors != null && selectors.length > 0) {
for (final String level : selectors) {
if("tidy".equals(level)) {
isTidy = true;
break;
}
}
}
Map<String, Object> options = new HashMap<>();
options.put(JsonGenerator.PRETTY_PRINTING, isTidy);
Json.createGeneratorFactory(options).createGenerator(response.getWriter()).write(acl).flush();
} catch (AccessDeniedException ade) {
response.sendError(HttpServletResponse.SC_NOT_FOUND);
} catch (ResourceNotFoundException rnfe) {
response.sendError(HttpServletResponse.SC_NOT_FOUND, rnfe.getMessage());
} catch (Throwable throwable) {
log.debug("Exception while handling GET "
+ request.getResource().getPath() + " with "
+ getClass().getName(), throwable);
throw new ServletException(throwable);
}
}
@SuppressWarnings("unchecked")
protected JsonObject internalGetAcl(Session jcrSession, String resourcePath) throws RepositoryException {
if (jcrSession == null) {
throw new RepositoryException("JCR Session not found");
}
Item item = jcrSession.getItem(resourcePath);
if (item != null) {
resourcePath = item.getPath();
} else {
throw new ResourceNotFoundException("Resource is not a JCR Node");
}
// Calculate a map of privileges to all the aggregate privileges it is contained in.
// Use for fast lookup during the mergePrivilegeSets calls below.
AccessControlManager accessControlManager = AccessControlUtil.getAccessControlManager(jcrSession);
Map<Privilege, Set<Privilege>> privilegeToAncestorMap = new HashMap<Privilege, Set<Privilege>>();
Privilege[] supportedPrivileges = accessControlManager.getSupportedPrivileges(item.getPath());
for (Privilege privilege : supportedPrivileges) {
if (privilege.isAggregate()) {
Privilege[] ap = privilege.getAggregatePrivileges();
for (Privilege privilege2 : ap) {
Set<Privilege> set = privilegeToAncestorMap.get(privilege2);
if (set == null) {
set = new HashSet<Privilege>();
privilegeToAncestorMap.put(privilege2, set);
}
set.add(privilege);
}
}
}
AccessControlEntry[] declaredAccessControlEntries = getAccessControlEntries(jcrSession, resourcePath);
Map<String, Map<String, Object>> aclMap = new LinkedHashMap<String, Map<String,Object>>();
int sequence = 0;
for (AccessControlEntry ace : declaredAccessControlEntries) {
Principal principal = ace.getPrincipal();
Map<String, Object> map = aclMap.get(principal.getName());
if (map == null) {
map = new LinkedHashMap<String, Object>();
aclMap.put(principal.getName(), map);
map.put("order", sequence++);
}
}
//evaluate these in reverse order so the most entries with highest specificity are last
for (int i = declaredAccessControlEntries.length - 1; i >= 0; i--) {
AccessControlEntry ace = declaredAccessControlEntries[i];
Principal principal = ace.getPrincipal();
Map<String, Object> map = aclMap.get(principal.getName());
Set<Privilege> grantedSet = (Set<Privilege>) map.get("granted");
if (grantedSet == null) {
grantedSet = new LinkedHashSet<Privilege>();
map.put("granted", grantedSet);
}
Set<Privilege> deniedSet = (Set<Privilege>) map.get("denied");
if (deniedSet == null) {
deniedSet = new LinkedHashSet<Privilege>();
map.put("denied", deniedSet);
}
boolean allow = AccessControlUtil.isAllow(ace);
if (allow) {
Privilege[] privileges = ace.getPrivileges();
for (Privilege privilege : privileges) {
mergePrivilegeSets(privilege,
privilegeToAncestorMap,
grantedSet, deniedSet);
}
} else {
Privilege[] privileges = ace.getPrivileges();
for (Privilege privilege : privileges) {
mergePrivilegeSets(privilege,
privilegeToAncestorMap,
deniedSet, grantedSet);
}
}
}
List<JsonObject> aclList = new ArrayList<>();
Set<Entry<String, Map<String, Object>>> entrySet = aclMap.entrySet();
for (Entry<String, Map<String, Object>> entry : entrySet) {
String principalName = entry.getKey();
Map<String, Object> value = entry.getValue();
JsonObjectBuilder aceObject = Json.createObjectBuilder();
aceObject.add("principal", principalName);
Set<Privilege> grantedSet = (Set<Privilege>) value.get("granted");
if (grantedSet != null && !grantedSet.isEmpty()) {
JsonArrayBuilder arrayBuilder = Json.createArrayBuilder();
for (Privilege v : grantedSet)
{
arrayBuilder.add(v.getName());
}
aceObject.add("granted", arrayBuilder);
}
Set<Privilege> deniedSet = (Set<Privilege>) value.get("denied");
if (deniedSet != null && !deniedSet.isEmpty()) {
JsonArrayBuilder arrayBuilder = Json.createArrayBuilder();
for (Privilege v : deniedSet)
{
arrayBuilder.add(v.getName());
}
aceObject.add("denied", arrayBuilder);
}
aceObject.add("order", (Integer) value.get("order"));
aclList.add(aceObject.build());
}
JsonObjectBuilder jsonAclMap = Json.createObjectBuilder();
for (Map.Entry<String, Map<String, Object>> entry : aclMap.entrySet())
{
JsonObjectBuilder builder = Json.createObjectBuilder();
for (Map.Entry<String, Object> inner : entry.getValue().entrySet())
{
addTo(builder, inner.getKey(), inner.getValue());
}
jsonAclMap.add(entry.getKey(), builder);
}
for (JsonObject jsonObj : aclList) {
jsonAclMap.add(jsonObj.getString("principal"), jsonObj);
}
return jsonAclMap.build();
}
private JsonObjectBuilder addTo(JsonObjectBuilder builder, String key, Object value) {
if (value instanceof Byte || value instanceof Short || value instanceof Integer || value instanceof Long)
{
builder.add(key, ((Number) value).longValue());
}
else if (value instanceof Float || value instanceof Double)
{
builder.add(key, ((Number) value).doubleValue());
}
else if (value instanceof Privilege)
{
JsonObjectBuilder privilegeBuilder = Json.createObjectBuilder();
privilegeBuilder.add("name", ((Privilege) value).getName());
builder.add(key, privilegeBuilder);
}
else if (value instanceof String)
{
builder.add(key, (String) value);
}
else
{
builder.add(key, value.toString());
}
return builder;
}
/**
* Update the granted and denied privilege sets by merging the result of adding
* the supplied privilege.
*/
private void mergePrivilegeSets(Privilege privilege,
Map<Privilege, Set<Privilege>> privilegeToAncestorMap,
Set<Privilege> firstSet, Set<Privilege> secondSet) {
//1. remove duplicates and invalid privileges from the list
if (privilege.isAggregate()) {
Privilege[] aggregatePrivileges = privilege.getAggregatePrivileges();
//remove duplicates from the granted set
List<Privilege> asList = Arrays.asList(aggregatePrivileges);
firstSet.removeAll(asList);
//remove from the denied set
secondSet.removeAll(asList);
}
secondSet.remove(privilege);
//2. check if the privilege is already contained in another granted privilege
boolean isAlreadyGranted = false;
Set<Privilege> ancestorSet = privilegeToAncestorMap.get(privilege);
if (ancestorSet != null) {
for (Privilege privilege2 : ancestorSet) {
if (firstSet.contains(privilege2)) {
isAlreadyGranted = true;
break;
}
}
}
//3. add the privilege
if (!isAlreadyGranted) {
firstSet.add(privilege);
}
//4. Deal with expanding existing aggregate privileges to remove the invalid
// items and add the valid ones.
Set<Privilege> filterSet = privilegeToAncestorMap.get(privilege);
if (filterSet != null) {
//re-pack the denied set to compensate
for (Privilege privilege2 : filterSet) {
if (secondSet.contains(privilege2)) {
secondSet.remove(privilege2);
if (privilege2.isAggregate()) {
filterAndMergePrivilegesFromAggregate(privilege2,
firstSet, secondSet, filterSet, privilege);
}
}
}
}
}
/**
* Add all the declared aggregate privileges from the supplied privilege to the secondSet
* unless the privilege is already in the firstSet and not contained in the supplied filterSet.
*/
private void filterAndMergePrivilegesFromAggregate(Privilege privilege, Set<Privilege> firstSet,
Set<Privilege> secondSet, Set<Privilege> filterSet, Privilege ignorePrivilege) {
Privilege[] declaredAggregatePrivileges = privilege.getDeclaredAggregatePrivileges();
for (Privilege privilege3 : declaredAggregatePrivileges) {
if (ignorePrivilege.equals(privilege3)) {
continue; //skip it.
}
if (!firstSet.contains(privilege3) && !filterSet.contains(privilege3)) {
secondSet.add(privilege3);
}
if (privilege3.isAggregate()) {
Privilege[] declaredAggregatePrivileges2 = privilege3.getDeclaredAggregatePrivileges();
for (Privilege privilege2 : declaredAggregatePrivileges2) {
if (!ignorePrivilege.equals(privilege2)) {
if (privilege2.isAggregate()) {
filterAndMergePrivilegesFromAggregate(privilege2,
firstSet, secondSet, filterSet, ignorePrivilege);
}
}
}
}
}
}
protected abstract AccessControlEntry[] getAccessControlEntries(Session session, String absPath) throws RepositoryException;
}