diff --git a/config/alfresco/templates/webscripts/org/alfresco/repository/invite/invite.get.desc.xml b/config/alfresco/templates/webscripts/org/alfresco/repository/invite/invite.get.desc.xml index 98f539a388..8cdd738cd6 100644 --- a/config/alfresco/templates/webscripts/org/alfresco/repository/invite/invite.get.desc.xml +++ b/config/alfresco/templates/webscripts/org/alfresco/repository/invite/invite.get.desc.xml @@ -1,9 +1,9 @@ Invite Processes Inviter actions ('start' or 'cancel' invite) - /api/invite/start?inviteeFirstName={inviteeFirstName}&inviteeLastName={inviteeLastName}&inviteeEmail={inviteeEmailAddress}&siteShortName={siteShortName}&inviteeSiteRole={inviteeSiteRole} + /api/invite/start?inviteeFirstName={inviteeFirstName}&inviteeLastName={inviteeLastName}&inviteeEmail={inviteeEmailAddress}&siteShortName={siteShortName}&inviteeSiteRole={inviteeSiteRole}&serverPath={serverPath}&acceptUrl={acceptUrl}&rejectUrl={rejectUrl} /api/invite/cancel?inviteId={inviteId} user required - \ No newline at end of file + diff --git a/config/alfresco/web-scripts-application-context.xml b/config/alfresco/web-scripts-application-context.xml index 6ff4aa7920..9fe85d1a85 100644 --- a/config/alfresco/web-scripts-application-context.xml +++ b/config/alfresco/web-scripts-application-context.xml @@ -332,14 +332,14 @@ class="org.alfresco.repo.web.scripts.invite.Invite" parent="webscript"> - - + + + - diff --git a/config/alfresco/workflow/invite_processdefinition.xml b/config/alfresco/workflow/invite_processdefinition.xml index e84ef28b25..9a83307ee6 100644 --- a/config/alfresco/workflow/invite_processdefinition.xml +++ b/config/alfresco/workflow/invite_processdefinition.xml @@ -62,6 +62,9 @@ + + + diff --git a/source/java/org/alfresco/repo/web/scripts/invite/CancelInviteAction.java b/source/java/org/alfresco/repo/web/scripts/invite/CancelInviteAction.java new file mode 100644 index 0000000000..3e36fb6cf3 --- /dev/null +++ b/source/java/org/alfresco/repo/web/scripts/invite/CancelInviteAction.java @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2005-2007 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program 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 General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have received a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ +package org.alfresco.repo.web.scripts.invite; + +import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.repo.security.authentication.MutableAuthenticationDao; +import org.alfresco.repo.site.SiteModel; +import org.alfresco.repo.site.SiteService; +import org.alfresco.repo.workflow.jbpm.JBPMSpringActionHandler; +import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.cmr.security.PersonService; +import org.alfresco.service.cmr.workflow.WorkflowService; +import org.alfresco.web.scripts.Status; +import org.alfresco.web.scripts.WebScriptException; +import org.jbpm.graph.exe.ExecutionContext; +import org.springframework.beans.factory.BeanFactory; + +/** + * This class contains logic that gets executed when + * the wf:invitePendingTask in the invite workflow gets cancelled + * along the "cancel" transition + * + * @author glen johnson at alfresco com + */ +public class CancelInviteAction extends JBPMSpringActionHandler +{ + private static final long serialVersionUID = 776961141883350908L; + + private MutableAuthenticationDao mutableAuthenticationDao; + private PersonService personService; + private WorkflowService workflowService; + private SiteService siteService; + + /* (non-Javadoc) + * @see org.alfresco.repo.workflow.jbpm.JBPMSpringActionHandler#initialiseHandler(org.springframework.beans.factory.BeanFactory) + */ + @Override + protected void initialiseHandler(BeanFactory factory) + { + ServiceRegistry services = (ServiceRegistry)factory.getBean(ServiceRegistry.SERVICE_REGISTRY); + mutableAuthenticationDao = (MutableAuthenticationDao) factory.getBean("authenticationDao"); + personService = (PersonService) services.getPersonService(); + workflowService = (WorkflowService) services.getWorkflowService(); + siteService = (SiteService) services.getSiteService(); + } + + /* (non-Javadoc) + * @see org.jbpm.graph.def.ActionHandler#execute(org.jbpm.graph.exe.ExecutionContext) + */ + @SuppressWarnings("unchecked") + public void execute(final ExecutionContext executionContext) throws Exception + { + // get the invitee user name and site short name variables off the execution context + final String inviteeUserName = (String) executionContext.getVariable( + InviteWorkflowModel.wfVarInviteeUserName); + final String siteShortName = (String) executionContext.getVariable( + InviteWorkflowModel.wfVarSiteShortName); + final String inviteId = (String) executionContext.getVariable( + InviteWorkflowModel.wfVarWorkflowInstanceId); + + // throw http status 'forbidden' Web Script Exception if current user is not a Site Manager of the site + // associated with the invite (identified by inviteID) + String currentUserName = AuthenticationUtil.getCurrentUserName(); + String currentUserSiteRole = this.siteService.getMembersRole(siteShortName, currentUserName); + if ((currentUserSiteRole == null) || (currentUserSiteRole.equals(SiteModel.SITE_MANAGER) == false)) + { + throw new WebScriptException(Status.STATUS_FORBIDDEN, + "Current user '" + currentUserName + "' cannot cancel invite having ID '" + inviteId + + "' because he\\she is not a Site Manager of the site with short name:'" + siteShortName + + "'"); + } + + // clean up invitee's user account and person node if they are not in use i.e. + // account is still disabled and there are no pending invites outstanding for the + // invitee + InviteHelper.cleanUpStaleInviteeResources(inviteeUserName, mutableAuthenticationDao, personService, + workflowService); + } +} diff --git a/source/java/org/alfresco/repo/web/scripts/invite/Invite.java b/source/java/org/alfresco/repo/web/scripts/invite/Invite.java index e273aeba1c..2810810ea9 100644 --- a/source/java/org/alfresco/repo/web/scripts/invite/Invite.java +++ b/source/java/org/alfresco/repo/web/scripts/invite/Invite.java @@ -31,6 +31,8 @@ import java.util.Map; import java.util.Set; import org.alfresco.model.ContentModel; +import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; import org.alfresco.repo.security.authentication.MutableAuthenticationDao; import org.alfresco.repo.security.authentication.PasswordGenerator; import org.alfresco.repo.security.authentication.UserNameGenerator; @@ -43,10 +45,10 @@ import org.alfresco.service.cmr.repository.datatype.DefaultTypeConverter; import org.alfresco.service.cmr.security.AuthenticationService; import org.alfresco.service.cmr.security.PersonService; import org.alfresco.service.cmr.workflow.WorkflowDefinition; +import org.alfresco.service.cmr.workflow.WorkflowException; import org.alfresco.service.cmr.workflow.WorkflowPath; import org.alfresco.service.cmr.workflow.WorkflowService; import org.alfresco.service.cmr.workflow.WorkflowTask; -import org.alfresco.service.namespace.NamespacePrefixResolver; import org.alfresco.service.namespace.NamespaceService; import org.alfresco.service.namespace.QName; import org.alfresco.util.GUID; @@ -400,13 +402,23 @@ public class Invite extends DeclarativeWebScript // create a person node for the invitee with generated invitee user name // and other provided person property values - Map properties = new HashMap(); + final Map properties = new HashMap(); properties.put(ContentModel.PROP_USERNAME, inviteeUserName); properties.put(ContentModel.PROP_FIRSTNAME, inviteeFirstName); properties.put(ContentModel.PROP_LASTNAME, inviteeLastName); properties.put(ContentModel.PROP_EMAIL, inviteeEmail); - this.personService.createPerson(properties); + AuthenticationUtil.runAs(new RunAsWork() + { + public Object doWork() throws Exception + { + personService.createPerson(properties); + + return null; + } + + }, AuthenticationUtil.getSystemUserName()); + return inviteeUserName; } @@ -676,8 +688,9 @@ public class Invite extends DeclarativeWebScript } /** - * Cancels pending invite. Note that only the Inviter should cancel the - * pending invite. + * Cancels pending invite. Note that only a Site Manager of the + * site associated with the pending invite should be able to cancel that + * invite * * @param model * model to add objects to, which will be passed to the template @@ -695,10 +708,31 @@ public class Invite extends DeclarativeWebScript "Given invite ID " + inviteId + " null or empty"); } - // cancel the workflow with the given invite ID - // which is actually the workflow ID - this.workflowService.cancelWorkflow(inviteId); - + try + { + // complete the wf:invitePendingTask along the 'cancel' transition because the invitation has been cancelled + InviteHelper.completeInviteTask(inviteId, InviteWorkflowModel.WF_INVITE_TASK_INVITE_PENDING, + InviteWorkflowModel.WF_TRANSITION_CANCEL, this.workflowService); + } + catch (WorkflowException wfe) + { + // + // If the indirect cause of workflow exception is a WebScriptException object + // then throw this directly, otherwise it will not be picked up by unit tests + // + + Throwable indirectCause = wfe.getCause().getCause(); + if (indirectCause instanceof WebScriptException) + { + WebScriptException wse = (WebScriptException) indirectCause; + throw wse; + } + else + { + throw wfe; + } + } + // add model properties for template to render model.put(MODEL_PROP_KEY_ACTION, ACTION_CANCEL); model.put(MODEL_PROP_KEY_INVITE_ID, inviteId); diff --git a/source/java/org/alfresco/repo/web/scripts/invite/InviteHelper.java b/source/java/org/alfresco/repo/web/scripts/invite/InviteHelper.java index 0b16930d88..7947d468b2 100644 --- a/source/java/org/alfresco/repo/web/scripts/invite/InviteHelper.java +++ b/source/java/org/alfresco/repo/web/scripts/invite/InviteHelper.java @@ -29,6 +29,9 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.repo.security.authentication.MutableAuthenticationDao; +import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; import org.alfresco.repo.site.SiteInfo; import org.alfresco.repo.site.SiteService; import org.alfresco.repo.template.TemplateNode; @@ -95,7 +98,7 @@ public class InviteHelper // set process name to "wf:invite" so that only tasks associated with // invite workflow instances are returned by query - wfTaskQuery.setProcessName(QName.createQName(NamespaceService.WORKFLOW_MODEL_1_0_URI, "invite")); + wfTaskQuery.setProcessName(InviteWorkflowModel.WF_PROCESS_INVITE); // set query to only pick up invite workflow instances // associated with the given invitee user name @@ -105,7 +108,7 @@ public class InviteHelper // set query to only pick up in-progress invite pending tasks wfTaskQuery.setTaskState(WorkflowTaskState.IN_PROGRESS); - wfTaskQuery.setTaskName(InviteWorkflowModel.WF_TASK_INVITE_PENDING); + wfTaskQuery.setTaskName(InviteWorkflowModel.WF_INVITE_TASK_INVITE_PENDING); // query for invite workflow task associate List inviteStartTasks = workflowService @@ -178,4 +181,88 @@ public class InviteHelper return inviteInfo; } + + /** + * Clean up invitee user account and person node when no longer in use. + * They are deemed to no longer be in use when the invitee user account + * is still disabled and there are no outstanding pending invites for that invitee. + * + * @param inviteeUserName + * @param authenticationDao + * @param personService + * @param workflowService + */ + public static void cleanUpStaleInviteeResources(final String inviteeUserName, + final MutableAuthenticationDao authenticationDao, final PersonService personService, + final WorkflowService workflowService) + { + AuthenticationUtil.runAs(new RunAsWork() + { + public Object doWork() throws Exception + { + // see if there are any pending invites (invite workflow instances with invitePending task in-progress) + // outstanding for given invitee user name + List pendingTasks = InviteHelper.findInvitePendingTasks(inviteeUserName, workflowService); + boolean invitesPending = (pendingTasks != null) && (pendingTasks.size() > 0); + + // if invitee's user account is still disabled and there are no pending invites outstanding + // for the invitee, then remove the account and delete the invitee's person node + if ((authenticationDao.userExists(inviteeUserName)) + && (authenticationDao.getEnabled(inviteeUserName) == false) + && (invitesPending == false)) + { + // delete the invitee's user account + authenticationDao.deleteUser(inviteeUserName); + + // delete the invitee's person node if one exists + if (personService.personExists(inviteeUserName)) + { + personService.deletePerson(inviteeUserName); + } + } + + return null; + } + }, AuthenticationUtil.getSystemUserName()); + } + + /** + * Complete the specified Invite Workflow Task for the invite workflow + * instance associated with the given invite ID, and follow the given + * transition upon completing the task + * + * @param inviteId the invite ID of the invite workflow instance for which + * we want to complete the given task + * @param fullTaskName qualified name of invite workflow task to complete + * @param transitionId the task transition to take on completion of + * the task (or null, for the default transition) + */ + public static void completeInviteTask(String inviteId, QName fullTaskName, String transitionId, + final WorkflowService workflowService) + { + // create workflow task query + WorkflowTaskQuery wfTaskQuery = new WorkflowTaskQuery(); + + // set the given invite ID as the workflow process ID in the workflow query + wfTaskQuery.setProcessId(inviteId); + + // find incomplete invite workflow tasks with given task name + wfTaskQuery.setActive(Boolean.TRUE); + wfTaskQuery.setTaskState(WorkflowTaskState.IN_PROGRESS); + wfTaskQuery.setTaskName(fullTaskName); + + // set process name to "wf:invite" so that only + // invite workflow instances are considered by this query + wfTaskQuery.setProcessName(InviteWorkflowModel.WF_PROCESS_INVITE); + + // query for invite workflow tasks with the constructed query + List wf_invite_tasks = workflowService + .queryTasks(wfTaskQuery); + + // end all tasks found with this name + for (WorkflowTask workflowTask : wf_invite_tasks) + { + workflowService.endTask(workflowTask.id, transitionId); + } + } } diff --git a/source/java/org/alfresco/repo/web/scripts/invite/InviteResponse.java b/source/java/org/alfresco/repo/web/scripts/invite/InviteResponse.java index 02f2edd16b..e13d828c5c 100644 --- a/source/java/org/alfresco/repo/web/scripts/invite/InviteResponse.java +++ b/source/java/org/alfresco/repo/web/scripts/invite/InviteResponse.java @@ -25,14 +25,10 @@ package org.alfresco.repo.web.scripts.invite; import java.util.HashMap; -import java.util.List; import java.util.Map; import org.alfresco.service.cmr.workflow.WorkflowService; import org.alfresco.service.cmr.workflow.WorkflowTask; -import org.alfresco.service.cmr.workflow.WorkflowTaskQuery; -import org.alfresco.service.cmr.workflow.WorkflowTaskState; -import org.alfresco.service.namespace.QName; import org.alfresco.web.scripts.DeclarativeWebScript; import org.alfresco.web.scripts.Status; import org.alfresco.web.scripts.WebScriptException; @@ -140,7 +136,8 @@ public class InviteResponse extends DeclarativeWebScript InviteWorkflowModel.WF_PROP_SITE_SHORT_NAME); // complete the wf:invitePendingTask along the 'accept' transition because the invitation has been accepted - completeInviteTask(inviteId, InviteWorkflowModel.WF_TASK_INVITE_PENDING, InviteWorkflowModel.WF_TRANSITION_ACCEPT); + InviteHelper.completeInviteTask(inviteId, InviteWorkflowModel.WF_INVITE_TASK_INVITE_PENDING, + InviteWorkflowModel.WF_TRANSITION_ACCEPT, this.workflowService); // add model properties for template to render model.put(MODEL_PROP_KEY_RESPONSE, RESPONSE_ACCEPT); @@ -167,49 +164,11 @@ public class InviteResponse extends DeclarativeWebScript InviteWorkflowModel.WF_PROP_SITE_SHORT_NAME); // complete the wf:invitePendingTask task along the 'reject' transition because the invitation has been rejected - completeInviteTask(inviteId, InviteWorkflowModel.WF_TASK_INVITE_PENDING, InviteWorkflowModel.WF_TRANSITION_REJECT); + InviteHelper.completeInviteTask(inviteId, InviteWorkflowModel.WF_INVITE_TASK_INVITE_PENDING, + InviteWorkflowModel.WF_TRANSITION_REJECT, this.workflowService); // add model properties for template to render model.put(MODEL_PROP_KEY_RESPONSE, RESPONSE_REJECT); model.put(MODEL_PROP_KEY_SITE_SHORT_NAME, siteShortName); - } - - /** - * Complete the specified Invite Workflow Task for the invite workflow - * instance associated with the given invite ID, and follow the given - * transition upon completing the task - * - * @param inviteId the invite ID of the invite workflow instance for which - * we want to complete the given task - * @param fullTaskName qualified name of invite workflow task to complete - * @param transitionId the task transition to take on completion of - * the task (or null, for the default transition) - */ - private void completeInviteTask(String inviteId, QName fullTaskName, String transitionId) - { - // create workflow task query - WorkflowTaskQuery wfTaskQuery = new WorkflowTaskQuery(); - - // set the given invite ID as the workflow process ID in the workflow query - wfTaskQuery.setProcessId(inviteId); - - // find incomplete invite workflow tasks with given task name - wfTaskQuery.setActive(Boolean.TRUE); - wfTaskQuery.setTaskState(WorkflowTaskState.IN_PROGRESS); - wfTaskQuery.setTaskName(fullTaskName); - - // set process name to "wf:invite" so that only - // invite workflow instances are considered by this query - wfTaskQuery.setProcessName(InviteWorkflowModel.WF_PROCESS_INVITE); - - // query for invite workflow tasks with the constructed query - List wf_invite_tasks = this.workflowService - .queryTasks(wfTaskQuery); - - // end all tasks found with this name - for (WorkflowTask workflowTask : wf_invite_tasks) - { - this.workflowService.endTask(workflowTask.id, transitionId); - } - } + } } diff --git a/source/java/org/alfresco/repo/web/scripts/invite/InviteServiceTest.java b/source/java/org/alfresco/repo/web/scripts/invite/InviteServiceTest.java index dca4a64b99..4590cab0a6 100644 --- a/source/java/org/alfresco/repo/web/scripts/invite/InviteServiceTest.java +++ b/source/java/org/alfresco/repo/web/scripts/invite/InviteServiceTest.java @@ -29,6 +29,7 @@ import java.util.List; import java.util.Set; import org.alfresco.model.ContentModel; +import org.alfresco.repo.content.MimetypeMap; import org.alfresco.repo.security.authentication.AuthenticationComponent; import org.alfresco.repo.security.authentication.AuthenticationUtil; import org.alfresco.repo.security.authentication.MutableAuthenticationDao; @@ -36,6 +37,8 @@ import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; import org.alfresco.repo.site.SiteInfo; import org.alfresco.repo.site.SiteModel; import org.alfresco.repo.site.SiteService; +import org.alfresco.repo.transaction.RetryingTransactionHelper; +import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; import org.alfresco.repo.web.scripts.BaseWebScriptTest; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeService; @@ -47,6 +50,8 @@ import org.alfresco.service.cmr.security.PersonService; import org.alfresco.service.cmr.workflow.WorkflowDefinition; import org.alfresco.service.cmr.workflow.WorkflowInstance; import org.alfresco.service.cmr.workflow.WorkflowService; +import org.alfresco.service.namespace.NamespaceService; +import org.alfresco.service.transaction.TransactionService; import org.alfresco.util.PropertyMap; import org.alfresco.util.URLEncoder; import org.alfresco.web.scripts.Status; @@ -55,6 +60,7 @@ import org.alfresco.web.scripts.TestWebScriptServer.PutRequest; import org.alfresco.web.scripts.TestWebScriptServer.Response; import org.apache.commons.lang.RandomStringUtils; import org.json.JSONObject; +import org.springframework.core.io.ClassPathResource; /** * Unit Test to test Invite Web Script API @@ -72,6 +78,8 @@ public class InviteServiceTest extends BaseWebScriptTest private NodeService nodeService; private WorkflowService workflowService; private MutableAuthenticationDao mutableAuthenticationDao; + private NamespaceService namespaceService; + private TransactionService transactionService; // stores invitee email addresses, one entry for each "start invite" operation // invoked, so that resources created for each invitee for each test @@ -116,7 +124,19 @@ public class InviteServiceTest extends BaseWebScriptTest this.workflowService = (WorkflowService) getServer().getApplicationContext().getBean("WorkflowService"); this.mutableAuthenticationDao = (MutableAuthenticationDao) getServer().getApplicationContext() .getBean("authenticationDao"); + this.namespaceService = (NamespaceService) getServer().getApplicationContext().getBean("NamespaceService"); + this.transactionService = (TransactionService) getServer().getApplicationContext() + .getBean("TransactionService"); + // redeploy invite process definition in case it has been modified + WorkflowDefinition inviteWfDefinition = this.workflowService.getDefinitionByName( + "jbpm$" + InviteWorkflowModel.WF_PROCESS_INVITE.toPrefixString(this.namespaceService)); + this.workflowService.undeployDefinition(inviteWfDefinition.id); + ClassPathResource inviteWfResource = new ClassPathResource( + "alfresco/workflow/invite_processdefinition.xml"); + workflowService.deployDefinition( + "jbpm", inviteWfResource.getInputStream(), MimetypeMap.MIMETYPE_XML); + // Create new invitee email address list this.inviteeEmailAddrs = new ArrayList(); @@ -354,6 +374,26 @@ public class InviteServiceTest extends BaseWebScriptTest return startInvite(inviteeFirstName, inviteeLastName, inviteeEmail, inviteeSiteRole, siteShortName, expectedStatus); } + + private JSONObject cancelInvite(String inviteId, int expectedStatus) throws Exception + { + String cancelInviteUrl = URL_INVITE + "/" + + INVITE_ACTION_CANCEL + "?inviteId=" + inviteId; + + Response response = sendRequest(new GetRequest(cancelInviteUrl), expectedStatus);; + JSONObject result = new JSONObject(response.getContentAsString()); + + return result; + } + + private JSONObject rejectInvite(String inviteId, String inviteTicket, int expectedStatus) throws Exception + { + // Invitee rejects invitation to a Site from Inviter + String rejectInviteUrl = URL_INVITE + "/" + inviteId + "/" + inviteTicket + "/reject"; + Response response = sendRequest(new PutRequest(rejectInviteUrl, (byte[])null, null), expectedStatus); + JSONObject result = new JSONObject(response.getContentAsString()); + return result; + } private JSONObject getInvitesByInviteId(String inviteId, int expectedStatus) throws Exception @@ -465,7 +505,7 @@ public class InviteServiceTest extends BaseWebScriptTest }, USER_INVITER); - JSONObject result = startInvite(INVITEE_FIRSTNAME, INVITEE_LASTNAME, inviteeEmailAddr, INVITEE_SITE_ROLE, + startInvite(INVITEE_FIRSTNAME, INVITEE_LASTNAME, inviteeEmailAddr, INVITEE_SITE_ROLE, SITE_SHORT_NAME_INVITE_1, Status.STATUS_CONFLICT); } @@ -503,9 +543,7 @@ public class InviteServiceTest extends BaseWebScriptTest String inviteId = result.getString("inviteId"); // Inviter cancels pending invitation - String cancelInviteUrl = URL_INVITE + "/" - + INVITE_ACTION_CANCEL + "?inviteId=" + inviteId; - Response response = sendRequest(new GetRequest(cancelInviteUrl), Status.STATUS_OK); + cancelInvite(inviteId, Status.STATUS_OK); } public void testAcceptInvite() throws Exception @@ -520,7 +558,7 @@ public class InviteServiceTest extends BaseWebScriptTest // Invitee accepts invitation to a Site from Inviter String acceptInviteUrl = URL_INVITE + "/" + inviteId + "/" + inviteTicket + "/accept"; - Response response = sendRequest(new PutRequest(acceptInviteUrl, (byte[])null, null), Status.STATUS_OK); + sendRequest(new PutRequest(acceptInviteUrl, (byte[])null, null), Status.STATUS_OK); // // test that invitation represented by invite ID (of invitation started @@ -548,9 +586,7 @@ public class InviteServiceTest extends BaseWebScriptTest String inviteId = result.getString("inviteId"); String inviteTicket = result.getString("inviteTicket"); - // Invitee rejects invitation to a Site from Inviter - String rejectInviteUrl = URL_INVITE + "/" + inviteId + "/" + inviteTicket + "/reject"; - Response response = sendRequest(new PutRequest(rejectInviteUrl, (byte[])null, null), Status.STATUS_OK); + rejectInvite(inviteId, inviteTicket, Status.STATUS_OK); // // test that invite represented by invite ID (of invitation started @@ -593,7 +629,7 @@ public class InviteServiceTest extends BaseWebScriptTest public void testGetInvitesByInviterUserName() throws Exception { // inviter starts invite workflow - JSONObject startInviteResult = startInvite(INVITEE_FIRSTNAME, + startInvite(INVITEE_FIRSTNAME, INVITEE_LASTNAME, INVITEE_SITE_ROLE, SITE_SHORT_NAME_INVITE_1, Status.STATUS_OK); // get pending invites matching inviter user name used in invite started @@ -658,11 +694,104 @@ public class InviteServiceTest extends BaseWebScriptTest assertEquals(siteShortName, inviteJSONObj.getJSONObject("site").get("shortName")); } - public void testInviteForbiddenWhenInviterNotSiteManager() throws Exception + public void testStartInviteForbiddenWhenInviterNotSiteManager() throws Exception { // inviter2 starts invite workflow, but he/she is not the site manager of the given site AuthenticationUtil.setCurrentUser(USER_INVITER_2); startInvite(INVITEE_FIRSTNAME, INVITEE_LASTNAME, INVITEE_SITE_ROLE, SITE_SHORT_NAME_INVITE_3, Status.STATUS_FORBIDDEN); } + + public void testCancelInviteForbiddenWhenInviterNotSiteManager() throws Exception + { + // inviter (who is Site Manager of the given site) starts invite workflow + JSONObject result = startInvite(INVITEE_FIRSTNAME, + INVITEE_LASTNAME, INVITEE_SITE_ROLE, SITE_SHORT_NAME_INVITE_3, Status.STATUS_OK); + + // get hold of invite ID of started invite + String inviteId = result.getString("inviteId"); + + // when inviter 2 (who is not Site Manager of the given site) tries to cancel invite + // http status FORBIDDEN must be returned + AuthenticationUtil.setCurrentUser(USER_INVITER_2); + cancelInvite(inviteId, Status.STATUS_FORBIDDEN); + } + + public void testInviteeResourcesDeletedUponRejectWhenNoInvitePending() throws Exception + { + // inviter starts invite workflow + JSONObject result = startInvite(INVITEE_FIRSTNAME, + INVITEE_LASTNAME, INVITEE_SITE_ROLE, SITE_SHORT_NAME_INVITE_1, Status.STATUS_OK); + + // get hold of properties of started invite + String inviteId = result.getString("inviteId"); + String inviteTicket = result.getString("inviteTicket"); + final String inviteeUserName = result.getString("inviteeUserName"); + + rejectInvite(inviteId, inviteTicket, Status.STATUS_OK); + + AuthenticationUtil.runAs(new RunAsWork() + { + public Object doWork() throws Exception + { + assertEquals(false, mutableAuthenticationDao.userExists(inviteeUserName)); + assertEquals(false, personService.personExists(inviteeUserName)); + + return null; + } + + }, AuthenticationUtil.getSystemUserName()); + } + + public void testInviteeResourcesNotDeletedUponRejectWhenInvitesPending() throws Exception + { + // inviter invites invitee to site 1 + JSONObject result = startInvite(INVITEE_FIRSTNAME, + INVITEE_LASTNAME, INVITEE_SITE_ROLE, SITE_SHORT_NAME_INVITE_1, Status.STATUS_OK); + + // get hold of properties of started invite + String invite1Id = result.getString("inviteId"); + String invite1Ticket = result.getString("inviteTicket"); + String inviteeEmail = result.getString("inviteeEmail"); + final String inviteeUserName = result.getString("inviteeUserName"); + + // inviter invites invitee to site 2 + startInvite(INVITEE_FIRSTNAME, + INVITEE_LASTNAME, inviteeEmail, INVITEE_SITE_ROLE, SITE_SHORT_NAME_INVITE_2, Status.STATUS_OK); + + rejectInvite(invite1Id, invite1Ticket, Status.STATUS_OK); + + boolean inviteeUserExists = AuthenticationUtil.runAs(new RunAsWork() + { + public Boolean doWork() throws Exception + { + RetryingTransactionHelper tranHelper = transactionService.getRetryingTransactionHelper(); + Boolean result = tranHelper.doInTransaction(new RetryingTransactionCallback() + { + public Boolean execute() throws Throwable + { + Boolean result = mutableAuthenticationDao.userExists(inviteeUserName); + return result; + } + }); + + return result; + } + }, AuthenticationUtil.getSystemUserName()); + + // test that the invitee's user account still exists (has not been deleted + assertEquals(true, inviteeUserExists); + + boolean inviteePersonExists = AuthenticationUtil.runAs(new RunAsWork() + { + public Boolean doWork() throws Exception + { + Boolean result = personService.personExists(inviteeUserName); + + return result; + } + }, AuthenticationUtil.getSystemUserName()); + + assertEquals(true, inviteePersonExists); + } } diff --git a/source/java/org/alfresco/repo/web/scripts/invite/InviteWorkflowModel.java b/source/java/org/alfresco/repo/web/scripts/invite/InviteWorkflowModel.java index 47363579eb..f3323ee07a 100644 --- a/source/java/org/alfresco/repo/web/scripts/invite/InviteWorkflowModel.java +++ b/source/java/org/alfresco/repo/web/scripts/invite/InviteWorkflowModel.java @@ -16,13 +16,12 @@ public interface InviteWorkflowModel { public static final QName WF_INVITE_TASK_INVITE_PENDING = QName.createQName(NamespaceService.WORKFLOW_MODEL_1_0_URI, "invitePendingTask"); public static final QName WF_TASK_ACCEPT_INVITE = QName.createQName(NamespaceService.WORKFLOW_MODEL_1_0_URI, "acceptInviteTask"); public static final QName WF_TASK_REJECT_INVITE = QName.createQName(NamespaceService.WORKFLOW_MODEL_1_0_URI, "rejectInviteTask"); - public static final QName WF_TASK_INVITE_PENDING = QName.createQName(NamespaceService.WORKFLOW_MODEL_1_0_URI, "invitePendingTask"); - // transition names public static final String WF_TRANSITION_SEND_INVITE = "sendInvite"; public static final String WF_TRANSITION_ACCEPT = "accept"; public static final String WF_TRANSITION_REJECT = "reject"; + public static final String WF_TRANSITION_CANCEL = "cancel"; public static final String WF_TRANSITION_ACCEPT_INVITE_END = "end"; public static final String WF_TRANSITION_REJECT_INVITE_END = "end"; @@ -40,4 +39,8 @@ public interface InviteWorkflowModel { public static final QName WF_PROP_SENT_INVITE_DATE = QName.createQName(NamespaceService.WORKFLOW_MODEL_1_0_URI, "sentInviteDate"); public static final QName WF_PROP_INVITEE_GEN_PASSWORD = QName.createQName(NamespaceService.WORKFLOW_MODEL_1_0_URI, "inviteeGenPassword"); + // workflow execution context variable names + public static final String wfVarInviteeUserName = "wf_inviteeUserName"; + public static final String wfVarSiteShortName = "wf_siteShortName"; + public static final String wfVarWorkflowInstanceId = "workflowinstanceid"; } diff --git a/source/java/org/alfresco/repo/web/scripts/invite/RejectInviteAction.java b/source/java/org/alfresco/repo/web/scripts/invite/RejectInviteAction.java index c80bb74c08..101fd9b92e 100644 --- a/source/java/org/alfresco/repo/web/scripts/invite/RejectInviteAction.java +++ b/source/java/org/alfresco/repo/web/scripts/invite/RejectInviteAction.java @@ -24,16 +24,11 @@ */ package org.alfresco.repo.web.scripts.invite; -import java.util.List; - -import org.alfresco.repo.security.authentication.AuthenticationUtil; import org.alfresco.repo.security.authentication.MutableAuthenticationDao; -import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; import org.alfresco.repo.workflow.jbpm.JBPMSpringActionHandler; import org.alfresco.service.ServiceRegistry; import org.alfresco.service.cmr.security.PersonService; import org.alfresco.service.cmr.workflow.WorkflowService; -import org.alfresco.service.cmr.workflow.WorkflowTask; import org.jbpm.graph.exe.ExecutionContext; import org.springframework.beans.factory.BeanFactory; @@ -71,35 +66,12 @@ public class RejectInviteAction extends JBPMSpringActionHandler public void execute(final ExecutionContext executionContext) throws Exception { // get the invitee user name - final String inviteeUserName = (String) executionContext.getVariable("wf_inviteeUserName"); - - AuthenticationUtil.runAs(new RunAsWork() - { - public Object doWork() throws Exception - { - // see if there are any pending invites (invite workflow instances with invitePending task in-progress) - // outstanding for invitee who has responded here with an invite rejection - List pendingTasks = InviteHelper.findInvitePendingTasks(inviteeUserName, workflowService); - boolean invitesPending = (pendingTasks != null) && (pendingTasks.size() > 0); - - // if invitee's user account is still disabled and there are no pending invites outstanding - // for the invitee, then remove the account and delete the invitee's person node - if ((RejectInviteAction.this.mutableAuthenticationDao.userExists(inviteeUserName)) - && (RejectInviteAction.this.mutableAuthenticationDao.getEnabled(inviteeUserName) == false) - && (invitesPending == false)) - { - // delete the invitee's user account - mutableAuthenticationDao.deleteUser(inviteeUserName); - - // delete the invitee's person node if one exists - if (personService.personExists(inviteeUserName)) - { - personService.deletePerson(inviteeUserName); - } - } + final String inviteeUserName = (String) executionContext.getVariable(InviteWorkflowModel.wfVarInviteeUserName); - return null; - } - }, AuthenticationUtil.getSystemUserName()); + // clean up invitee's user account and person node if they are not in use i.e. + // account is still disabled and there are no pending invites outstanding for the + // invitee + InviteHelper.cleanUpStaleInviteeResources(inviteeUserName, mutableAuthenticationDao, personService, + workflowService); } }