MNT-16224, RA-1093: Fixed Quickshare issue where deleting a shared node didn't remove the 'shared' aspect. The fix also takes care of the shared nodes that have been deleted but not yet restored, as well as, shared nodes that have already been restored from the trashcan (allow the user to un-share).

git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/BRANCHES/DEV/5.1.N/root@127454 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
This commit is contained in:
Jamal Kaabi-Mofrad
2016-05-31 16:57:14 +00:00
parent 29c85366ed
commit 1cabca593c
4 changed files with 314 additions and 20 deletions

View File

@@ -64,6 +64,9 @@
<property name="thumbnailService" ref="ThumbnailService"/> <property name="thumbnailService" ref="ThumbnailService"/>
<property name="eventPublisher" ref="eventPublisher" /> <property name="eventPublisher" ref="eventPublisher" />
<property name="behaviourFilter" ref="policyBehaviourFilter" /> <property name="behaviourFilter" ref="policyBehaviourFilter" />
<property name="searchService" ref="SearchService" />
<property name="siteService" ref="SiteService" />
<property name="authorityService" ref="AuthorityService" />
</bean> </bean>
</beans> </beans>

View File

@@ -50,6 +50,7 @@ import org.alfresco.repo.policy.PolicyComponent;
import org.alfresco.repo.security.authentication.AuthenticationUtil; import org.alfresco.repo.security.authentication.AuthenticationUtil;
import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork;
import org.alfresco.repo.security.permissions.AccessDeniedException; import org.alfresco.repo.security.permissions.AccessDeniedException;
import org.alfresco.repo.site.SiteModel;
import org.alfresco.repo.tenant.TenantService; import org.alfresco.repo.tenant.TenantService;
import org.alfresco.repo.tenant.TenantUtil; import org.alfresco.repo.tenant.TenantUtil;
import org.alfresco.repo.tenant.TenantUtil.TenantRunAsWork; import org.alfresco.repo.tenant.TenantUtil.TenantRunAsWork;
@@ -60,14 +61,21 @@ import org.alfresco.service.cmr.quickshare.InvalidSharedIdException;
import org.alfresco.service.cmr.quickshare.QuickShareDTO; import org.alfresco.service.cmr.quickshare.QuickShareDTO;
import org.alfresco.service.cmr.quickshare.QuickShareDisabledException; import org.alfresco.service.cmr.quickshare.QuickShareDisabledException;
import org.alfresco.service.cmr.quickshare.QuickShareService; 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.ContentData;
import org.alfresco.service.cmr.repository.InvalidNodeRefException; import org.alfresco.service.cmr.repository.InvalidNodeRefException;
import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.NodeService; 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.AccessStatus;
import org.alfresco.service.cmr.security.AuthorityService;
import org.alfresco.service.cmr.security.NoSuchPersonException; import org.alfresco.service.cmr.security.NoSuchPersonException;
import org.alfresco.service.cmr.security.PermissionService; import org.alfresco.service.cmr.security.PermissionService;
import org.alfresco.service.cmr.security.PersonService; 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.cmr.thumbnail.ThumbnailService;
import org.alfresco.service.namespace.NamespaceService; import org.alfresco.service.namespace.NamespaceService;
import org.alfresco.service.namespace.QName; import org.alfresco.service.namespace.QName;
@@ -87,7 +95,10 @@ import org.safehaus.uuid.UUIDGenerator;
* *
* @author Alex Miller, janv * @author Alex Miller, janv
*/ */
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); private static final Log logger = LogFactory.getLog(QuickShareServiceImpl.class);
@@ -108,7 +119,10 @@ public class QuickShareServiceImpl implements QuickShareService, NodeServicePoli
/** Component to determine which behaviours are active and which not */ /** Component to determine which behaviours are active and which not */
private BehaviourFilter behaviourFilter; private BehaviourFilter behaviourFilter;
private SearchService searchService;
private SiteService siteService;
private AuthorityService authorityService;
/** /**
* Spring configuration * Spring configuration
* *
@@ -118,11 +132,41 @@ public class QuickShareServiceImpl implements QuickShareService, NodeServicePoli
{ {
this.behaviourFilter = behaviourFilter; 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. * Enable or disable this service.
*/ */
public void setEnabled(boolean enabled) public void setEnabled(boolean enabled)
{ {
this.enabled = enabled; this.enabled = enabled;
} }
@@ -215,6 +259,11 @@ public class QuickShareServiceImpl implements QuickShareService, NodeServicePoli
CopyServicePolicies.OnCopyNodePolicy.QNAME, CopyServicePolicies.OnCopyNodePolicy.QNAME,
QuickShareModel.ASPECT_QSHARE, QuickShareModel.ASPECT_QSHARE,
new JavaBehaviour(this, "getCopyCallback")); new JavaBehaviour(this, "getCopyCallback"));
this.policyComponent.bindClassBehaviour(
NodeServicePolicies.OnRestoreNodePolicy.QNAME,
QuickShareModel.ASPECT_QSHARE,
new JavaBehaviour(this, "onRestoreNode"));
} }
@@ -417,23 +466,56 @@ public class QuickShareServiceImpl implements QuickShareService, NodeServicePoli
@Override @Override
public Pair<String, NodeRef> getTenantNodeRefFromSharedId(final String sharedId) public Pair<String, NodeRef> getTenantNodeRefFromSharedId(final String sharedId)
{ {
final NodeRef nodeRef = TenantUtil.runAsDefaultTenant(new TenantRunAsWork<NodeRef>() NodeRef nodeRef = TenantUtil.runAsDefaultTenant(new TenantRunAsWork<NodeRef>()
{ {
public NodeRef doWork() throws Exception 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) 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<NodeRef> 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 // note: relies on tenant-specific (ie. mangled) nodeRef
String tenantDomain = tenantService.getDomain(nodeRef.getStoreRef().getIdentifier()); String tenantDomain = tenantService.getDomain(nodeRef.getStoreRef().getIdentifier());
return new Pair<>(tenantDomain, tenantService.getBaseName(nodeRef));
return new Pair<String, NodeRef>(tenantDomain, tenantService.getBaseName(nodeRef));
} }
@Override @Override
@@ -491,12 +573,22 @@ public class QuickShareServiceImpl implements QuickShareService, NodeServicePoli
final String tenantDomain = pair.getFirst(); final String tenantDomain = pair.getFirst();
final NodeRef nodeRef = pair.getSecond(); final NodeRef nodeRef = pair.getSecond();
// note: deleted nodeRef might not match, eg. for upload new version -> checkin -> delete working copy // note: deleted nodeRef might not match, eg. for upload new version -> checkin -> delete working copy
if (nodeRef.equals(beforeDeleteNodeRef)) if (nodeRef.equals(beforeDeleteNodeRef))
{ {
removeSharedId(sharedId); // 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);
}
}
catch (InvalidSharedIdException ex) catch (InvalidSharedIdException ex)
{ {
logger.warn("Couldn't find shareId, " + sharedId + ", attributes for node " + beforeDeleteNodeRef); logger.warn("Couldn't find shareId, " + sharedId + ", attributes for node " + beforeDeleteNodeRef);
@@ -506,7 +598,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<Void>()
{
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) private void removeSharedId(final String sharedId)
{ {
TenantUtil.runAsDefaultTenant(new TenantRunAsWork<Void>() TenantUtil.runAsDefaultTenant(new TenantRunAsWork<Void>()
@@ -602,5 +724,52 @@ public class QuickShareServiceImpl implements QuickShareService, NodeServicePoli
}, tenantDomain); }, tenantDomain);
} }
@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();
}
} }

View File

@@ -84,4 +84,9 @@ public interface QuickShareService
* Determine if the current user has permission to read the shared content. * Determine if the current user has permission to read the shared content.
*/ */
public boolean canRead(String sharedId); public boolean canRead(String sharedId);
/**
* Determine if the current user has permission to delete the shared link.
*/
public boolean canDeleteSharedLink(NodeRef nodeRef, String sharedByUserId);
} }

View File

@@ -30,6 +30,7 @@ import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull; import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.Serializable; import java.io.Serializable;
@@ -38,9 +39,14 @@ import java.util.Map;
import org.alfresco.model.ContentModel; import org.alfresco.model.ContentModel;
import org.alfresco.model.QuickShareModel; import org.alfresco.model.QuickShareModel;
import org.alfresco.repo.model.Repository; 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;
import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork;
import org.alfresco.repo.security.permissions.AccessDeniedException; 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.attributes.AttributeService;
import org.alfresco.service.cmr.dictionary.DictionaryService; import org.alfresco.service.cmr.dictionary.DictionaryService;
import org.alfresco.service.cmr.quickshare.InvalidSharedIdException; import org.alfresco.service.cmr.quickshare.InvalidSharedIdException;
@@ -116,6 +122,7 @@ public class QuickShareServiceIntegrationTest
private static Repository repository; private static Repository repository;
private static AttributeService attributeService; private static AttributeService attributeService;
private static PermissionService permissionService; private static PermissionService permissionService;
private static NodeArchiveService nodeArchiveService;
private static AlfrescoPerson user1 = new AlfrescoPerson(testContext, "UserOne"); private static AlfrescoPerson user1 = new AlfrescoPerson(testContext, "UserOne");
private static AlfrescoPerson user2 = new AlfrescoPerson(testContext, "UserTwo"); private static AlfrescoPerson user2 = new AlfrescoPerson(testContext, "UserTwo");
@@ -150,6 +157,7 @@ public class QuickShareServiceIntegrationTest
repository = ctx.getBean("repositoryHelper", Repository.class); repository = ctx.getBean("repositoryHelper", Repository.class);
attributeService = ctx.getBean("AttributeService", AttributeService.class); attributeService = ctx.getBean("AttributeService", AttributeService.class);
permissionService = ctx.getBean("PermissionService", PermissionService.class); permissionService = ctx.getBean("PermissionService", PermissionService.class);
nodeArchiveService = ctx.getBean("nodeArchiveService", NodeArchiveService.class);
} }
@Before public void createTestData() @Before public void createTestData()
@@ -227,8 +235,117 @@ public class QuickShareServiceIntegrationTest
}); });
} }
// MNT-16224, RA-1093
@Test public void testDeleteAndRestoreSharedNode()
{
// Share the test node
share(testNode, user1.getUsername());
AuthenticationUtil.runAsSystem(new RunAsWork<Void>()
{
@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<NodeRef>()
{
@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<Void>()
{
@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<Void>()
{
public Void doWork() throws Exception
{
attributeService.removeAttribute(".sharedIds", dto.getId());
return null;
}
});
// Check the 'shared' aspect does exist
AuthenticationUtil.runAsSystem(new RunAsWork<Void>()
{
@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<Void>()
{
@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<Void>() AuthenticationUtil.runAs(new RunAsWork<Void>()
{ {
@@ -240,7 +357,7 @@ public class QuickShareServiceIntegrationTest
return null; return null;
} }
}, userName); }, userName);
} }
private QuickShareDTO share(final NodeRef nodeRef, String username) private QuickShareDTO share(final NodeRef nodeRef, String username)
{ {