/** * Copyright (C) 2010 eXo Platform SAS. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. * */ package org.exoplatform.services.jcr.webdav.command; import org.exoplatform.common.http.HTTPStatus; import org.exoplatform.common.util.HierarchicalProperty; import org.exoplatform.services.jcr.access.AccessControlList; import org.exoplatform.services.jcr.access.PermissionType; import org.exoplatform.services.jcr.impl.core.NodeImpl; import org.exoplatform.services.jcr.webdav.command.acl.ACLProperties; import org.exoplatform.services.log.ExoLogger; import org.exoplatform.services.log.Log; import org.exoplatform.services.security.IdentityConstants; import java.security.AccessControlException; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import javax.jcr.PathNotFoundException; import javax.jcr.RepositoryException; import javax.jcr.Session; import javax.ws.rs.core.Response; /** * * Created by The eXo Platform SAS. * ACL method implementation for Web Distributed Authoring and Versioning (WebDAV) * protocol extension - Access Control Protocol: RFC3744. * * @author <a href="mailto:gavrikvetal@gmail.com">Vitaliy Gulyy</a> * @version $ */ public class AclCommand { /** * logger. */ private static final Log LOG = ExoLogger.getLogger(AclCommand.class); /** * Applies changes for JCR node's {@link AccessControlList} * according to WebDAV ACL method request body represented by {@link HierarchicalProperty} * @param session - actual session * @param path - absolute path to jcr node * @param requestBody - tree like structure to contain ACL method request body * @return response - http response for ACL method request */ public Response acl(Session session, String path, HierarchicalProperty requestBody) { NodeImpl node; try { node = (NodeImpl)session.getItem(path); boolean isSessionToBeSaved = false; boolean nodeIsNotCheckedOut = node.isNodeType("mix:versionable") && !node.isCheckedOut(); // to set ACL the node necessarily must be exo:owneable if (!node.isNodeType("exo:owneable")) { if (nodeIsNotCheckedOut) { node.checkout(); } node.addMixin("exo:owneable"); isSessionToBeSaved = true; } // to set ACL the node necessarily must be exo:privilegeable if (!node.isNodeType("exo:privilegeable")) { if (nodeIsNotCheckedOut) { node.checkout(); } node.addMixin("exo:privilegeable"); isSessionToBeSaved = true; } if (isSessionToBeSaved) { session.save(); if (nodeIsNotCheckedOut) { node.checkin(); session.save(); } } changeNodeACL(node, requestBody); } catch (PathNotFoundException e) { return Response.status(HTTPStatus.NOT_FOUND).entity(e.getMessage()).build(); } catch (RepositoryException exc) { LOG.error(exc.getMessage(), exc); return Response.status(HTTPStatus.INTERNAL_ERROR).entity(exc.getMessage()).build(); } catch (AccessControlException exc) { LOG.error(exc.getMessage(), exc); return Response.status(HTTPStatus.FORBIDDEN).entity(exc.getMessage()).build(); } catch (IllegalArgumentException exc) { LOG.error(exc.getMessage(), exc); return Response.status(HTTPStatus.BAD_REQUEST).entity(exc.getMessage()).build(); } catch (Exception exc) { LOG.error(exc.getMessage(), exc); return Response.status(HTTPStatus.BAD_REQUEST).build(); } return Response.status(HTTPStatus.OK).build(); } /** * Changes JCR node ACL by addition or removal mentioned in {@link HierarchicalProperty} requestBody permissions. * @param node - node to change its ACL * @param requestBody - tree like structure to contain ACL method request body * @throws AccessControlException * @throws RepositoryException */ private void changeNodeACL(NodeImpl node, HierarchicalProperty requestBody) throws AccessControlException, RepositoryException { Map<String, String[]> permissionsToGrant = new HashMap<String, String[]>(); Map<String, String[]> permissionsToDeny = new HashMap<String, String[]>(); for (HierarchicalProperty ace : requestBody.getChildren()) { HierarchicalProperty principalProperty = ace.getChild(ACLProperties.PRINCIPAL); // each ace element must contain principal element // <!ELEMENT ace ((principal | invert), (grant|deny), protected?, inherited?)> if (principalProperty == null) { throw new IllegalArgumentException("Malformed ace element (seems that no principal element specified)"); } String principal; if (principalProperty.getChild(ACLProperties.HREF) != null) { principal = principalProperty.getChild(ACLProperties.HREF).getValue(); } else if (principalProperty.getChild(ACLProperties.ALL) != null) { principal = IdentityConstants.ANY; } // each principal must contain either href or all element // <!ELEMENT principal (href | all | authenticated | unauthenticated | property | self)> else { throw new IllegalArgumentException("Malformed principal element"); } HierarchicalProperty denyProperty = ace.getChild(ACLProperties.DENY); HierarchicalProperty grantProperty = ace.getChild(ACLProperties.GRANT); // each ace element must contain at least one grant or deny property // <!ELEMENT ace ((principal | invert), (grant|deny), protected?, inherited?)> if (denyProperty == null && grantProperty == null) { throw new IllegalArgumentException("Malformed ace element (seems that no deny|grant element specified)"); } if (denyProperty != null) { permissionsToDeny.put(principal, getPermissions(denyProperty)); } if (grantProperty != null) { permissionsToGrant.put(principal, getPermissions(grantProperty)); } // request must not grant and deny the same privilege in a single ace // http://www.webdav.org/specs/rfc3744.html#rfc.section.8.1.5 if (permissionsToDeny.size() != 0 && permissionsToGrant.size() != 0) { for (String denyPermission : permissionsToDeny.get(principal)) { for (String grantPermission : permissionsToGrant.get(principal)) { if (denyPermission.equals(grantPermission)) { throw new IllegalArgumentException( "Malformed ace element (seems that a client is trying to grant and denay the same privilege in " + "a single ace)"); } } } } } if (permissionsToDeny.size() != 0) { for (Entry<String, String[]> entry : permissionsToDeny.entrySet()) { for (String p : entry.getValue()) { node.removePermission(entry.getKey(), p); } } node.getSession().save(); } if (permissionsToGrant.size() != 0) { for (Entry<String, String[]> entry : permissionsToGrant.entrySet()) { node.setPermission(entry.getKey(), entry.getValue()); } node.getSession().save(); } } /** * Processes {@link HierarchicalProperty} instance, representing grant or deny * element of ACL request body, to pull out permissions set represented as {@link String} array. * @param property * @return String[] - permissions set */ private String[] getPermissions(HierarchicalProperty property) { Set<String> permissionsToBeChanged = new HashSet<String>(); // grant|deny element must have at least one privilege element // <!ELEMENT grant (privilege+)> // <!ELEMENT deny (privilege+)> if (property.getChildren().size() == 0) { throw new IllegalArgumentException("Malformed grant|deny element (seems that no privilige is specified)"); } for (HierarchicalProperty propertyRunner : property.getChildren()) { HierarchicalProperty permissionProperty; // obviously privilege must be single named // <!ELEMENT privilege ANY> if (ACLProperties.PRIVILEGE.equals(propertyRunner.getName())) { if (propertyRunner.getChildren().size() > 1) { throw new IllegalArgumentException( "Malformed privilege name (element privilege must contain only one element)"); } permissionProperty = propertyRunner.getChild(0); } else { permissionProperty = propertyRunner; } if (ACLProperties.READ.equals(permissionProperty.getName())) { permissionsToBeChanged.add(PermissionType.READ); } else if (ACLProperties.WRITE.equals(permissionProperty.getName())) { permissionsToBeChanged.add(PermissionType.ADD_NODE); permissionsToBeChanged.add(PermissionType.SET_PROPERTY); permissionsToBeChanged.add(PermissionType.REMOVE); } else if (ACLProperties.ALL.equals(permissionProperty.getName())) { permissionsToBeChanged.add(PermissionType.READ); permissionsToBeChanged.add(PermissionType.ADD_NODE); permissionsToBeChanged.add(PermissionType.SET_PROPERTY); permissionsToBeChanged.add(PermissionType.REMOVE); } // in case privilege with specified name is unsupported // or simply incorrect privilege name else { throw new IllegalArgumentException("Malformed privilege element (unsupported privilege name)"); } } return permissionsToBeChanged.toArray(new String[0]); } }