diff --git a/config/alfresco/quickshare-services-context.xml b/config/alfresco/quickshare-services-context.xml
index 490f484dd9..e53367597d 100644
--- a/config/alfresco/quickshare-services-context.xml
+++ b/config/alfresco/quickshare-services-context.xml
@@ -81,6 +81,9 @@
+
+
+
diff --git a/source/java/org/alfresco/repo/quickshare/QuickShareServiceImpl.java b/source/java/org/alfresco/repo/quickshare/QuickShareServiceImpl.java
index b88dbb6b4a..d447a184ae 100644
--- a/source/java/org/alfresco/repo/quickshare/QuickShareServiceImpl.java
+++ b/source/java/org/alfresco/repo/quickshare/QuickShareServiceImpl.java
@@ -50,6 +50,7 @@ import org.alfresco.repo.quickshare.ClientAppConfig.ClientApp;
import org.alfresco.repo.security.authentication.AuthenticationUtil;
import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork;
import org.alfresco.repo.security.permissions.AccessDeniedException;
+import org.alfresco.repo.site.SiteModel;
import org.alfresco.repo.tenant.TenantService;
import org.alfresco.repo.tenant.TenantUtil;
import org.alfresco.repo.tenant.TenantUtil.TenantRunAsWork;
@@ -63,14 +64,21 @@ import org.alfresco.service.cmr.quickshare.InvalidSharedIdException;
import org.alfresco.service.cmr.quickshare.QuickShareDTO;
import org.alfresco.service.cmr.quickshare.QuickShareDisabledException;
import org.alfresco.service.cmr.quickshare.QuickShareService;
+import org.alfresco.service.cmr.repository.ChildAssociationRef;
import org.alfresco.service.cmr.repository.ContentData;
import org.alfresco.service.cmr.repository.InvalidNodeRefException;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.NodeService;
+import org.alfresco.service.cmr.repository.StoreRef;
+import org.alfresco.service.cmr.search.ResultSet;
+import org.alfresco.service.cmr.search.SearchParameters;
+import org.alfresco.service.cmr.search.SearchService;
import org.alfresco.service.cmr.security.AccessStatus;
+import org.alfresco.service.cmr.security.AuthorityService;
import org.alfresco.service.cmr.security.NoSuchPersonException;
import org.alfresco.service.cmr.security.PermissionService;
import org.alfresco.service.cmr.security.PersonService;
+import org.alfresco.service.cmr.site.SiteService;
import org.alfresco.service.cmr.thumbnail.ThumbnailService;
import org.alfresco.service.namespace.NamespaceService;
import org.alfresco.service.namespace.QName;
@@ -94,7 +102,10 @@ import org.springframework.extensions.surf.util.I18NUtil;
*
* @author Alex Miller, janv, Jamal Kaabi-Mofrad
*/
-public class QuickShareServiceImpl implements QuickShareService, NodeServicePolicies.BeforeDeleteNodePolicy, CopyServicePolicies.OnCopyNodePolicy
+public class QuickShareServiceImpl implements QuickShareService,
+ NodeServicePolicies.BeforeDeleteNodePolicy,
+ CopyServicePolicies.OnCopyNodePolicy,
+ NodeServicePolicies.OnRestoreNodePolicy
{
private static final Log logger = LogFactory.getLog(QuickShareServiceImpl.class);
@@ -122,6 +133,9 @@ public class QuickShareServiceImpl implements QuickShareService, NodeServicePoli
private PreferenceService preferenceService;
/** Component to determine which behaviours are active and which not */
private BehaviourFilter behaviourFilter;
+ private SearchService searchService;
+ private SiteService siteService;
+ private AuthorityService authorityService;
private boolean enabled;
private String defaultEmailSender;
@@ -225,6 +239,36 @@ public class QuickShareServiceImpl implements QuickShareService, NodeServicePoli
this.behaviourFilter = behaviourFilter;
}
+ /**
+ * Spring configuration
+ *
+ * @param searchService the searchService to set
+ */
+ public void setSearchService(SearchService searchService)
+ {
+ this.searchService = searchService;
+ }
+
+ /**
+ * Spring configuration
+ *
+ * @param siteService the siteService to set
+ */
+ public void setSiteService(SiteService siteService)
+ {
+ this.siteService = siteService;
+ }
+
+ /**
+ * Spring configuration
+ *
+ * @param authorityService the authorityService to set
+ */
+ public void setAuthorityService(AuthorityService authorityService)
+ {
+ this.authorityService = authorityService;
+ }
+
/**
* Enable or disable this service.
*/
@@ -265,6 +309,9 @@ public class QuickShareServiceImpl implements QuickShareService, NodeServicePoli
PropertyCheck.mandatory(this, "behaviourFilter", behaviourFilter);
PropertyCheck.mandatory(this, "defaultEmailSender", defaultEmailSender);
PropertyCheck.mandatory(this, "clientAppConfig", clientAppConfig);
+ PropertyCheck.mandatory(this, "searchService", searchService);
+ PropertyCheck.mandatory(this, "siteService", siteService);
+ PropertyCheck.mandatory(this, "authorityService", authorityService);
}
/**
@@ -285,6 +332,11 @@ public class QuickShareServiceImpl implements QuickShareService, NodeServicePoli
CopyServicePolicies.OnCopyNodePolicy.QNAME,
QuickShareModel.ASPECT_QSHARE,
new JavaBehaviour(this, "getCopyCallback"));
+
+ this.policyComponent.bindClassBehaviour(
+ NodeServicePolicies.OnRestoreNodePolicy.QNAME,
+ QuickShareModel.ASPECT_QSHARE,
+ new JavaBehaviour(this, "onRestoreNode"));
}
@@ -487,23 +539,56 @@ public class QuickShareServiceImpl implements QuickShareService, NodeServicePoli
@Override
public Pair getTenantNodeRefFromSharedId(final String sharedId)
{
- final NodeRef nodeRef = TenantUtil.runAsDefaultTenant(new TenantRunAsWork()
+ NodeRef nodeRef = TenantUtil.runAsDefaultTenant(new TenantRunAsWork()
{
public NodeRef doWork() throws Exception
{
- return (NodeRef)attributeService.getAttribute(ATTR_KEY_SHAREDIDS_ROOT, sharedId);
+ return (NodeRef) attributeService.getAttribute(ATTR_KEY_SHAREDIDS_ROOT, sharedId);
}
});
-
+
if (nodeRef == null)
{
- throw new InvalidSharedIdException(sharedId);
+ /* TODO
+ * Temporary fix for RA-1093 and MNT-16224. The extra lookup should be
+ * removed (the same as before, just throw the 'InvalidSharedIdException' exception) when we
+ * have a system wide patch to remove the 'shared' aspect of the nodes that have been archived while shared.
+ */
+ // TMDQ
+ final String query = "+TYPE:\"cm:content\" AND +ASPECT:\"qshare:shared\" AND =qshare:sharedId:\"" + sharedId + "\"";
+ SearchParameters sp = new SearchParameters();
+ sp.setLanguage(SearchService.LANGUAGE_FTS_ALFRESCO);
+ sp.setQuery(query);
+ sp.addStore(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE);
+
+ List nodeRefs = null;
+ ResultSet results = null;
+ try
+ {
+ results = searchService.query(sp);
+ nodeRefs = results.getNodeRefs();
+ }
+ catch (Exception ex)
+ {
+ throw new InvalidSharedIdException(sharedId);
+ }
+ finally
+ {
+ if (results != null)
+ {
+ results.close();
+ }
+ }
+ if (nodeRefs.size() != 1)
+ {
+ throw new InvalidSharedIdException(sharedId);
+ }
+ nodeRef = tenantService.getName(nodeRefs.get(0));
}
-
+
// note: relies on tenant-specific (ie. mangled) nodeRef
String tenantDomain = tenantService.getDomain(nodeRef.getStoreRef().getIdentifier());
-
- return new Pair(tenantDomain, tenantService.getBaseName(nodeRef));
+ return new Pair<>(tenantDomain, tenantService.getBaseName(nodeRef));
}
@Override
@@ -564,6 +649,16 @@ public class QuickShareServiceImpl implements QuickShareService, NodeServicePoli
// note: deleted nodeRef might not match, eg. for upload new version -> checkin -> delete working copy
if (nodeRef.equals(beforeDeleteNodeRef))
{
+ // Disable audit to preserve modifier and modified date
+ behaviourFilter.disableBehaviour(nodeRef, ContentModel.ASPECT_AUDITABLE);
+ try
+ {
+ nodeService.removeAspect(nodeRef, QuickShareModel.ASPECT_QSHARE);
+ }
+ finally
+ {
+ behaviourFilter.enableBehaviour(nodeRef, ContentModel.ASPECT_AUDITABLE);
+ }
removeSharedId(sharedId);
}
}
@@ -576,7 +671,37 @@ public class QuickShareServiceImpl implements QuickShareService, NodeServicePoli
}
});
}
-
+
+ /* TODO
+ * Temporary fix for MNT-16224. This method should be removed when we
+ * have a system wide patch to remove the 'shared' aspect of the nodes that have been archived while shared.
+ */
+ @Override
+ public void onRestoreNode(ChildAssociationRef childAssocRef)
+ {
+ final NodeRef childNodeRef = childAssocRef.getChildRef();
+ AuthenticationUtil.runAsSystem(new RunAsWork()
+ {
+ public Void doWork() throws Exception
+ {
+ if (nodeService.hasAspect(childNodeRef, QuickShareModel.ASPECT_QSHARE))
+ {
+ // Disable audit to preserve modifier and modified date
+ behaviourFilter.disableBehaviour(childNodeRef, ContentModel.ASPECT_AUDITABLE);
+ try
+ {
+ nodeService.removeAspect(childNodeRef, QuickShareModel.ASPECT_QSHARE);
+ }
+ finally
+ {
+ behaviourFilter.enableBehaviour(childNodeRef, ContentModel.ASPECT_AUDITABLE);
+ }
+ }
+ return null;
+ }
+ });
+ }
+
private void removeSharedId(final String sharedId)
{
TenantUtil.runAsDefaultTenant(new TenantRunAsWork()
@@ -727,6 +852,55 @@ public class QuickShareServiceImpl implements QuickShareService, NodeServicePoli
}
}
+ @Override
+ public boolean canDeleteSharedLink(NodeRef nodeRef, String sharedByUserId)
+ {
+ boolean canDeleteSharedLink = false;
+
+ String currentUser = AuthenticationUtil.getFullyAuthenticatedUser();
+ String siteName = getSiteName(nodeRef);
+ boolean isSharedByCurrentUser = currentUser.equals(sharedByUserId);
+
+ if (siteName != null)
+ {
+ // node belongs to a site - current user must be a manager or collaborator or someone who shared the link
+ String role = siteService.getMembersRole(siteName, currentUser);
+ if (isSharedByCurrentUser || (role != null && (role.equals(SiteModel.SITE_MANAGER) || role.equals(SiteModel.SITE_COLLABORATOR))))
+ {
+ canDeleteSharedLink = true;
+ }
+ }
+ else if (isSharedByCurrentUser || (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 String getUrl(String url)
{
if (url.endsWith("/"))
@@ -914,3 +1088,4 @@ public class QuickShareServiceImpl implements QuickShareService, NodeServicePoli
}
}
}
+
diff --git a/source/java/org/alfresco/service/cmr/quickshare/QuickShareService.java b/source/java/org/alfresco/service/cmr/quickshare/QuickShareService.java
index e93d703456..d183879621 100644
--- a/source/java/org/alfresco/service/cmr/quickshare/QuickShareService.java
+++ b/source/java/org/alfresco/service/cmr/quickshare/QuickShareService.java
@@ -85,4 +85,9 @@ public interface QuickShareService
* @param emailRequest The email details including its template details
*/
public void sendEmailNotification(QuickShareEmailRequest emailRequest);
+
+ /**
+ * Determine if the current user has permission to delete the shared link.
+ */
+ public boolean canDeleteSharedLink(NodeRef nodeRef, String sharedByUserId);
}
diff --git a/source/test-java/org/alfresco/repo/quickshare/QuickShareServiceIntegrationTest.java b/source/test-java/org/alfresco/repo/quickshare/QuickShareServiceIntegrationTest.java
index 4dd57d10a5..c8dcb26009 100644
--- a/source/test-java/org/alfresco/repo/quickshare/QuickShareServiceIntegrationTest.java
+++ b/source/test-java/org/alfresco/repo/quickshare/QuickShareServiceIntegrationTest.java
@@ -23,6 +23,7 @@ import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
import java.io.ByteArrayInputStream;
import java.io.Serializable;
@@ -31,9 +32,14 @@ import java.util.Map;
import org.alfresco.model.ContentModel;
import org.alfresco.model.QuickShareModel;
import org.alfresco.repo.model.Repository;
+import org.alfresco.repo.node.archive.NodeArchiveService;
+import org.alfresco.repo.node.archive.RestoreNodeReport;
+import org.alfresco.repo.node.archive.RestoreNodeReport.RestoreStatus;
import org.alfresco.repo.security.authentication.AuthenticationUtil;
import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork;
import org.alfresco.repo.security.permissions.AccessDeniedException;
+import org.alfresco.repo.tenant.TenantUtil;
+import org.alfresco.repo.tenant.TenantUtil.TenantRunAsWork;
import org.alfresco.service.cmr.attributes.AttributeService;
import org.alfresco.service.cmr.dictionary.DictionaryService;
import org.alfresco.service.cmr.quickshare.InvalidSharedIdException;
@@ -109,6 +115,7 @@ public class QuickShareServiceIntegrationTest
private static Repository repository;
private static AttributeService attributeService;
private static PermissionService permissionService;
+ private static NodeArchiveService nodeArchiveService;
private static AlfrescoPerson user1 = new AlfrescoPerson(testContext, "UserOne");
private static AlfrescoPerson user2 = new AlfrescoPerson(testContext, "UserTwo");
@@ -143,6 +150,7 @@ public class QuickShareServiceIntegrationTest
repository = ctx.getBean("repositoryHelper", Repository.class);
attributeService = ctx.getBean("AttributeService", AttributeService.class);
permissionService = ctx.getBean("PermissionService", PermissionService.class);
+ nodeArchiveService = ctx.getBean("nodeArchiveService", NodeArchiveService.class);
}
@Before public void createTestData()
@@ -220,8 +228,117 @@ public class QuickShareServiceIntegrationTest
});
}
+ // MNT-16224, RA-1093
+ @Test public void testDeleteAndRestoreSharedNode()
+ {
+ // Share the test node
+ share(testNode, user1.getUsername());
+ AuthenticationUtil.runAsSystem(new RunAsWork()
+ {
+ @Override
+ public Void doWork() throws Exception
+ {
+ assertTrue(nodeService.hasAspect(testNode, QuickShareModel.ASPECT_QSHARE));
+ return null;
+ }
+ });
- private void unshare(final String sharedId, final String userName) {
+ // Delete and restore the shared node.
+ testNode = AuthenticationUtil.runAs(new RunAsWork()
+ {
+ @Override
+ public NodeRef doWork() throws Exception
+ {
+ // Delete the shared node
+ nodeService.deleteNode(testNode);
+
+ // Check if the node has been archived
+ final NodeRef archivedNode = nodeArchiveService.getArchivedNode(testNode);
+ assertNotNull(archivedNode);
+
+ // Restore the deleted shared node from trashcan
+ RestoreNodeReport restoreNodeReport = nodeArchiveService.restoreArchivedNode(archivedNode);
+ assertNotNull(restoreNodeReport);
+ assertTrue(restoreNodeReport.getStatus() == RestoreStatus.SUCCESS);
+ NodeRef restoredNodeRef = restoreNodeReport.getRestoredNodeRef();
+ assertNotNull(restoredNodeRef);
+ return restoredNodeRef;
+ }
+
+ }, user1.getUsername());
+
+ // Check the restored node doesn't have the 'shared' aspect.
+ AuthenticationUtil.runAsSystem(new RunAsWork()
+ {
+ @Override
+ public Void doWork() throws Exception
+ {
+ assertFalse(nodeService.hasAspect(testNode, QuickShareModel.ASPECT_QSHARE));
+ assertNull(nodeService.getProperty(testNode, QuickShareModel.PROP_QSHARE_SHAREDID));
+ assertNull(nodeService.getProperty(testNode, QuickShareModel.PROP_QSHARE_SHAREDBY));
+ return null;
+ }
+ });
+
+
+ /**
+ * Tests the scenario where the shared node has been deleted and restored before the fix (MNT-16224).
+ * In this scenario the user should be able to un-share the restored node.
+ */
+ {
+ // Share the test node again
+ final QuickShareDTO dto = share(testNode, user1.getUsername());
+
+ // Delete only the sharedId without removing the 'shared' aspect!(The cause of MNT-16224 and RA-1093)
+ TenantUtil.runAsDefaultTenant(new TenantRunAsWork()
+ {
+ public Void doWork() throws Exception
+ {
+ attributeService.removeAttribute(".sharedIds", dto.getId());
+ return null;
+ }
+ });
+
+ // Check the 'shared' aspect does exist
+ AuthenticationUtil.runAsSystem(new RunAsWork()
+ {
+ @Override
+ public Void doWork() throws Exception
+ {
+ assertTrue(nodeService.hasAspect(testNode, QuickShareModel.ASPECT_QSHARE));
+ return null;
+ }
+ });
+
+ try
+ {
+ // Try to un-share the node even though the sharedId was deleted.
+ unshare(dto.getId(), user2.getUsername());
+ fail("user2 shouldn't be able to un-share the node.");
+ }
+ catch (InvalidSharedIdException ex)
+ {
+ // Expected
+ }
+
+ // Un-share the node even though the sharedId was deleted.
+ // This should succeed as the lookup will use TMDQ.
+ unshare(dto.getId(), user1.getUsername());
+
+ // Check the 'shared' aspect does not exist
+ AuthenticationUtil.runAsSystem(new RunAsWork()
+ {
+ @Override
+ public Void doWork() throws Exception
+ {
+ assertFalse(nodeService.hasAspect(testNode, QuickShareModel.ASPECT_QSHARE));
+ return null;
+ }
+ });
+ }
+ }
+
+ private void unshare(final String sharedId, final String userName) {
AuthenticationUtil.runAs(new RunAsWork()
{
@@ -233,7 +350,7 @@ public class QuickShareServiceIntegrationTest
return null;
}
}, userName);
- }
+ }
private QuickShareDTO share(final NodeRef nodeRef, String username)
{