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 de4b24d846..f14e42088a 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,8 +1,9 @@ Invite Processes Inviter actions ('start' or 'cancel' invite) - /api/invite/{action}?site={siteShortName} - extension + /api/invite/start?inviteeEmail={inviteeEmailAddress}&siteShortName={siteShortName} + /api/invite/cancel?workflowId={workflowId} + user required \ No newline at end of file diff --git a/config/alfresco/templates/webscripts/org/alfresco/repository/invite/invite.get.js b/config/alfresco/templates/webscripts/org/alfresco/repository/invite/invite.get.js index 2f9b2130f4..3893115a8d 100644 --- a/config/alfresco/templates/webscripts/org/alfresco/repository/invite/invite.get.js +++ b/config/alfresco/templates/webscripts/org/alfresco/repository/invite/invite.get.js @@ -1,33 +1,57 @@ +var WORKFLOW_DEFINITION_NAME = "jbpm$wf:invite"; +var ACTION_START = "start"; +var ACTION_CANCEL = "cancel"; +var TRANSITION_SEND_INVITE = "sendInvite"; + /** * Starts the Invite workflow * * @method start * @static + * @param inviteeEmail string email address of invitee * @param siteShortName string short name of site that the invitee is being * invited to by the inviter * */ -function start(siteShortName) +function start(inviteeEmail, siteShortName) { - var wfDefinition = workflow.getDefinitionByName("wf:invite"); + var wfDefinition = workflow.getDefinitionByName(WORKFLOW_DEFINITION_NAME); - // create invitee with generated user name and password, and with a + // handle workflow definition does not exist + if (wfDefinition === null) + { + status.setCode(status.STATUS_INTERNAL_SERVER_ERROR, "Workflow definition " + + "for name " + WORKFLOW_DEFINITION_NAME + " does not exist"); + return; + } + + // create invitee person with generated user name and password, and with a // disabled user account var invitee = people.createPerson(true, false); + invitee.properties["cm:email"] = inviteeEmail; + invitee.save(); - // create workflow properties, containing inviter and invitee user name, - // and site short name + var inviteeUserName = invitee.properties.userName; + + // create workflow properties var workflowProps = []; - workflowProps["inviterUserName"] = person.properties.userName; - workflowProps["inviteeUserName"] = invitee.properties.userName; - workflowProps["siteShortName"] = siteShortName; - wfDefinition.startWorkflow(null, workflowProps); + workflowProps["wf:inviterUserName"] = person.properties.userName; + workflowProps["wf:inviteeUserName"] = inviteeUserName; + workflowProps["wf:inviteeGenPassword"] = invitee.properties.generatedPassword; + workflowProps["wf:siteShortName"] = siteShortName; - // add action context info to model - model.action = "start"; + // start the workflow + var wfPath = wfDefinition.startWorkflow(workflowProps); + var workflowId = wfPath.instance.id; + + // send out the invite + wfPath.signal(TRANSITION_SEND_INVITE); + + // add action info to model for template processing + model.action = ACTION_START; + model.workflowId = workflowId; model.inviteeUserName = inviteeUserName; model.siteShortName = siteShortName; - } /** @@ -42,10 +66,20 @@ function start(siteShortName) function cancel(workflowId) { var workflowInstance = workflow.getInstance(workflowId); + + // handle workflow instance for given workflow ID does not exist + if (workflowInstance === null) + { + status.setCode(status.STATUS_BAD_REQUEST, "Workflow instance for given " + + "workflow ID " + workflowId + " does not exist"); + return; + } + + // cancel the workflow workflowInstance.cancel(); - // add action context info to model - model.action = "cancel"; + // add action info to model for template + model.action = ACTION_CANCEL; model.workflowId = workflowId; } @@ -57,60 +91,77 @@ function cancel(workflowId) */ function main() { - // check that the action ('start' or 'cancel') has been provided on the URL - // and that URL parameters have been provided - if ((url.extension === null) || (args.length == 0)) + // extract action string from URL + var action = null; + var actionStartIndex = url.service.lastIndexOf("/") + 1; + if (actionStartIndex <= url.service.length() - 1) { - // handle action not provided or no parameters given - status.code = 400; - status.message = "Action has not been provided in URL or " + - "no parameters have been provided on URL"; - status.redirect = true; + action = url.service.substring(actionStartIndex, url.service.length()); } + + // check that the action has been provided on the URL + // and that URL parameters have been provided + if ((action === null) || (action.length == 0)) + { + // handle action not provided on URL + status.setCode(status.STATUS_BAD_REQUEST, "Action has not been provided in URL"); + return; + } + + // handle no parameters given on URL + if (args.length == 0) + { + status.setCode(status.STATUS_BAD_REQUEST, "No parameters have been provided on URL"); + return; + } + + // handle action 'start' + if (action == ACTION_START) + { + // check for 'inviteeEmail' parameter not provided + if ((args["inviteeEmail"] === null) || (args["inviteeEmail"].length == 0)) + { + // handle inviteeEmail URL parameter not provided + status.setCode(status.STATUS_BAD_REQUEST, "'inviteeEmail' parameter " + + "has not been provided in URL for action '" + ACTION_START + "'"); + return; + } + + // check for 'siteShortName' parameter not provided + if ((args["siteShortName"] === null) || (args["siteShortName"].length == 0)) + { + // handle siteShortName URL parameter not provided + status.setCode(status.STATUS_BAD_REQUEST, "'siteShortName' parameter " + + "has not been provided in URL for action '" + ACTION_START + "'"); + return; + } + + // process action 'start' with provided parameters + var inviteeEmail = args["inviteeEmail"]; + var siteShortName = args["siteShortName"]; + start(inviteeEmail, siteShortName); + } + // else handle if provided 'action' is 'cancel' + else if (action == ACTION_CANCEL) + { + // check for 'workflowId' parameter not provided + if ((args["workflowId"] === null) || (args["workflowId"].length == 0)) + { + // handle workflowId URL parameter not provided + status.setCode(status.STATUS_BAD_REQUEST, "'workflowId' parameter has " + + "not been provided in URL for action '" + ACTION_CANCEL +"'"); + return; + } + + // process action 'cancel' with provided parameters + var workflowId = args["workflowId"]; + cancel(workflowId); + } + // handle action not recognised else { - // handle action provided in URL ('start' or 'cancel') - var action = url.extension; - - // handle if provided 'action' is 'start' - if (action == "start") - { - // check for 'siteShortName' parameter - if ((args["siteShortName"] === null) || (args["siteShortName"].length == 0)) - { - // handle siteShortName URL parameter not provided - status.code = 400; - status.message = "'siteShortName' parameter has not been provided in URL for action 'start'"; - status.redirect = true; - } - else - { - start(args["siteShortName"]); - } - } - // else handle if provided 'action' is 'cancel' - else if (action == "cancel") - { - // check for 'workflowId' parameter - if ((args["workflowId"] === null) || (args["workflowId"].length == 0)) - { - // handle workflowId URL parameter not provided - status.code = 400; - status.message = "'workflowId' parameter has not been provided in URL for action 'cancel'"; - status.redirect = true; - } - else - { - cancel(args["workflowId"]); - } - } - // handle action not recognised - else - { - status.code = 400; - status.message = "Action, '" + action + "', provided in URL has not been recognised."; - status.redirect = true; - } + status.setCode(status.STATUS_BAD_REQUEST, "Action, '" + action + "', " + + "provided in URL has not been recognised."); } } diff --git a/config/alfresco/templates/webscripts/org/alfresco/repository/invite/invite.get.json.ftl b/config/alfresco/templates/webscripts/org/alfresco/repository/invite/invite.get.json.ftl new file mode 100644 index 0000000000..890cd90664 --- /dev/null +++ b/config/alfresco/templates/webscripts/org/alfresco/repository/invite/invite.get.json.ftl @@ -0,0 +1,18 @@ +{ + "action" : "${action}", + <#if workflowId??> + "workflowId" : "${workflowId}", + <#else> + "workflowId" : undefined, + + <#if inviteeUserName??> + "inviteeUserName" : "${inviteeUserName}", + <#else> + "inviteeUserName" : undefined, + + <#if siteShortName??> + "siteShortName" : "${siteShortName}" + <#else> + "siteShortName" : undefined + +} \ No newline at end of file diff --git a/config/alfresco/templates/webscripts/org/alfresco/repository/invite/inviteresponse.get.desc.xml b/config/alfresco/templates/webscripts/org/alfresco/repository/invite/inviteresponse.get.desc.xml index 9c4e619bf0..9539128faf 100644 --- a/config/alfresco/templates/webscripts/org/alfresco/repository/invite/inviteresponse.get.desc.xml +++ b/config/alfresco/templates/webscripts/org/alfresco/repository/invite/inviteresponse.get.desc.xml @@ -2,7 +2,7 @@ Invite Response Processes invite response from Invitee /api/inviteresponse/{response}?workflowId={workflowId}&inviteeUserName={inviteeUserName}&siteShortName={siteShortName} - extension + guest required \ No newline at end of file diff --git a/config/alfresco/templates/webscripts/org/alfresco/repository/invite/inviteresponse.get.js b/config/alfresco/templates/webscripts/org/alfresco/repository/invite/inviteresponse.get.js index ddb1b3b295..f392275c85 100644 --- a/config/alfresco/templates/webscripts/org/alfresco/repository/invite/inviteresponse.get.js +++ b/config/alfresco/templates/webscripts/org/alfresco/repository/invite/inviteresponse.get.js @@ -1,4 +1,6 @@ -var SITE_ROLE_COLLABORATOR = "collaborator"; +var SITE_ROLE_COLLABORATOR = "SiteCollaborator"; +var TRANSITION_ACCEPT = "accept"; +var TRANSITION_REJECT = "reject"; /** * Processes 'accept' response from invitee @@ -12,8 +14,22 @@ var SITE_ROLE_COLLABORATOR = "collaborator"; function accept(workflowId, inviteeUserName, siteShortName) { var wfInstance = workflow.getInstance(workflowId); - var wfPath = wfInstance.getPaths()[0]; - wfPath.signal("accept"); + var wfPaths = wfInstance.getPaths(); + + // return error message if no workflow paths found for + // supplied workflow id + if ((wfPaths === null) || (wfPaths.length == 0)) + { + status.setCode(status.STATUS_INTERNAL_SERVER_ERROR, + "No workflow paths associated with workflow ID: " + + workflowId); + return; + } + + // get the first workflow path off the workflow paths array + // (there should only be one) and signal a transition to "accept" + var wfPath = wfPaths[0]; + wfPath.signal(TRANSITION_ACCEPT); people.enablePerson(inviteeUserName); @@ -21,8 +37,8 @@ function accept(workflowId, inviteeUserName, siteShortName) * Find out role string that Invitee should be added to Site as */ // Add Invitee to Site - var site = sites.getSite(siteShortName); - site.setMembership(username, SITE_ROLE_COLLABORATOR); + var site = siteService.getSite(siteShortName); + site.setMembership(inviteeUserName, SITE_ROLE_COLLABORATOR); // add data to appear in rendition model.response = "accept"; @@ -40,9 +56,12 @@ function accept(workflowId, inviteeUserName, siteShortName) */ function reject(workflowId, inviteeUserName, siteShortName) { - var wfInstance = workflow.getInstance(wfid); + var wfInstance = workflow.getInstance(workflowId); var wfPath = wfInstance.getPaths()[0]; - wfPath.signal("reject"); + wfPath.signal(TRANSITION_REJECT); + + // delete the person created for invitee + people.deletePerson(inviteeUserName); // add data to appear in rendition model.response = "reject"; diff --git a/config/alfresco/templates/webscripts/org/alfresco/repository/invite/inviteresponse.get.json.ftl b/config/alfresco/templates/webscripts/org/alfresco/repository/invite/inviteresponse.get.json.ftl new file mode 100644 index 0000000000..c0adf83a3a --- /dev/null +++ b/config/alfresco/templates/webscripts/org/alfresco/repository/invite/inviteresponse.get.json.ftl @@ -0,0 +1,4 @@ +{ + "response" : "${response}", + "siteShortName" : "${siteShortName}" +} diff --git a/config/alfresco/workflow/invite-workflow-model.xml b/config/alfresco/workflow/invite-workflow-model.xml index b8b58b618d..4f53446d43 100644 --- a/config/alfresco/workflow/invite-workflow-model.xml +++ b/config/alfresco/workflow/invite-workflow-model.xml @@ -1,11 +1,12 @@ - + @@ -13,6 +14,24 @@ bpm:startTask + + + d:text + + + d:text + + + d:text + + + d:text + + + + + + bpm:workflowTask diff --git a/config/alfresco/workflow/invite_processdefinition.xml b/config/alfresco/workflow/invite_processdefinition.xml index 5478137027..7a24201123 100644 --- a/config/alfresco/workflow/invite_processdefinition.xml +++ b/config/alfresco/workflow/invite_processdefinition.xml @@ -2,76 +2,80 @@ - + + + - + + - - + + - + + - - + - - + + - - - - - + - + diff --git a/source/java/org/alfresco/repo/web/scripts/invite/InviteServiceTest.java b/source/java/org/alfresco/repo/web/scripts/invite/InviteServiceTest.java new file mode 100644 index 0000000000..785158edb7 --- /dev/null +++ b/source/java/org/alfresco/repo/web/scripts/invite/InviteServiceTest.java @@ -0,0 +1,207 @@ +/* + * 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.model.ContentModel; +import org.alfresco.repo.security.authentication.AuthenticationComponent; +import org.alfresco.repo.site.SiteInfo; +import org.alfresco.repo.site.SiteService; +import org.alfresco.repo.web.scripts.BaseWebScriptTest; +import org.alfresco.service.cmr.security.AuthenticationService; +import org.alfresco.service.cmr.security.PersonService; +import org.alfresco.util.PropertyMap; +import org.alfresco.web.scripts.Status; +import org.json.JSONObject; +import org.springframework.mock.web.MockHttpServletResponse; + +/** + * Unit Test to test Invite Web Script API + * + * @author Glen Johnson at Alfresco dot com + */ +public class InviteServiceTest extends BaseWebScriptTest +{ + private AuthenticationService authenticationService; + private AuthenticationComponent authenticationComponent; + private PersonService personService; + private SiteService siteService; + + private static final String USER_ADMIN = "admin"; + private static final String USER_INVITER = "InviteeUser"; + private static final String INVITEE_EMAIL = "inviter123@email.com"; + private static final String SITE_SHORT_NAME_INVITE = "InviteSiteShortName"; + + private static final String URL_INVITE_SERVICE = "/api/invite"; + private static final String URL_INVITERSP_SERVICE = "/api/inviteresponse"; + + private static final String INVITE_ACTION_START = "start"; + private static final String INVITE_ACTION_CANCEL = "cancel"; + + private static final String INVITE_RSP_ACCEPT = "accept"; + private static final String INVITE_RSP_REJECT = "reject"; + + @Override + protected void setUp() throws Exception + { + super.setUp(); + + this.authenticationService = (AuthenticationService)getServer().getApplicationContext().getBean("AuthenticationService"); + this.authenticationComponent = (AuthenticationComponent)getServer().getApplicationContext().getBean("authenticationComponent"); + this.personService = (PersonService)getServer().getApplicationContext().getBean("PersonService"); + this.siteService = (SiteService)getServer().getApplicationContext().getBean("siteService"); + + // Create inviter user + createUser(USER_INVITER); + + // Create site for Inviter to invite Invitee to + // - only create the site if it doesn't already exist + SiteInfo siteInfo = this.siteService.getSite(SITE_SHORT_NAME_INVITE); + if (siteInfo == null) + { + this.siteService.createSite("InviteSitePreset", SITE_SHORT_NAME_INVITE, "InviteSiteTitle", + "InviteSiteDescription", true); + } + + // Do tests as inviter user + this.authenticationComponent.setCurrentUser(USER_INVITER); + } + + @Override + protected void tearDown() throws Exception + { + super.tearDown(); + + // admin user required to delete user + this.authenticationComponent.setCurrentUser(USER_ADMIN); + + // delete the inviter user + personService.deletePerson(USER_INVITER); + + // delete invite site + siteService.deleteSite(SITE_SHORT_NAME_INVITE); + } + + private void createUser(String userName) + { + // if user with given user name doesn't already exist then create user + if (this.authenticationService.authenticationExists(userName) == false) + { + // create user + this.authenticationService.createAuthentication(userName, "password".toCharArray()); + + // create person properties + PropertyMap personProps = new PropertyMap(); + personProps.put(ContentModel.PROP_USERNAME, userName); + personProps.put(ContentModel.PROP_FIRSTNAME, "FirstName123"); + personProps.put(ContentModel.PROP_LASTNAME, "LastName123"); + personProps.put(ContentModel.PROP_EMAIL, "FirstName123.LastName123@email.com"); + personProps.put(ContentModel.PROP_JOBTITLE, "JobTitle123"); + personProps.put(ContentModel.PROP_JOBTITLE, "Organisation123"); + + // create person node for user + this.personService.createPerson(personProps); + } + } + + private JSONObject startInvite(String inviteeEmail, String siteShortName, int expectedStatus) + throws Exception + { + // Inviter sends invitation to Invitee to join a Site + String startInviteUrl = URL_INVITE_SERVICE + "/" + INVITE_ACTION_START + "?inviteeEmail=" + inviteeEmail + + "&siteShortName=" + siteShortName; + MockHttpServletResponse response = getRequest(startInviteUrl, expectedStatus); + + JSONObject result = new JSONObject(response.getContentAsString()); + + return result; + } + + public void testStartInvite() throws Exception + { + // admin user required to delete user + this.authenticationComponent.setCurrentUser(USER_ADMIN); + + JSONObject result = startInvite(INVITEE_EMAIL, SITE_SHORT_NAME_INVITE, Status.STATUS_OK); + + assertEquals(INVITE_ACTION_START, result.get("action")); + assertEquals(SITE_SHORT_NAME_INVITE, result.get("siteShortName")); + } + + public void testCancelInvite() throws Exception + { + // admin user required to delete user + this.authenticationComponent.setCurrentUser(USER_ADMIN); + + // inviter starts invite workflow + JSONObject result = startInvite(INVITEE_EMAIL, SITE_SHORT_NAME_INVITE, Status.STATUS_OK); + + // get hold of workflow ID of started invite workflow instance + String workflowId = result.getString("workflowId"); + + // Inviter cancels pending invitation + String cancelInviteUrl = URL_INVITE_SERVICE + "/" + INVITE_ACTION_CANCEL + "?workflowId=" + workflowId; + MockHttpServletResponse response = getRequest(cancelInviteUrl, Status.STATUS_OK); + } + + public void testAcceptInvite() throws Exception + { + // admin user required to delete user + this.authenticationComponent.setCurrentUser(USER_ADMIN); + + // inviter starts invite workflow + JSONObject result = startInvite(INVITEE_EMAIL, SITE_SHORT_NAME_INVITE, Status.STATUS_OK); + + // get hold of workflow ID of started invite workflow instance + String workflowId = result.getString("workflowId"); + + // get hold of invitee user name that was generated as part of starting the invite + String inviteeUserName = result.getString("inviteeUserName"); + + // Invitee accepts invitation to a Site from Inviter + String acceptInviteUrl = URL_INVITERSP_SERVICE + "/" + INVITE_RSP_ACCEPT + "?workflowId=" + workflowId + + "&inviteeUserName=" + inviteeUserName + "&siteShortName=" + SITE_SHORT_NAME_INVITE; + MockHttpServletResponse response = getRequest(acceptInviteUrl, Status.STATUS_OK); + } + + public void testRejectInvite() throws Exception + { + // admin user required to delete user + this.authenticationComponent.setCurrentUser(USER_ADMIN); + + // inviter starts invite workflow + JSONObject result = startInvite(INVITEE_EMAIL, SITE_SHORT_NAME_INVITE, Status.STATUS_OK); + + // get hold of workflow ID of started invite workflow instance + String workflowId = result.getString("workflowId"); + + // get hold of invitee user name that was generated as part of starting the invite + String inviteeUserName = result.getString("inviteeUserName"); + + // Invitee rejects invitation to a Site from Inviter + String rejectInviteUrl = URL_INVITERSP_SERVICE + "/" + INVITE_RSP_REJECT + "?workflowId=" + workflowId + + "&inviteeUserName=" + inviteeUserName + "&siteShortName=" + SITE_SHORT_NAME_INVITE; + MockHttpServletResponse response = getRequest(rejectInviteUrl, Status.STATUS_OK); + } +} \ No newline at end of file