/**
* 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]);
}
}