diff --git a/source/java/org/alfresco/rest/api/QuickShareLinks.java b/source/java/org/alfresco/rest/api/QuickShareLinks.java index cb885fb5ac..d2e45e23ef 100644 --- a/source/java/org/alfresco/rest/api/QuickShareLinks.java +++ b/source/java/org/alfresco/rest/api/QuickShareLinks.java @@ -102,4 +102,6 @@ public interface QuickShareLinks * API Constants - query parameters, etc */ String PARAM_SHAREDBY = "sharedByUser"; + + String PARAM_SELECT_ISLINK = "allowableOperations"; } \ No newline at end of file diff --git a/source/java/org/alfresco/rest/api/impl/QuickShareLinksImpl.java b/source/java/org/alfresco/rest/api/impl/QuickShareLinksImpl.java index 62ea6712a3..3937965e0f 100644 --- a/source/java/org/alfresco/rest/api/impl/QuickShareLinksImpl.java +++ b/source/java/org/alfresco/rest/api/impl/QuickShareLinksImpl.java @@ -25,6 +25,7 @@ import org.alfresco.repo.quickshare.QuickShareServiceImpl.QuickShareEmailRequest import org.alfresco.repo.search.QueryParameterDefImpl; import org.alfresco.repo.security.authentication.AuthenticationUtil; import org.alfresco.repo.security.permissions.AccessDeniedException; +import org.alfresco.repo.site.SiteModel; import org.alfresco.repo.tenant.TenantUtil; import org.alfresco.rest.antlr.WhereClauseParser; import org.alfresco.rest.api.Nodes; @@ -67,6 +68,7 @@ import org.alfresco.service.cmr.search.SearchParameters; import org.alfresco.service.cmr.search.SearchService; import org.alfresco.service.cmr.security.AuthorityService; import org.alfresco.service.cmr.security.PersonService; +import org.alfresco.service.cmr.site.SiteService; import org.alfresco.service.namespace.NamespaceService; import org.alfresco.service.namespace.QName; import org.alfresco.util.Pair; @@ -76,10 +78,13 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.beans.factory.InitializingBean; import org.springframework.extensions.surf.util.I18NUtil; +import org.springframework.extensions.webscripts.WebScriptException; +import javax.servlet.http.HttpServletResponse; import java.io.Serializable; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.HashSet; @@ -114,6 +119,7 @@ public class QuickShareLinksImpl implements QuickShareLinks, InitializingBean private SearchService searchService; private DictionaryService dictionaryService; private NamespaceService namespaceService; + private SiteService siteService; public void setServiceRegistry(ServiceRegistry sr) { @@ -155,6 +161,7 @@ public class QuickShareLinksImpl implements QuickShareLinks, InitializingBean this.searchService = sr.getSearchService(); this.dictionaryService = sr.getDictionaryService(); this.namespaceService = sr.getNamespaceService(); + this.siteService = sr.getSiteService(); } /** @@ -162,7 +169,7 @@ public class QuickShareLinksImpl implements QuickShareLinks, InitializingBean *

* Note: does *not* require authenticated access for (public) shared link. */ - public QuickShareLink readById(final String sharedId, Parameters parameters) + public QuickShareLink readById(final String sharedId, final Parameters parameters) { checkEnabled(); @@ -177,7 +184,7 @@ public class QuickShareLinksImpl implements QuickShareLinks, InitializingBean { public QuickShareLink doWork() throws Exception { - return getQuickShareInfo(sharedId, noAuth); + return getQuickShareInfo(sharedId, noAuth, parameters.getSelectedProperties()); } }, networkTenantDomain); } @@ -264,14 +271,9 @@ public class QuickShareLinksImpl implements QuickShareLinks, InitializingBean try { NodeRef nodeRef = quickShareService.getTenantNodeRefFromSharedId(sharedId).getSecond(); - String currentUser = AuthenticationUtil.getFullyAuthenticatedUser(); - // TODO site check - see ACE-XXX - //String siteName = getSiteName(nodeRef); - - String sharedBy = (String) nodeService.getProperty(nodeRef, QuickShareModel.PROP_QSHARE_SHAREDBY); - - if ((!currentUser.equals(sharedBy)) && (!authorityService.isAdminAuthority(currentUser))) + String sharedByUserId = (String)nodeService.getProperty(nodeRef, QuickShareModel.PROP_QSHARE_SHAREDBY); + if (! canDeleteSharedLink(nodeRef, sharedByUserId)) { throw new PermissionDeniedException("Can't perform unshare action: " + sharedId); } @@ -307,6 +309,8 @@ public class QuickShareLinksImpl implements QuickShareLinks, InitializingBean boolean noAuth = (AuthenticationUtil.getRunAsUser() == null); + List selectParam = parameters.getSelectedProperties(); + for (QuickShareLink qs : nodeIds) { String nodeId = qs.getNodeId(); @@ -332,7 +336,7 @@ public class QuickShareLinksImpl implements QuickShareLinks, InitializingBean try { QuickShareDTO qsDto = quickShareService.shareContent(nodeRef); - result.add(getQuickShareInfo(qsDto.getId(), noAuth)); + result.add(getQuickShareInfo(qsDto.getId(), noAuth, selectParam)); } catch (InvalidNodeRefException inre) { @@ -434,17 +438,18 @@ public class QuickShareLinksImpl implements QuickShareLinks, InitializingBean sp.setSkipCount(pagingRequest.getSkipCount()); sp.setMaxItems(pagingRequest.getMaxItems()); - // TODO is perf ok with Solr 4 paging/sorting ? (otherwise make this optional via orderBy and leave default sort as undefined) sp.addSort("@" + ContentModel.PROP_MODIFIED, false); ResultSet results = searchService.query(sp); List qsLinks = new ArrayList<>(results.length()); + List selectParam = parameters.getSelectedProperties(); + for (ResultSetRow row : results) { NodeRef nodeRef = row.getNodeRef(); - qsLinks.add(getQuickShareInfo(nodeRef, false)); + qsLinks.add(getQuickShareInfo(nodeRef, false, selectParam)); } results.close(); @@ -452,23 +457,23 @@ public class QuickShareLinksImpl implements QuickShareLinks, InitializingBean return CollectionWithPagingInfo.asPaged(paging, qsLinks, results.hasMore(), new Long(results.getNumberFound()).intValue()); } - private QuickShareLink getQuickShareInfo(String sharedId, boolean noAuth) + private QuickShareLink getQuickShareInfo(String sharedId, boolean noAuth, List selectParam) { checkValidShareId(sharedId); Map map = (Map) quickShareService.getMetaData(sharedId).get("item"); NodeRef nodeRef = new NodeRef((String) map.get("nodeRef")); - return getQuickShareInfo(nodeRef, map, noAuth); + return getQuickShareInfo(nodeRef, map, noAuth, selectParam); } - private QuickShareLink getQuickShareInfo(NodeRef nodeRef, boolean noAuth) + private QuickShareLink getQuickShareInfo(NodeRef nodeRef, boolean noAuth, List selectParam) { Map map = (Map) quickShareService.getMetaData(nodeRef).get("item"); - return getQuickShareInfo(nodeRef, map , noAuth); + return getQuickShareInfo(nodeRef, map , noAuth, selectParam); } - private QuickShareLink getQuickShareInfo(NodeRef nodeRef, Map map, boolean noAuth) + private QuickShareLink getQuickShareInfo(NodeRef nodeRef, Map map, boolean noAuth, List selectParam) { String sharedId = (String)map.get("sharedId"); @@ -489,17 +494,24 @@ public class QuickShareLinksImpl implements QuickShareLinks, InitializingBean UserInfo modifiedByUser = Node.lookupUserInfo((String)nodeProps.get(ContentModel.PROP_MODIFIER), mapUserInfo, personService, displayNameOnly); // TODO review - should we return sharedByUser for authenticated users only ?? (not exposed by V0 but needed for "find") - UserInfo sharedByUser = Node.lookupUserInfo((String)nodeProps.get(QuickShareModel.PROP_QSHARE_SHAREDBY), mapUserInfo, personService, displayNameOnly); + String sharedByUserId = (String)nodeProps.get(QuickShareModel.PROP_QSHARE_SHAREDBY); + UserInfo sharedByUser = Node.lookupUserInfo(sharedByUserId, mapUserInfo, personService, displayNameOnly); - // TODO other "properties" (if needed) - eg. cm:title, cm:lastThumbnailModificationData, ... thumbnail info ... - - QuickShareLink qs = new QuickShareLink(sharedId, nodeRef.getId()); + QuickShareLink qs = new QuickShareLink(sharedId, (noAuth ? null : nodeRef.getId())); qs.setName((String) map.get("name")); qs.setContent(contentInfo); qs.setModifiedAt((Date) map.get("modified")); qs.setModifiedByUser(modifiedByUser); qs.setSharedByUser(sharedByUser); + if ((! noAuth) && selectParam.contains(PARAM_SELECT_ISLINK)) + { + if (canDeleteSharedLink(nodeRef, sharedByUserId)) + { + qs.setAllowableOperations(Collections.singletonList("delete")); + } + } + return qs; } catch (InvalidSharedIdException ex) @@ -514,6 +526,54 @@ public class QuickShareLinksImpl implements QuickShareLinks, InitializingBean } } + // TODO push down to QuickShareService and also update v0 webscript (UnshareContentDelete) + private boolean canDeleteSharedLink(NodeRef nodeRef, String sharedByUserId) + { + boolean canDeleteSharedLink = false; + + String currentUser = AuthenticationUtil.getFullyAuthenticatedUser(); + String siteName = getSiteName(nodeRef); + + if (siteName != null) + { + // node belongs to a site - current user must be a manager or collaborator (irrespective of whether they shared the link or not) + String role = siteService.getMembersRole(siteName, currentUser); + if (role.equals(SiteModel.SITE_MANAGER) || role.equals(SiteModel.SITE_COLLABORATOR)) + { + canDeleteSharedLink = true; + } + } + else if ((currentUser.equals(sharedByUserId)) || (authorityService.isAdminAuthority(currentUser))) + { + // node does not belongs to a site - current user must be the person who shared the link or an admin + canDeleteSharedLink = true; + } + + return canDeleteSharedLink; + } + + private String getSiteName(NodeRef nodeRef) + { + NodeRef parent = nodeService.getPrimaryParent(nodeRef).getParentRef(); + while (parent != null && !nodeService.getType(parent).equals(SiteModel.TYPE_SITE)) + { + // check that we can read parent name + String parentName = (String) nodeService.getProperty(parent, ContentModel.PROP_NAME); + + if (nodeService.getPrimaryParent(nodeRef) != null) + { + parent = nodeService.getPrimaryParent(parent).getParentRef(); + } + } + + if (parent == null) + { + return null; + } + + return nodeService.getProperty(parent, ContentModel.PROP_NAME).toString(); + } + private void checkEnabled() { if (!enabled) diff --git a/source/java/org/alfresco/rest/api/model/QuickShareLink.java b/source/java/org/alfresco/rest/api/model/QuickShareLink.java index 5e823a3cdb..0555ffc6be 100644 --- a/source/java/org/alfresco/rest/api/model/QuickShareLink.java +++ b/source/java/org/alfresco/rest/api/model/QuickShareLink.java @@ -19,6 +19,7 @@ package org.alfresco.rest.api.model; import java.util.Date; +import java.util.List; /** * Representation of quick share link @@ -45,10 +46,13 @@ public class QuickShareLink private String name; private ContentInfo content; - protected Date modifiedAt; - protected UserInfo modifiedByUser; + private Date modifiedAt; + private UserInfo modifiedByUser; + + private UserInfo sharedByUser; + + private List allowableOperations; - protected UserInfo sharedByUser; public QuickShareLink() { @@ -126,6 +130,16 @@ public class QuickShareLink this.sharedByUser = sharedByUser; } + public List getAllowableOperations() + { + return allowableOperations; + } + + public void setAllowableOperations(List allowableOperations) + { + this.allowableOperations = allowableOperations; + } + // eg. for debug logging etc @Override public String toString() @@ -138,6 +152,7 @@ public class QuickShareLink sb.append(", modifiedByUser=").append(getModifiedByUser()); sb.append(", sharedByUser=").append(getSharedByUser()); sb.append(", content=").append(getContent()); + sb.append(", allowableOperations=").append(getAllowableOperations()); sb.append("]"); return sb.toString(); } diff --git a/source/test-java/org/alfresco/rest/api/tests/SharedLinkApiTest.java b/source/test-java/org/alfresco/rest/api/tests/SharedLinkApiTest.java index 138d6d18b7..c229f1ae49 100644 --- a/source/test-java/org/alfresco/rest/api/tests/SharedLinkApiTest.java +++ b/source/test-java/org/alfresco/rest/api/tests/SharedLinkApiTest.java @@ -21,7 +21,6 @@ package org.alfresco.rest.api.tests; import org.alfresco.repo.content.MimetypeMap; import org.alfresco.repo.security.authentication.AuthenticationUtil; import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; -import org.alfresco.rest.api.Nodes; import org.alfresco.rest.api.People; import org.alfresco.rest.api.QuickShareLinks; import org.alfresco.rest.api.impl.QuickShareLinksImpl; @@ -41,6 +40,7 @@ import org.junit.Before; import org.junit.Test; import java.util.ArrayList; +import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.List; @@ -182,29 +182,54 @@ public class SharedLinkApiTest extends AbstractBaseApiTest post(URL_SHARED_LINKS, user2, toJsonAsStringNonNull(body), 409); - // auth access to get shared link info - response = getSingle(QuickShareLinkEntityResource.class, user1, sharedId, null, 200); + // auth access to get shared link info - as user1 + Map params = Collections.singletonMap("select", "allowableOperations"); + response = getSingle(QuickShareLinkEntityResource.class, user1, sharedId, params, 200); resp = RestApiUtil.parseRestApiEntry(response.getJsonResponse(), QuickShareLink.class); assertEquals(sharedId, resp.getId()); - assertEquals(d1Id, resp.getNodeId()); assertEquals(docName1, resp.getName()); + assertEquals(d1Id, resp.getNodeId()); assertEquals(user1, resp.getModifiedByUser().getId()); // returned if authenticated assertEquals(user2, resp.getSharedByUser().getId()); // returned if authenticated + assertNull(resp.getAllowableOperations()); - // unauth access to get shared link info - response = getSingle(QuickShareLinkEntityResource.class, null, sharedId, null, 200); + // auth access to get shared link info - as user2 + params = Collections.singletonMap("select", "allowableOperations"); + response = getSingle(QuickShareLinkEntityResource.class, user2, sharedId, params, 200); resp = RestApiUtil.parseRestApiEntry(response.getJsonResponse(), QuickShareLink.class); assertEquals(sharedId, resp.getId()); - assertEquals(d1Id, resp.getNodeId()); assertEquals(docName1, resp.getName()); + assertEquals(d1Id, resp.getNodeId()); - assertNull(resp.getModifiedByUser().getId()); + assertEquals(user1, resp.getModifiedByUser().getId()); // returned if authenticated + assertEquals(user2, resp.getSharedByUser().getId()); // returned if authenticated + + assertEquals(1, resp.getAllowableOperations().size()); + assertEquals("delete", resp.getAllowableOperations().get(0)); + + // allowable operations not selected + response = getSingle(QuickShareLinkEntityResource.class, user2, sharedId, null, 200); + resp = RestApiUtil.parseRestApiEntry(response.getJsonResponse(), QuickShareLink.class); + assertNull(resp.getAllowableOperations()); + + + // unauth access to get shared link info + params = Collections.singletonMap("select", "allowableOperations"); // note: this will be ignore for unauth access + response = getSingle(QuickShareLinkEntityResource.class, null, sharedId, params, 200); + resp = RestApiUtil.parseRestApiEntry(response.getJsonResponse(), QuickShareLink.class); + + assertEquals(sharedId, resp.getId()); + assertEquals(docName1, resp.getName()); + assertNull(resp.getNodeId()); // nodeId not returned + assertNull(resp.getAllowableOperations()); // select is ignored + + assertNull(resp.getModifiedByUser().getId()); // userId not returned assertEquals(user1+" "+user1, resp.getModifiedByUser().getDisplayName()); - assertNull(resp.getSharedByUser().getId()); + assertNull(resp.getSharedByUser().getId()); // userId not returned assertEquals(user2+" "+user2, resp.getSharedByUser().getDisplayName());