From c6e240697688da83169ba3f048c0cee09932a5fb Mon Sep 17 00:00:00 2001 From: Alan Davis Date: Tue, 6 Dec 2016 17:05:36 +0000 Subject: [PATCH] Merged 5.2.0 (5.2.0) to HEAD (5.2) 133021 rmunteanu: REPO-558, REPO-557: Update Permissions For Node - Merged changes from branch 5.2.N-NODEPERMS-REST-API - Added tests for update node permissions git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@133380 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261 --- source/java/org/alfresco/rest/api/Nodes.java | 22 +- .../org/alfresco/rest/api/impl/NodesImpl.java | 164 +++++++ .../org/alfresco/rest/api/model/Node.java | 11 + .../rest/api/model/NodePermissions.java | 170 +++++++ .../alfresco/rest/api/tests/NodeApiTest.java | 464 ++++++++++++++++-- .../rest/api/tests/client/data/Node.java | 11 + 6 files changed, 784 insertions(+), 58 deletions(-) create mode 100644 source/java/org/alfresco/rest/api/model/NodePermissions.java diff --git a/source/java/org/alfresco/rest/api/Nodes.java b/source/java/org/alfresco/rest/api/Nodes.java index 257685269f..5a0d0c562f 100644 --- a/source/java/org/alfresco/rest/api/Nodes.java +++ b/source/java/org/alfresco/rest/api/Nodes.java @@ -25,7 +25,19 @@ */ package org.alfresco.rest.api; -import org.alfresco.rest.api.model.*; +import java.io.InputStream; +import java.io.Serializable; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.alfresco.rest.api.model.AssocChild; +import org.alfresco.rest.api.model.AssocTarget; +import org.alfresco.rest.api.model.Document; +import org.alfresco.rest.api.model.Folder; +import org.alfresco.rest.api.model.LockInfo; +import org.alfresco.rest.api.model.Node; +import org.alfresco.rest.api.model.UserInfo; import org.alfresco.rest.framework.resource.content.BasicContentInfo; import org.alfresco.rest.framework.resource.content.BinaryResource; import org.alfresco.rest.framework.resource.parameters.CollectionWithPagingInfo; @@ -35,12 +47,6 @@ import org.alfresco.service.cmr.repository.StoreRef; import org.alfresco.service.namespace.QName; import org.springframework.extensions.webscripts.servlet.FormData; -import java.io.InputStream; -import java.io.Serializable; -import java.util.List; -import java.util.Map; -import java.util.Set; - /** * File Folder (Nodes) API * @@ -323,6 +329,7 @@ public interface Nodes String OP_CREATE = "create"; String OP_DELETE = "delete"; String OP_UPDATE = "update"; + String OP_UPDATE_PERMISSIONS = "updatePermissions"; String PARAM_RELATIVE_PATH = "relativePath"; String PARAM_PERMANENT = "permanent"; @@ -333,6 +340,7 @@ public interface Nodes String PARAM_INCLUDE_ISLINK = "isLink"; String PARAM_INCLUDE_ISLOCKED = "isLocked"; String PARAM_INCLUDE_ALLOWABLEOPERATIONS = "allowableOperations"; + String PARAM_INCLUDE_PERMISSIONS = "permissions"; String PARAM_INCLUDE_ASSOCIATION = "association"; diff --git a/source/java/org/alfresco/rest/api/impl/NodesImpl.java b/source/java/org/alfresco/rest/api/impl/NodesImpl.java index ae05652ddd..74a67e7bb6 100644 --- a/source/java/org/alfresco/rest/api/impl/NodesImpl.java +++ b/source/java/org/alfresco/rest/api/impl/NodesImpl.java @@ -86,6 +86,7 @@ import org.alfresco.rest.api.model.Document; import org.alfresco.rest.api.model.Folder; import org.alfresco.rest.api.model.LockInfo; import org.alfresco.rest.api.model.Node; +import org.alfresco.rest.api.model.NodePermissions; import org.alfresco.rest.api.model.PathInfo; import org.alfresco.rest.api.model.PathInfo.ElementInfo; import org.alfresco.rest.api.model.QuickShareLink; @@ -143,6 +144,7 @@ import org.alfresco.service.cmr.repository.NodeService; import org.alfresco.service.cmr.repository.Path; import org.alfresco.service.cmr.repository.Path.Element; import org.alfresco.service.cmr.repository.StoreRef; +import org.alfresco.service.cmr.security.AccessPermission; import org.alfresco.service.cmr.security.AccessStatus; import org.alfresco.service.cmr.security.AuthorityService; import org.alfresco.service.cmr.security.OwnableService; @@ -917,6 +919,8 @@ public class NodesImpl implements Nodes mapPermsToOps.put(PermissionService.DELETE, OP_DELETE); mapPermsToOps.put(PermissionService.ADD_CHILDREN, OP_CREATE); mapPermsToOps.put(PermissionService.WRITE, OP_UPDATE); + mapPermsToOps.put(PermissionService.CHANGE_PERMISSIONS, OP_UPDATE_PERMISSIONS); + List allowableOperations = new ArrayList<>(3); for (Entry kv : mapPermsToOps.entrySet()) @@ -943,6 +947,39 @@ public class NodesImpl implements Nodes node.setAllowableOperations((allowableOperations.size() > 0 )? allowableOperations : null); } + if (includeParam.contains(PARAM_INCLUDE_PERMISSIONS)) + { + Boolean inherit = permissionService.getInheritParentPermissions(nodeRef); + + List inheritedPerms = new ArrayList<>(5); + List setDirectlyPerms = new ArrayList<>(5); + Set settablePerms = null; + + try + { + for (AccessPermission accessPerm : permissionService.getAllSetPermissions(nodeRef)) + { + NodePermissions.NodePermission nodePerm = new NodePermissions.NodePermission(accessPerm.getAuthority(), accessPerm.getPermission(), accessPerm.getAccessStatus().toString()); + if (accessPerm.isSetDirectly()) + { + setDirectlyPerms.add(nodePerm); + } else + { + inheritedPerms.add(nodePerm); + } + } + + settablePerms = permissionService.getSettablePermissions(nodeRef); + } + catch (AccessDeniedException ade) + { + // ignore - ie. denied access to retrieve permissions, eg. non-admin on root (Company Home) + } + + NodePermissions nodePerms = new NodePermissions(inherit, inheritedPerms, setDirectlyPerms, settablePerms); + node.setPermissions(nodePerms); + } + if (includeParam.contains(PARAM_INCLUDE_ASSOCIATION)) { // Ugh ... can we optimise this and return the actual assoc directly (via FileFolderService/GetChildrenCQ) ? @@ -2145,6 +2182,113 @@ public class NodesImpl implements Nodes props.put(ContentModel.PROP_NAME, name); } + NodePermissions nodePerms = nodeInfo.getPermissions(); + if (nodePerms != null) + { + // Cannot set inherited permissions, only direct (locally set) permissions can be set + if ((nodePerms.getInherited() != null) && (nodePerms.getInherited().size() > 0)) + { + throw new InvalidArgumentException("Cannot set *inherited* permissions on this node"); + } + + // Check inherit from parent value and if it's changed set the new value + if (nodePerms.isInheritanceEnabled() != null) + { + if (nodePerms.isInheritanceEnabled() != permissionService.getInheritParentPermissions(nodeRef)) + { + permissionService.setInheritParentPermissions(nodeRef, nodePerms.isInheritanceEnabled()); + } + } + + // set direct permissions + if ((nodePerms.getLocallySet() != null)) + { + // list of all directly set permissions + Set directPerms = new HashSet<>(5); + for (AccessPermission accessPerm : permissionService.getAllSetPermissions(nodeRef)) + { + if (accessPerm.isSetDirectly()) + { + directPerms.add(accessPerm); + } + } + + // + // replace (or clear) set of direct permissions + // + + // TODO cleanup the way we replace permissions (ie. add, update and delete) + + // check if same permission is sent more than once + if (hasDuplicatePermissions(nodePerms.getLocallySet())) + { + throw new InvalidArgumentException("Duplicate node permissions, there is more than one permission with the same authority and name!"); + } + + for (NodePermissions.NodePermission nodePerm : nodePerms.getLocallySet()) + { + String permName = nodePerm.getName(); + String authorityId = nodePerm.getAuthorityId(); + + AccessStatus accessStatus = AccessStatus.ALLOWED; + if (nodePerm.getAccessStatus() != null) + { + accessStatus = AccessStatus.valueOf(nodePerm.getAccessStatus()); + } + + if ((authorityId == null) || + ((! authorityId.equals(PermissionService.ALL_AUTHORITIES) && (! authorityService.authorityExists(authorityId))))) + { + throw new InvalidArgumentException("Cannot set permissions on this node - unknown authority: "+authorityId); + } + + AccessPermission existing = null; + boolean addPerm = true; + boolean updatePerm = false; + + // If the permission already exists but with different access status it will be updated + for (AccessPermission accessPerm : directPerms) + { + if (accessPerm.getAuthority().equals(authorityId) && accessPerm.getPermission().equals(permName)) + { + existing = accessPerm; + addPerm = false; + + if (accessPerm.getAccessStatus() != accessStatus) + { + updatePerm = true; + } + break; + } + } + + if (existing != null) + { + // ignore existing permissions + directPerms.remove(existing); + } + + if (addPerm || updatePerm) + { + try + { + permissionService.setPermission(nodeRef, authorityId, permName, (accessStatus == AccessStatus.ALLOWED)); + } + catch (UnsupportedOperationException e) + { + throw new InvalidArgumentException("Cannot set permissions on this node - unknown access level: " + permName); + } + } + } + + // remove any remaining direct perms + for (AccessPermission accessPerm : directPerms) + { + permissionService.deletePermission(nodeRef, accessPerm.getAuthority(), accessPerm.getPermission()); + } + } + } + String nodeType = nodeInfo.getNodeType(); if ((nodeType != null) && (! nodeType.isEmpty())) { @@ -3105,6 +3249,26 @@ public class NodesImpl implements Nodes } /** + * Checks if same permission is sent more than once + * @param locallySetPermissions + * @return + */ + private boolean hasDuplicatePermissions(List locallySetPermissions) + { + boolean duplicate = false; + if (locallySetPermissions != null) + { + HashSet temp = new HashSet(locallySetPermissions.size()); + for (NodePermissions.NodePermission permission : locallySetPermissions) + { + temp.add(permission); + } + duplicate = (locallySetPermissions.size() != temp.size()); + } + return duplicate; + } + + /** * @author Jamal Kaabi-Mofrad */ /* diff --git a/source/java/org/alfresco/rest/api/model/Node.java b/source/java/org/alfresco/rest/api/model/Node.java index 0605ad18d2..f668225818 100644 --- a/source/java/org/alfresco/rest/api/model/Node.java +++ b/source/java/org/alfresco/rest/api/model/Node.java @@ -91,6 +91,7 @@ public class Node implements Comparable protected Map properties; protected List allowableOperations; + protected NodePermissions nodePermissions; //optional SearchEntry (only ever returned from a search) protected SearchEntry search = null; @@ -336,6 +337,16 @@ public class Node implements Comparable this.allowableOperations = allowableOperations; } + public NodePermissions getPermissions() + { + return nodePermissions; + } + + public void setPermissions(NodePermissions nodePermissions) + { + this.nodePermissions = nodePermissions; + } + public List getTargets() { return targets; diff --git a/source/java/org/alfresco/rest/api/model/NodePermissions.java b/source/java/org/alfresco/rest/api/model/NodePermissions.java new file mode 100644 index 0000000000..00be3c6c6b --- /dev/null +++ b/source/java/org/alfresco/rest/api/model/NodePermissions.java @@ -0,0 +1,170 @@ +/* + * #%L + * Alfresco Remote API + * %% + * Copyright (C) 2005 - 2016 Alfresco Software Limited + * %% + * This file is part of the Alfresco software. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * Alfresco 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 3 of the License, or + * (at your option) any later version. + * + * Alfresco 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 Alfresco. If not, see . + * #L% + */ +package org.alfresco.rest.api.model; + +import java.util.List; +import java.util.Set; + +import org.alfresco.service.cmr.security.AccessStatus; + + +/** + * Representation of Node Permissions + * + * @author janv + */ +public class NodePermissions +{ + private Boolean inherit; + private List inherited; + private List locallySet; + private Set settable; + + public NodePermissions() + { + } + + public NodePermissions(Boolean inherit, + List inherited, + List locallySet, + Set settable) + { + this.inherit = inherit; + this.inherited = inherited; + this.locallySet = locallySet; + this.settable = settable; + } + + public Boolean isInheritanceEnabled() + { + return inherit; + } + + public void setInheritanceEnabled(boolean inherit) + { + this.inherit = inherit; + } + + public List getInherited() + { + return inherited; + } + + public List getLocallySet() + { + return locallySet; + } + + public void setLocallySet(List directPermissions) + { + this.locallySet = directPermissions; + } + + public Set getSettable() + { + return settable; + } + + @Override + public String toString() + { + final StringBuilder sb = new StringBuilder(120); + sb.append("PathInfo [inheritanceEnabled=").append(inherit) + .append(", inherited=").append(getInherited()) + .append(", locallySet=").append(getLocallySet()) + .append(", settable=").append(getSettable()) + .append(']'); + return sb.toString(); + } + + public static class NodePermission + { + + private String authorityId; + private String name; + private String accessStatus; + + public NodePermission() + { + } + + public NodePermission(String authorityId, String name, String accessStatus) + { + this.authorityId = authorityId; + this.name = name; + this.accessStatus = accessStatus != null ? accessStatus : AccessStatus.ALLOWED.toString(); + } + + public String getName() + { + return name; + } + + public String getAuthorityId() + { + return authorityId; + } + + public String getAccessStatus() + { + return accessStatus; + } + + @Override + public String toString() + { + final StringBuilder sb = new StringBuilder(250); + sb.append("NodePermission [authorityId=").append(authorityId) + .append(", name=").append(name) + .append(", accessStatus=").append(accessStatus) + .append(']'); + return sb.toString(); + } + + @Override + public boolean equals(Object o) + { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + + NodePermission that = (NodePermission) o; + + if (!authorityId.equals(that.authorityId)) + return false; + return name.equals(that.name); + } + + @Override + public int hashCode() + { + int result = authorityId.hashCode(); + result = 31 * result + name.hashCode(); + return result; + } + } +} diff --git a/source/test-java/org/alfresco/rest/api/tests/NodeApiTest.java b/source/test-java/org/alfresco/rest/api/tests/NodeApiTest.java index 786f895187..e7b438cb41 100644 --- a/source/test-java/org/alfresco/rest/api/tests/NodeApiTest.java +++ b/source/test-java/org/alfresco/rest/api/tests/NodeApiTest.java @@ -59,6 +59,7 @@ import org.alfresco.repo.tenant.TenantUtil; import org.alfresco.rest.AbstractSingleNetworkSiteTest; import org.alfresco.rest.api.Nodes; import org.alfresco.rest.api.model.LockInfo; +import org.alfresco.rest.api.model.NodePermissions; import org.alfresco.rest.api.model.NodeTarget; import org.alfresco.rest.api.model.Site; import org.alfresco.rest.api.nodes.NodesEntityResource; @@ -83,11 +84,14 @@ import org.alfresco.rest.api.tests.util.RestApiUtil; import org.alfresco.service.cmr.lock.LockType; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.StoreRef; +import org.alfresco.service.cmr.security.AccessPermission; +import org.alfresco.service.cmr.security.AccessStatus; +import org.alfresco.service.cmr.security.AuthorityService; +import org.alfresco.service.cmr.security.AuthorityType; import org.alfresco.service.cmr.security.PermissionService; import org.alfresco.service.cmr.site.SiteVisibility; import org.alfresco.util.GUID; import org.alfresco.util.TempFileProvider; -import org.apache.http.HttpStatus; import org.json.simple.JSONObject; import org.junit.After; import org.junit.Before; @@ -117,14 +121,20 @@ public class NodeApiTest extends AbstractSingleNetworkSiteTest private static final String EMPTY_BODY = "{}"; protected PermissionService permissionService; + protected AuthorityService authorityService; + + private String rootGroupName = null; + private String groupA = null; + private String groupB = null; + private String groupC = null; - @Before public void setup() throws Exception { super.setup(); permissionService = applicationContext.getBean("permissionService", PermissionService.class); + authorityService = (AuthorityService) applicationContext.getBean("AuthorityService"); } @After @@ -3872,65 +3882,417 @@ public class NodeApiTest extends AbstractSingleNetworkSiteTest } - /** - * Tests unlock of a node - *

POST:

- * {@literal :/alfresco/api/-default-/public/alfresco/versions/1/nodes//unlock} - */ - @Test - public void testUnlock() throws Exception - { - setRequestContext(user1); + /** + * Tests unlock of a node + *

POST:

+ * {@literal :/alfresco/api/-default-/public/alfresco/versions/1/nodes//unlock} + */ + @Test + public void testUnlock() throws Exception + { + setRequestContext(user1); - // create folder - Folder folderResp = createFolder(Nodes.PATH_MY, "folder" + RUNID); - String folderId = folderResp.getId(); - - // create doc d1 - String d1Name = "content" + RUNID + "_1l"; - Document d1 = createTextFile(folderId, d1Name, "The quick brown fox jumps over the lazy dog 1."); - String d1Id = d1.getId(); + // create folder + Folder folderResp = createFolder(Nodes.PATH_MY, "folder" + RUNID); + String folderId = folderResp.getId(); - lock(d1Id, EMPTY_BODY); + // create doc d1 + String d1Name = "content" + RUNID + "_1l"; + Document d1 = createTextFile(folderId, d1Name, "The quick brown fox jumps over the lazy dog 1."); + String d1Id = d1.getId(); - HttpResponse response = post(getNodeOperationUrl(d1Id, "unlock"), null, null, 200); - Document documentResp = RestApiUtil.parseRestApiEntry(response.getJsonResponse(), Document.class); + lock(d1Id, EMPTY_BODY); - assertEquals(d1Name, documentResp.getName()); - assertEquals(d1Id, documentResp.getId()); - assertNull(documentResp.getProperties().get("cm:lockType")); - assertNull(documentResp.getProperties().get("cm:lockOwner")); + HttpResponse response = post(getNodeOperationUrl(d1Id, "unlock"), null, null, 200); + Document documentResp = RestApiUtil.parseRestApiEntry(response.getJsonResponse(), Document.class); - lock(d1Id, EMPTY_BODY); - // Users with admin rights can unlock nodes locked by other users. - setRequestContext(networkAdmin); - post(getNodeOperationUrl(d1Id, "unlock"), null, null, 200); + assertEquals(d1Name, documentResp.getName()); + assertEquals(d1Id, documentResp.getId()); + assertNull(documentResp.getProperties().get("cm:lockType")); + assertNull(documentResp.getProperties().get("cm:lockOwner")); - // -ve - // Missing target node - post(getNodeOperationUrl("fakeId", "unlock"), null, null, 404); + lock(d1Id, EMPTY_BODY); + // Users with admin rights can unlock nodes locked by other users. + setRequestContext(networkAdmin); + post(getNodeOperationUrl(d1Id, "unlock"), null, null, 200); - // Unlock by a user without permission - lock(d1Id, EMPTY_BODY); - setRequestContext(user2); - post(getNodeOperationUrl(d1Id, "unlock"), null, null, 403); + // -ve + // Missing target node + post(getNodeOperationUrl("fakeId", "unlock"), null, null, 404); - setRequestContext(user1); - - //Unlock on a not locked node - post(getNodeOperationUrl(folderId, "unlock"), null, null, 422); + // Unlock by a user without permission + lock(d1Id, EMPTY_BODY); + setRequestContext(user2); + post(getNodeOperationUrl(d1Id, "unlock"), null, null, 403); - // clean up - setRequestContext(user1); // all locks were made by user1 + setRequestContext(user1); - unlock(d1Id); - deleteNode(folderId); - } + //Unlock on a not locked node + post(getNodeOperationUrl(folderId, "unlock"), null, null, 422); - @Override - public String getScope() - { - return "public"; - } + // clean up + setRequestContext(user1); // all locks were made by user1 + + unlock(d1Id); + deleteNode(folderId); + } + + /** + * Creates authority context + * + * @param user + * @return + */ + private String createAuthorityContext(String user) + { + AuthenticationUtil.setRunAsUser(user); + if (rootGroupName == null) + { + rootGroupName = authorityService.getName(AuthorityType.GROUP, "GroupsTest_ROOT"); + } + + if (!authorityService.authorityExists(rootGroupName)) + { + AuthenticationUtil.setAdminUserAsFullyAuthenticatedUser(); + rootGroupName = authorityService.createAuthority(AuthorityType.GROUP, "GroupsTest_ROOT"); + groupA = authorityService.createAuthority(AuthorityType.GROUP, "Test_GroupA"); + authorityService.addAuthority(rootGroupName, groupA); + groupB = authorityService.createAuthority(AuthorityType.GROUP, "Test_GroupB"); + authorityService.addAuthority(rootGroupName, groupB); + groupC = authorityService.createAuthority(AuthorityType.GROUP, "Test_GroupC"); + authorityService.addAuthority(rootGroupName, groupC); + } + + return rootGroupName; + } + + /** + * Clears authority context: removes root group and all child groups + */ + private void clearAuthorityContext() + { + if (authorityService.authorityExists(rootGroupName)) + { + AuthenticationUtil.setAdminUserAsFullyAuthenticatedUser(); + authorityService.deleteAuthority(rootGroupName, true); + } + } + + /** + * Tests set permissions on an existing node + * + * @throws Exception + */ + //@Test + public void testUpdateNodePermissions() throws Exception + { + try + { + createAuthorityContext(networkAdmin); + + setRequestContext(user1); + // +ve tests + testUpdatePermissionsOnNode(); + + // -ve tests + // invalid permission tests (authority, name or access level) + testUpdatePermissionInvalidAuthority(); + testUpdatePermissionInvalidName(); + testUpdatePermissionInvalidAccessStatus(); + + // 'Permission Denied' tests + testUpdatePermissionsPermissionDeniedUser(); + testUpdatePermissionsOnCompanyHome(); + + // Inherit from parent tests + testUpdatePermissionsDefaultInheritFromParent(); + testUpdatePermissionsSetFalseInheritFromParent(); + } + finally + { + clearAuthorityContext(); + } + } + + /** + * Test update permission on a node + * + * @throws Exception + */ + private void testUpdatePermissionsOnNode() throws Exception + { + // create folder with an empty document + String postUrl = createFolder(); + String docId = createDocument(postUrl); + + // update permissions + Document dUpdate = new Document(); + NodePermissions nodePermissions = new NodePermissions(); + List locallySetPermissions = new ArrayList<>(); + locallySetPermissions.add(new NodePermissions.NodePermission(groupA, "Consumer", AccessStatus.ALLOWED.toString())); + locallySetPermissions.add(new NodePermissions.NodePermission(groupB, "Editor", AccessStatus.DENIED.toString())); + nodePermissions.setLocallySet(locallySetPermissions); + dUpdate.setPermissions(nodePermissions); + + // update node + HttpResponse response = put(URL_NODES, docId, toJsonAsStringNonNull(dUpdate), null, 200); + Document documentResp = RestApiUtil.parseRestApiEntry(response.getJsonResponse(), Document.class); + + validatePermissionsAfterUpdate(new NodeRef(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, documentResp.getId()), locallySetPermissions); + } + + /** + * Test attempt to set permission with an invalid authority + * + * @throws Exception + */ + private void testUpdatePermissionInvalidAuthority() throws Exception + { + // create folder containing an empty document + String postUrl = createFolder(); + String dId = createDocument(postUrl); + + // update permissions + Document dUpdate = new Document(); + NodePermissions nodePermissions = new NodePermissions(); + List locallySetPermissions = new ArrayList<>(); + locallySetPermissions.add(new NodePermissions.NodePermission("NonExistingAuthority", "Consumer", AccessStatus.DENIED.toString())); + nodePermissions.setLocallySet(locallySetPermissions); + dUpdate.setPermissions(nodePermissions); + + // "Cannot set permissions on this node - unknown authority: + // NonExistingAuthority" + put(URL_NODES, dId, toJsonAsStringNonNull(dUpdate), null, 400); + } + + /** + * Test attempt to set permission with an invalid name + * + * @throws Exception + */ + private void testUpdatePermissionInvalidName() throws Exception + { + // create folder with an empty document + String postUrl = createFolder(); + String dId = createDocument(postUrl); + + // update permissions + Document dUpdate = new Document(); + NodePermissions nodePermissions = new NodePermissions(); + List locallySetPermissions = new ArrayList<>(); + locallySetPermissions.add(new NodePermissions.NodePermission(groupA, "InvalidName", AccessStatus.DENIED.toString())); + nodePermissions.setLocallySet(locallySetPermissions); + dUpdate.setPermissions(nodePermissions); + + // "Cannot set permissions on this node - unknown permission name: + // InvalidName" + put(URL_NODES, dId, toJsonAsStringNonNull(dUpdate), null, 400); + } + + /** + * Test attempt to set permission with an invalid access status + * + * @throws Exception + */ + private void testUpdatePermissionInvalidAccessStatus() throws Exception + { + // create folder with an empty document + String postUrl = createFolder(); + String dId = createDocument(postUrl); + + // update permissions + Document dUpdate = new Document(); + NodePermissions nodePermissions = new NodePermissions(); + List locallySetPermissions = new ArrayList<>(); + locallySetPermissions.add(new NodePermissions.NodePermission(groupA, "Consumer", "InvalidAccessLevel")); + nodePermissions.setLocallySet(locallySetPermissions); + dUpdate.setPermissions(nodePermissions); + + // "Cannot set permissions on this node - unknown access status: + // InvalidName" + put(URL_NODES, dId, toJsonAsStringNonNull(dUpdate), null, 400); + } + + /** + * Tests updating permissions on a node that user doesn't have permission for + * + * @throws Exception + */ + private void testUpdatePermissionsPermissionDeniedUser() throws Exception + { + // create folder with an empty document + String postUrl = createFolder(); + String dId = createDocument(postUrl); + + // update permissions + Document dUpdate = new Document(); + NodePermissions nodePermissions = new NodePermissions(); + List locallySetPermissions = new ArrayList<>(); + locallySetPermissions.add(new NodePermissions.NodePermission(groupA, "Consumer", AccessStatus.DENIED.toString())); + nodePermissions.setLocallySet(locallySetPermissions); + dUpdate.setPermissions(nodePermissions); + + setRequestContext(user2); + // "Permission Denied" expected + put(URL_NODES, dId, toJsonAsStringNonNull(dUpdate), null, 403); + } + + /** + * Test update permissions on 'Company Home' + * + * @throws Exception + */ + private void testUpdatePermissionsOnCompanyHome() throws Exception + { + HttpResponse response = getSingle(NodesEntityResource.class, Nodes.PATH_ROOT, null, 200); + Node node = RestApiUtil.parseRestApiEntry(response.getJsonResponse(), Node.class); + NodePermissions nodePermissions = new NodePermissions(); + List locallySetPermissions = new ArrayList<>(); + locallySetPermissions.add(new NodePermissions.NodePermission(groupA, "Editor", AccessStatus.ALLOWED.toString())); + nodePermissions.setLocallySet(locallySetPermissions); + node.setPermissions(nodePermissions); + + // "Permission Denied" expected + put(URL_NODES, node.getId(), toJsonAsStringNonNull(node), null, 403); + } + + /** + * Test default inherit from parent + * + * @throws Exception + */ + private void testUpdatePermissionsDefaultInheritFromParent() throws Exception + { + // create folder + Folder folder = new Folder(); + folder.setName("testFolder" + GUID.generate()); + folder.setNodeType(TYPE_CM_FOLDER); + + // set permissions on previously created folder + NodePermissions nodePermissions = new NodePermissions(); + List locallySetPermissions = new ArrayList<>(); + locallySetPermissions.add(new NodePermissions.NodePermission(groupA, "Editor", AccessStatus.DENIED.toString())); + nodePermissions.setLocallySet(locallySetPermissions); + folder.setPermissions(nodePermissions); + + HttpResponse response = post(getNodeChildrenUrl(Nodes.PATH_MY), RestApiUtil.toJsonAsStringNonNull(folder), 201); + Folder f = RestApiUtil.parseRestApiEntry(response.getJsonResponse(), Folder.class); + + // create a new document in testFolder + String docId = createDocument(getNodeChildrenUrl(f.getId())); + + Map params = new HashMap<>(); + params.put("include", "permissions"); + + response = getSingle(NodesEntityResource.class, docId, params, 200); + Document docResp = RestApiUtil.parseRestApiEntry(response.getJsonResponse(), Document.class); + + assertTrue("Inheritance hasn't been enabled!", docResp.getPermissions().isInheritanceEnabled()); + assertTrue("Permissions were not inherited from parent!", docResp.getPermissions().getInherited().size() > 0); + } + + /** + * Test set inherit from parent to false + * + * @throws Exception + */ + private void testUpdatePermissionsSetFalseInheritFromParent() throws Exception + { + // create folder + String testFolderUrl = createFolder(); + String testDocId = createDocument(testFolderUrl); + + // create a new document in testFolder and set inherit to false + Document dUpdate = new Document(); + NodePermissions nodePermissionsUpdate = new NodePermissions(); + nodePermissionsUpdate.setInheritanceEnabled(false); + dUpdate.setPermissions(nodePermissionsUpdate); + + HttpResponse response = put(URL_NODES, testDocId, toJsonAsStringNonNull(dUpdate), null, 200); + Document documentResp = RestApiUtil.parseRestApiEntry(response.getJsonResponse(), Document.class); + + Map params = new HashMap<>(); + params.put("include", "permissions"); + + response = getSingle(NodesEntityResource.class, documentResp.getId(), params, 200); + Node nodeResp = RestApiUtil.parseRestApiEntry(response.getJsonResponse(), Node.class); + + assertFalse("Inheritance hasn't been disabled!" + nodeResp.getPermissions().isInheritanceEnabled(), nodeResp.getPermissions().isInheritanceEnabled()); + assertNull("Permissions were inherited from parent!", nodeResp.getPermissions().getInherited()); + + } + + private String createFolder() throws Exception + { + String folderName = "testPermissionsFolder-" + GUID.generate(); + String folderId = createFolder(Nodes.PATH_MY, folderName).getId(); + return getNodeChildrenUrl(folderId); + } + + /** + * Created an empty document in the given url path + * + * @param url + * @return + * @throws Exception + */ + private String createDocument(String url) throws Exception + { + Document d1 = new Document(); + d1.setName("testDoc" + GUID.generate()); + d1.setNodeType(TYPE_CM_CONTENT); + + HttpResponse response = post(url, toJsonAsStringNonNull(d1), 201); + Document documentResp = RestApiUtil.parseRestApiEntry(response.getJsonResponse(), Document.class); + return documentResp.getId(); + } + + private void validatePermissionsAfterUpdate(NodeRef nodeRef, List expectedPermissions) + { + Set permissions = permissionService.getAllSetPermissions(nodeRef); + + for (NodePermissions.NodePermission permission : expectedPermissions) + { + String authority = permission.getAuthorityId(); + AccessPermission ap = getPermission(permissions, authority); + assertNotNull("Permission " + authority + " missing", ap); + + assertEquals(authority, ap.getAuthority()); + comparePermissions(authority, permission, ap); + } + } + + private void comparePermissions(String authority, NodePermissions.NodePermission permission, AccessPermission ap) + { + assertEquals("Wrong permission for " + authority, permission.getAuthorityId(), ap.getAuthority()); + assertEquals("Wrong permission for " + authority, permission.getName(), ap.getPermission()); + assertEquals("Wrong access status for " + authority, permission.getAccessStatus(), ap.getAccessStatus().toString()); + } + + /** + * Searches through actual set of permissions + * + * @param permissions + * @param expectedAuthority + * @return + */ + private AccessPermission getPermission(Set permissions, String expectedAuthority) + { + AccessPermission result = null; + for (AccessPermission ap : permissions) + { + if (expectedAuthority.equals(ap.getAuthority())) + { + result = ap; + } + } + return result; + } + + @Override + public String getScope() + { + return "public"; + } } diff --git a/source/test-java/org/alfresco/rest/api/tests/client/data/Node.java b/source/test-java/org/alfresco/rest/api/tests/client/data/Node.java index 784fa25ae2..cb703b77ac 100644 --- a/source/test-java/org/alfresco/rest/api/tests/client/data/Node.java +++ b/source/test-java/org/alfresco/rest/api/tests/client/data/Node.java @@ -27,6 +27,7 @@ package org.alfresco.rest.api.tests.client.data; import org.alfresco.rest.api.model.AssocChild; import org.alfresco.rest.api.model.AssocTarget; +import org.alfresco.rest.api.model.NodePermissions; import java.util.Date; import java.util.List; @@ -77,6 +78,7 @@ public class Node protected ContentInfo contentInfo; protected List allowableOperations; + protected NodePermissions nodePermissions; // please note: these are currently only used (optionally) for node create request protected String relativePath; @@ -279,7 +281,16 @@ public class Node this.allowableOperations = allowableOperations; } + public NodePermissions getPermissions() + { + return nodePermissions; + } + public void setPermissions(NodePermissions nodePermissions) + { + this.nodePermissions = nodePermissions; + } + public String getRelativePath() { return relativePath;