From b96832b0fd5d3cd98a7c2af59334a996d23cf8a4 Mon Sep 17 00:00:00 2001 From: Jamal Kaabi-Mofrad Date: Thu, 16 Mar 2017 19:39:24 +0000 Subject: [PATCH] Merged WEBAPP-API (5.2.1) to 5.2.N (5.2.1) 135590 jkaabimofrad: APPSREPO-35: Added password reset V1 API. git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/BRANCHES/DEV/5.2.N/root@135930 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261 --- .../application-context-highlevel.xml | 1 + config/alfresco/bootstrap-context.xml | 9 + .../config/repo-clients-apps.properties | 19 +- .../reset-password-messages.properties | 15 + config/alfresco/repository.properties | 4 + config/alfresco/reset-password-context.xml | 40 + ...t-password-confirmation-email-template.ftl | 450 ++++++++++ .../reset-password-email-template.ftl | 455 ++++++++++ .../reset-password-workflow-model.xml | 64 ++ ...eset-password_processdefinition.bpmn20.xml | 45 + .../workflow/workflow-messages.properties | 4 + .../authentication/ResetPasswordService.java | 92 +++ .../ResetPasswordServiceImpl.java | 776 ++++++++++++++++++ .../AbstractResetPasswordDelegate.java | 44 + .../PerformResetPasswordDelegate.java | 48 ++ .../SendResetPasswordEmailDelegate.java | 48 ++ .../workflow/WorkflowModelResetPassword.java | 62 ++ 17 files changed, 2172 insertions(+), 4 deletions(-) create mode 100644 config/alfresco/messages/reset-password-messages.properties create mode 100644 config/alfresco/reset-password-context.xml create mode 100644 config/alfresco/templates/reset-password-email-templates/reset-password-confirmation-email-template.ftl create mode 100644 config/alfresco/templates/reset-password-email-templates/reset-password-email-template.ftl create mode 100644 config/alfresco/workflow/reset-password-workflow-model.xml create mode 100644 config/alfresco/workflow/reset-password_processdefinition.bpmn20.xml create mode 100644 source/java/org/alfresco/repo/security/authentication/ResetPasswordService.java create mode 100644 source/java/org/alfresco/repo/security/authentication/ResetPasswordServiceImpl.java create mode 100644 source/java/org/alfresco/repo/security/authentication/activiti/AbstractResetPasswordDelegate.java create mode 100644 source/java/org/alfresco/repo/security/authentication/activiti/PerformResetPasswordDelegate.java create mode 100644 source/java/org/alfresco/repo/security/authentication/activiti/SendResetPasswordEmailDelegate.java create mode 100644 source/java/org/alfresco/repo/workflow/WorkflowModelResetPassword.java diff --git a/config/alfresco/application-context-highlevel.xml b/config/alfresco/application-context-highlevel.xml index 3d71d7c776..8273b7ee91 100644 --- a/config/alfresco/application-context-highlevel.xml +++ b/config/alfresco/application-context-highlevel.xml @@ -42,4 +42,5 @@ + diff --git a/config/alfresco/bootstrap-context.xml b/config/alfresco/bootstrap-context.xml index 7682bb37cc..451d23ab3b 100644 --- a/config/alfresco/bootstrap-context.xml +++ b/config/alfresco/bootstrap-context.xml @@ -215,6 +215,14 @@ text/xml false + + + + activiti + alfresco/workflow/reset-password_processdefinition.bpmn20.xml + text/xml + false + @@ -223,6 +231,7 @@ alfresco/workflow/invitation-nominated-workflow-model.xml alfresco/workflow/invitation-moderated-workflow-model.xml alfresco/workflow/publishingWorkflowModel.xml + alfresco/workflow/reset-password-workflow-model.xml diff --git a/config/alfresco/client/config/repo-clients-apps.properties b/config/alfresco/client/config/repo-clients-apps.properties index a4a043fdcb..d1d0ff8779 100644 --- a/config/alfresco/client/config/repo-clients-apps.properties +++ b/config/alfresco/client/config/repo-clients-apps.properties @@ -1,9 +1,20 @@ # registry of clients that are able to email -repo.client-app.share.templateAssetsUrl=${shareUrl}/res/components/images/ -repo.client-app.share.sharedLinkBaseUrl=${shareUrl}/s -# the path could be: +## NOTE: when setting the email template path, the value could be: # an XPATH (e.g. app:company_home/app:dictionary/app:email_templates/example-email.ftl) # a NodeRef of the template # a class path of the template -repo.client-app.share.sharedLinkTemplatePath= \ No newline at end of file + +# template assets url for share client +repo.client-app.share.templateAssetsUrl=${shareUrl}/res/components/images/ +# shared-link (quickShare) base url +repo.client-app.share.sharedLinkBaseUrl=${shareUrl}/s +# shared-link email template path +repo.client-app.share.sharedLinkTemplatePath= + +# reset password request email template path +repo.client-app.share.requestResetPasswordTemplatePath= +# reset password UI page url +repo.client-app.share.resetPasswordPageUrl=${shareUrl}/page/reset-password +# reset password confirmation email template path +repo.client-app.share.confirmResetPasswordTemplatePath= \ No newline at end of file diff --git a/config/alfresco/messages/reset-password-messages.properties b/config/alfresco/messages/reset-password-messages.properties new file mode 100644 index 0000000000..518d310305 --- /dev/null +++ b/config/alfresco/messages/reset-password-messages.properties @@ -0,0 +1,15 @@ +# Email subjects. +reset-password-request.email.subject=Reset your password +reset-password-confirmation.email.subject=Password reset successful + +# Request reset password email +templates.reset-password-email.ftl.title=Reset your password +templates.reset-password-email.ftl.detail=You recently requested to change the password for your account. +templates.reset-password-email.ftl.ignore_message=If you didn't request a password reset or you don't want to change it, you can ignore and delete this message, and your account will remain secure. +templates.reset-password-email.ftl.having_trouble_clicking_button=If you're having trouble clicking on the Reset Password button, just copy and paste the following URL into your web browser: +templates.reset-password-email.ftl.reset_password_button=Reset password + +# Reset password confirmation email +templates.reset-password-confirm-email.ftl.title=Password reset successful +templates.reset-password-confirm-email.ftl.detail=The password for your Alfresco account was recently changed. +templates.reset-password-confirm-email.ftl.note=If you didn't request a password reset, please contact your administrator immediately. diff --git a/config/alfresco/repository.properties b/config/alfresco/repository.properties index 4c1a82b945..663a6b297b 100644 --- a/config/alfresco/repository.properties +++ b/config/alfresco/repository.properties @@ -1228,3 +1228,7 @@ category.queryFetchSize=5000 authentication.protection.enabled=true authentication.protection.limit=10 authentication.protection.periodSeconds=6 + +system.email.sender.default=noreply@alfresco.com +# reset password workflow will expire in an hour +system.reset-password.endTimer=PT1H diff --git a/config/alfresco/reset-password-context.xml b/config/alfresco/reset-password-context.xml new file mode 100644 index 0000000000..89cd0a0c17 --- /dev/null +++ b/config/alfresco/reset-password-context.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + alfresco.messages.reset-password-messages + + + + + + + + + + + + + + \ No newline at end of file diff --git a/config/alfresco/templates/reset-password-email-templates/reset-password-confirmation-email-template.ftl b/config/alfresco/templates/reset-password-email-templates/reset-password-confirmation-email-template.ftl new file mode 100644 index 0000000000..2669f885e9 --- /dev/null +++ b/config/alfresco/templates/reset-password-email-templates/reset-password-confirmation-email-template.ftl @@ -0,0 +1,450 @@ + + + + + + + + + + + + +
+ + + + +
+ + + + + + +
+ + + + + + +
+ + + + +
+
+ - +
+
+
+
+ + + + + + + +
+ + + + +
Alfresco
+
+ + + + +
make business flow +
+
+ + + + + + +
+
 
+
+ + + + + + + +
+ + + + +
+ +
+
+ + + + + +
+ + + + + + +
+ +

${message("templates.reset-password-confirm-email.ftl.title")}

+
+

${message("templates.generic-email.ftl.salutation", userName)?html}

+

${message("templates.reset-password-confirm-email.ftl.detail")}

+
+ +
+ + + + + + +
+
 
+
+ + + + + + +
+
+ +

${message("templates.reset-password-confirm-email.ftl.note")}

+ +
+
+
+
+ + + + + + +
+
 
+
+ +
+
+ + + + + + +
+
 
+
+ + + + + + +
+ + + + + + +
      alfresco.com
+
+ ${message("templates.generic-email.ftl.contact_us")} + © ${date?string["yyyy"]} Alfresco Software, Inc. + ${message("templates.generic-email.ftl.copy_right")}
+ Bridge Ave, The Place Maidenhead SL6 1AF United Kingdom
+ 1825 S Grant St, Suite 900 San Mateo, CA 94402 USA
+
+ + + + + + +
+
 
+
+ +
+
+ + + + + + + +
{{my.Litmus_Code}}
+ + diff --git a/config/alfresco/templates/reset-password-email-templates/reset-password-email-template.ftl b/config/alfresco/templates/reset-password-email-templates/reset-password-email-template.ftl new file mode 100644 index 0000000000..e8066f745f --- /dev/null +++ b/config/alfresco/templates/reset-password-email-templates/reset-password-email-template.ftl @@ -0,0 +1,455 @@ + + + + + + + + + + + + +
+ + + + +
+ + + + + + +
+ + + + + + +
+ + + + +
+
+ - +
+
+
+
+ + + + + + + +
+ + + + +
Alfresco
+
+ + + + +
make business flow +
+
+ + + + + + +
+
 
+
+ + + + + + + +
+ + + + +
+ +
+
+ + + + + +
+ + + + + + +
+ +

${message("templates.reset-password-email.ftl.title")}

+
+ +

${message("templates.reset-password-email.ftl.detail")}

+
+ +
+ + + + + + +
+
 
+
+ + + + + + +
+
+ + +

${message("templates.reset-password-email.ftl.ignore_message")}

+ +
+

${message("templates.reset-password-email.ftl.having_trouble_clicking_button")}

+

${reset_password_url}

+
+
+
+ + + + + + +
+
 
+
+ +
+
+ + + + + + +
+
 
+
+ + + + + + +
+ + + + + + +
    +   alfresco.com
+ ${message("templates.generic-email.ftl.contact_us")} + © ${date?string["yyyy"]} Alfresco Software, Inc. + ${message("templates.generic-email.ftl.copy_right")}
+ Bridge Ave, The Place Maidenhead SL6 1AF United Kingdom
+ 1825 S Grant St, Suite 900 San Mateo, CA 94402 USA
+
+ + + + + + +
+
 
+
+ +
+
+ + + + + + + +
{{my.Litmus_Code}}
+ + \ No newline at end of file diff --git a/config/alfresco/workflow/reset-password-workflow-model.xml b/config/alfresco/workflow/reset-password-workflow-model.xml new file mode 100644 index 0000000000..ab55d7b2ad --- /dev/null +++ b/config/alfresco/workflow/reset-password-workflow-model.xml @@ -0,0 +1,64 @@ + + + + + + + + + + + + + + + + + + bpm:startTask + + resetpasswordwf:resetPasswordInitialProperties + + + + + bpm:workflowTask + + resetpasswordwf:resetPasswordInitialProperties + + + + + bpm:workflowTask + + resetpasswordwf:resetPasswordInitialProperties + + + + + bpm:workflowTask + + resetpasswordwf:resetPasswordInitialProperties + + + + + + + + + d:text + + + d:text + + + d:text + + + d:text + + + + + \ No newline at end of file diff --git a/config/alfresco/workflow/reset-password_processdefinition.bpmn20.xml b/config/alfresco/workflow/reset-password_processdefinition.bpmn20.xml new file mode 100644 index 0000000000..6273f89651 --- /dev/null +++ b/config/alfresco/workflow/reset-password_processdefinition.bpmn20.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + ${resetpasswordwf_endTimer} + + + + + + + + + + + + + + + + diff --git a/config/alfresco/workflow/workflow-messages.properties b/config/alfresco/workflow/workflow-messages.properties index 7ff1ad8b06..a9047101f0 100644 --- a/config/alfresco/workflow/workflow-messages.properties +++ b/config/alfresco/workflow/workflow-messages.properties @@ -146,3 +146,7 @@ workflowtask.outcome.Approve=Approved workflowtask.outcome.Reject=Rejected workflowtask.already.done.error=This task has already been completed and is no longer editable. + +# Reset password Task Definitions +resetpasswordwf_resetpassword.resetpassword.workflow.title=Request Password Reset. +resetpasswordwf_resetpassword.resetpassword.workflow.description=Used to request a password reset for a user's own login. \ No newline at end of file diff --git a/source/java/org/alfresco/repo/security/authentication/ResetPasswordService.java b/source/java/org/alfresco/repo/security/authentication/ResetPasswordService.java new file mode 100644 index 0000000000..9f90a4ef31 --- /dev/null +++ b/source/java/org/alfresco/repo/security/authentication/ResetPasswordService.java @@ -0,0 +1,92 @@ +/* + * #%L + * Alfresco Remote API + * %% + * Copyright (C) 2005 - 2016 Alfresco Software Limited + * %% + * This file is part of the Alfresco software. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + * #L% + */ + +package org.alfresco.repo.security.authentication; + +import org.activiti.engine.delegate.DelegateExecution; +import org.alfresco.repo.client.config.ClientAppConfig.ClientApp; +import org.alfresco.repo.client.config.ClientAppNotFoundException; +import org.alfresco.repo.security.authentication.ResetPasswordServiceImpl.ResetPasswordDetails; +import org.alfresco.repo.security.authentication.ResetPasswordServiceImpl.ResetPasswordEmailDetails; + +/** + * Reset password service. + * + * @author Jamal Kaabi-Mofrad + * @since 5.2.1 + */ +public interface ResetPasswordService +{ + /** + * Request password reset (starts the workflow). + * + * @param userId the user id + * @param clientName the client app name (used to lookup the client that is registered to send emails so that + * client's specific configuration could be used.) + */ + void requestReset(String userId, String clientName); + + /** + * Validates the request reset password workflow and updates the workflow. + * + * @param resetDetails the {@code ResetPasswordDetails} object + */ + void resetPassword(ResetPasswordDetails resetDetails); + + /** + * Sends reset password email. + * + * @param execution the {@code DelegateExecution} object (is provided when a user requests password reset) + * @param fallbackEmailTemplatePath the class path of the fallback email template (request reset password email) + * @param emailSubject the email subject key + */ + void sendResetPasswordEmail(DelegateExecution execution, String fallbackEmailTemplatePath, String emailSubject); + + /** + * Updates the user's new password. + * + * @param execution the {@code DelegateExecution} object + * @param fallbackEmailTemplatePath the class path of the fallback email template (confirmation email) + * @param emailSubject the email subject key + */ + void performResetPassword(DelegateExecution execution, String fallbackEmailTemplatePath, String emailSubject); + + /** + * Sends an email. + * + * @param emailDetails the {@code ResetPasswordEmailDetails} object + */ + void sendEmail(ResetPasswordEmailDetails emailDetails); + + /** + * Gets the registered client. + * + * @param clientName the client name + * @return {@code ClientApp} object + * @throws ClientAppNotFoundException if no {@code ClientApp} is found with the given name + */ + ClientApp getClientAppConfig(String clientName); +} diff --git a/source/java/org/alfresco/repo/security/authentication/ResetPasswordServiceImpl.java b/source/java/org/alfresco/repo/security/authentication/ResetPasswordServiceImpl.java new file mode 100644 index 0000000000..38189efb83 --- /dev/null +++ b/source/java/org/alfresco/repo/security/authentication/ResetPasswordServiceImpl.java @@ -0,0 +1,776 @@ +/* + * #%L + * Alfresco Remote API + * %% + * Copyright (C) 2005 - 2017 Alfresco Software Limited + * %% + * This file is part of the Alfresco software. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + * #L% + */ + +package org.alfresco.repo.security.authentication; + +import org.activiti.engine.HistoryService; +import org.activiti.engine.TaskService; +import org.activiti.engine.delegate.DelegateExecution; +import org.activiti.engine.task.Task; +import org.alfresco.error.AlfrescoRuntimeException; +import org.alfresco.model.ContentModel; +import org.alfresco.repo.action.executer.MailActionExecuter; +import org.alfresco.repo.admin.SysAdminParams; +import org.alfresco.repo.client.config.ClientAppConfig; +import org.alfresco.repo.client.config.ClientAppConfig.ClientApp; +import org.alfresco.repo.client.config.ClientAppNotFoundException; +import org.alfresco.repo.workflow.BPMEngineRegistry; +import org.alfresco.repo.workflow.WorkflowModel; +import org.alfresco.repo.workflow.WorkflowModelResetPassword; +import org.alfresco.repo.workflow.activiti.ActivitiConstants; +import org.alfresco.service.cmr.action.Action; +import org.alfresco.service.cmr.action.ActionService; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.security.MutableAuthenticationService; +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.WorkflowInstance; +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.cmr.workflow.WorkflowTaskQuery; +import org.alfresco.service.namespace.QName; +import org.alfresco.util.EmailHelper; +import org.alfresco.util.GUID; +import org.alfresco.util.ParameterCheck; +import org.alfresco.util.PropertyCheck; +import org.alfresco.util.UrlUtil; +import org.apache.commons.lang.StringUtils; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.extensions.surf.util.I18NUtil; +import org.springframework.extensions.webscripts.WebScriptException; + +import java.io.Serializable; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; + +/** + * Reset password implementation based on workflow. + * + * @author Jamal Kaabi-Mofrad + * @since 5.2.1 + */ +public class ResetPasswordServiceImpl implements ResetPasswordService +{ + private static final Log LOGGER = LogFactory.getLog(ResetPasswordServiceImpl.class); + + private static final String TIMER_END = "PT1H"; + private static final String WORKFLOW_DESCRIPTION_KEY = "resetpasswordwf_resetpassword.resetpassword.workflow.description"; + private static final String FTL_TEMPLATE_ASSETS_URL = "template_assets_url"; + private static final String FTL_RESET_PASSWORD_URL = "reset_password_url"; + private static final String FTL_USER_NAME = "userName"; + + private WorkflowService workflowService; + private HistoryService activitiHistoryService; + private ActionService actionService; + private PersonService personService; + private NodeService nodeService; + private SysAdminParams sysAdminParams; + private MutableAuthenticationService authenticationService; + private TaskService activitiTaskService; + private EmailHelper emailHelper; + private ClientAppConfig clientAppConfig; + private String timerEnd = TIMER_END; + private String defaultEmailSender; + + public void setWorkflowService(WorkflowService workflowService) + { + this.workflowService = workflowService; + } + + public void setActivitiHistoryService(HistoryService activitiHistoryService) + { + this.activitiHistoryService = activitiHistoryService; + } + + public void setActionService(ActionService actionService) + { + this.actionService = actionService; + } + + public void setPersonService(PersonService personService) + { + this.personService = personService; + } + + public void setNodeService(NodeService nodeService) + { + this.nodeService = nodeService; + } + + public void setSysAdminParams(SysAdminParams sysAdminParams) + { + this.sysAdminParams = sysAdminParams; + } + + public void setAuthenticationService(MutableAuthenticationService authenticationService) + { + this.authenticationService = authenticationService; + } + + public void setActivitiTaskService(TaskService activitiTaskService) + { + this.activitiTaskService = activitiTaskService; + } + + public void setEmailHelper(EmailHelper emailHelper) + { + this.emailHelper = emailHelper; + } + + public void setClientAppConfig(ClientAppConfig clientAppConfig) + { + this.clientAppConfig = clientAppConfig; + } + + public void setTimerEnd(String timerEnd) + { + if (StringUtils.isNotEmpty(timerEnd)) + { + this.timerEnd = timerEnd; + } + } + + public void setDefaultEmailSender(String defaultEmailSender) + { + this.defaultEmailSender = defaultEmailSender; + } + + public void init() + { + PropertyCheck.mandatory(this, "workflowService", workflowService); + PropertyCheck.mandatory(this, "activitiHistoryService", activitiHistoryService); + PropertyCheck.mandatory(this, "actionService", actionService); + PropertyCheck.mandatory(this, "personService", personService); + PropertyCheck.mandatory(this, "nodeService", nodeService); + PropertyCheck.mandatory(this, "sysAdminParams", sysAdminParams); + PropertyCheck.mandatory(this, "authenticationService", authenticationService); + PropertyCheck.mandatory(this, "activitiTaskService", activitiTaskService); + PropertyCheck.mandatory(this, "emailHelper", emailHelper); + PropertyCheck.mandatory(this, "clientAppConfig", clientAppConfig); + PropertyCheck.mandatory(this, "defaultEmailSender", defaultEmailSender); + } + + @Override + public void requestReset(String userId, String clientName) + { + ParameterCheck.mandatoryString("userId", userId); + ParameterCheck.mandatoryString("clientName", clientName); + + String userEmail = validateUserAndGetEmail(userId); + + // Get the (latest) workflow definition for reset-password. + WorkflowDefinition wfDefinition = workflowService.getDefinitionByName(WorkflowModelResetPassword.WORKFLOW_DEFINITION_NAME); + + // create workflow properties + Map props = new HashMap<>(7); + props.put(WorkflowModel.PROP_WORKFLOW_DESCRIPTION, I18NUtil.getMessage(WORKFLOW_DESCRIPTION_KEY)); + props.put(WorkflowModelResetPassword.WF_PROP_USERNAME, userId); + props.put(WorkflowModelResetPassword.WF_PROP_USER_EMAIL, userEmail); + props.put(WorkflowModelResetPassword.WF_PROP_CLIENT_NAME, clientName); + props.put(WorkflowModel.ASSOC_PACKAGE, workflowService.createPackage(null)); + + String guid = GUID.generate(); + props.put(WorkflowModelResetPassword.WF_PROP_KEY, guid); + props.put(WorkflowModelResetPassword.WF_PROP_TIMER_END, timerEnd); + + // start the workflow + WorkflowPath path = workflowService.startWorkflow(wfDefinition.getId(), props); + if (path.isActive()) + { + WorkflowTask startTask = workflowService.getStartTask(path.getInstance().getId()); + workflowService.endTask(startTask.getId(), null); + } + } + + protected String validateUserAndGetEmail(String userId) + { + if (!personService.personExists(userId)) + { + throw new ResetPasswordWorkflowInvalidUserException("User does not exist: " + userId); + } + else if (!personService.isEnabled(userId)) + { + throw new ResetPasswordWorkflowInvalidUserException("User is disabled: " + userId); + } + + NodeRef personNode = personService.getPerson(userId, false); + return (String) nodeService.getProperty(personNode, ContentModel.PROP_EMAIL); + } + + @Override + public void resetPassword(ResetPasswordDetails resetDetails) + { + ParameterCheck.mandatory("resetDetails", resetDetails); + + validateIdAndKey(resetDetails.getWorkflowId(), resetDetails.getWorkflowKey(), resetDetails.getUserId()); + + // So now we know that the workflow instance exists, is active and has the correct key. We can proceed. + WorkflowTaskQuery processTaskQuery = new WorkflowTaskQuery(); + processTaskQuery.setProcessId(resetDetails.getWorkflowId()); + List tasks = workflowService.queryTasks(processTaskQuery, false); + + if (tasks.isEmpty()) + { + throw new InvalidResetPasswordWorkflowException( + "Invalid workflow identifier: " + resetDetails.getWorkflowId() + ", " + resetDetails.getWorkflowKey()); + } + WorkflowTask task = tasks.get(0); + + // Set the provided password into the task. We will remove this after we have updated the user's authentication details. + Map props = Collections.singletonMap(WorkflowModelResetPassword.WF_PROP_PASSWORD, resetDetails.getPassword()); + + // Note the taskId as taken from the WorkflowService will include a "activiti$" prefix. + final String taskId = task.getId(); + workflowService.updateTask(taskId, props, null, null); + workflowService.endTask(taskId, null); + + // Remove the previous task from Activiti's history - so that the password will not be in the database. + // See http://www.activiti.org/userguide/index.html#history for a description of how Activiti stores historical records of + // processes, tasks and properties. + // The activitiHistoryService does not expect the activiti$ prefix. + final String activitiTaskId = taskId.replace("activiti$", ""); + activitiHistoryService.deleteHistoricTaskInstance(activitiTaskId); + + if (LOGGER.isDebugEnabled()) + { + LOGGER.debug("Deleting historical task for security reasons " + activitiTaskId); + } + } + + /** + * This method ensures that the id refers to an in-progress workflow and that the key matches + * that stored in the workflow. + * + * @throws WebScriptException a 404 if any of the above is not true. + */ + private void validateIdAndKey(String id, String key, String userId) + { + WorkflowInstance workflowInstance = null; + try + { + workflowInstance = workflowService.getWorkflowById(id); + } + catch (WorkflowException ignored) + { + // Intentionally empty. + } + + if (workflowInstance == null) + { + if (LOGGER.isDebugEnabled()) + { + LOGGER.debug("The reset password workflow instance with the id [" + id + "] is not found."); + } + throw new ResetPasswordWorkflowNotFoundException("Reset Password cannot be linked to an ongoing secure process."); + } + + String recoveredKey; + String username; + if (workflowInstance.isActive()) + { + // If the workflow is active we will be able to read the path properties. + Map pathProps = workflowService.getPathProperties(id); + + username = (String) pathProps.get(WorkflowModelResetPassword.WF_PROP_USERNAME); + recoveredKey = (String) pathProps.get(WorkflowModelResetPassword.WF_PROP_KEY); + } + else + { + if (LOGGER.isDebugEnabled()) + { + LOGGER.debug("The reset password workflow instance with the id [" + id + "] is not active."); + } + throw new InvalidResetPasswordWorkflowException("Reset Password cannot be linked to an ongoing secure process."); + } + if (username == null || recoveredKey == null || !recoveredKey.equals(key)) + { + if (LOGGER.isDebugEnabled()) + { + if (username == null) + { + LOGGER.debug("The recovered user name is null for the reset password workflow instance with the id [" + id + "]"); + } + else if (recoveredKey == null) + { + LOGGER.debug("The recovered key is null for the reset password workflow instance with the id [" + id + "]"); + } + else + { + LOGGER.debug("The recovered key [" + recoveredKey + "] does not match the given workflow key [" + key + + "] for the reset password workflow instance with the id [" + id + "]"); + } + } + throw new InvalidResetPasswordWorkflowException("Reset Password cannot be linked to an ongoing secure process."); + } + else if (!username.equals(userId)) + { + if (LOGGER.isDebugEnabled()) + { + LOGGER.debug("The given user id [" + userId + "] does not match the person's user id [" + username + + "] who requested the password reset."); + } + throw new InvalidResetPasswordWorkflowException("Reset Password cannot be linked to an ongoing secure process."); + } + } + + @Override + public ClientApp getClientAppConfig(String clientName) + { + ParameterCheck.mandatoryString("clientName", clientName); + + ClientApp clientApp = clientAppConfig.getClient(clientName); + if (clientApp == null) + { + throw new ClientAppNotFoundException("Client was not found [" + clientName + "]"); + } + return clientApp; + } + + + @Override + public void sendResetPasswordEmail(DelegateExecution execution, String fallbackEmailTemplatePath, String emailSubject) + { + Map variables = execution.getVariables(); + final String userName = (String) variables.get(WorkflowModelResetPassword.WF_PROP_USERNAME_ACTIVITI); + final String toEmail = (String) variables.get(WorkflowModelResetPassword.WF_PROP_USER_EMAIL_ACTIVITI); + final String clientName = (String) variables.get(WorkflowModelResetPassword.WF_PROP_CLIENT_NAME_ACTIVITI); + final String key = (String) variables.get(WorkflowModelResetPassword.WF_PROP_KEY_ACTIVITI); + final String id = execution.getProcessInstanceId(); + + final ClientApp clientApp = getClientAppConfig(clientName); + Map emailTemplateModel = Collections.singletonMap(FTL_RESET_PASSWORD_URL, + createResetPasswordUrl(clientApp, id, key)); + + final String templatePath = emailHelper.getEmailTemplate(clientName, + getResetPasswordEmailTemplate(clientApp), + fallbackEmailTemplatePath); + + ResetPasswordEmailDetails emailRequest = new ResetPasswordEmailDetails() + .setUserName(userName) + .setUserEmail(toEmail) + .setTemplatePath(templatePath) + .setTemplateAssetsUrl(clientApp.getTemplateAssetsUrl()) + .setEmailSubject(emailSubject) + .setTemplateModel(emailTemplateModel); + + sendEmail(emailRequest); + } + + @Override + public void performResetPassword(DelegateExecution execution, String fallbackEmailTemplatePath, String emailSubject) + { + // This method chooses to take a rather indirect route to access the password value. + // This is for security reasons. We do not want to store the password in the Activiti DB. + + // We can get the username from the execution (process scope). + Map variables = execution.getVariables(); + final String userName = (String) variables.get(WorkflowModelResetPassword.WF_PROP_USERNAME_ACTIVITI); + final String userEmail = (String) variables.get(WorkflowModelResetPassword.WF_PROP_USER_EMAIL_ACTIVITI); + final String clientName = (String) variables.get(WorkflowModelResetPassword.WF_PROP_CLIENT_NAME_ACTIVITI); + + // But we cannot get the password from the execution as we have intentionally not stored the password there. + // Instead we recover the password from the specific task in which it was set. + List activitiTasks = activitiTaskService.createTaskQuery().taskDefinitionKey(WorkflowModelResetPassword.TASK_RESET_PASSWORD) + .processInstanceId(execution.getProcessInstanceId()).list(); + if (activitiTasks.size() != 1) + { + throw new ResetPasswordWorkflowException("Unexpected count of task instances: " + activitiTasks.size()); + } + Task activitiTask = activitiTasks.get(0); + String activitiTaskId = activitiTask.getId(); + final String password = (String) activitiTaskService.getVariable(activitiTaskId, WorkflowModelResetPassword.WF_PROP_PASSWORD_ACTIVITI); + + if (LOGGER.isDebugEnabled()) + { + LOGGER.debug("Retrieved new password from task " + activitiTaskId); + } + + ParameterCheck.mandatoryString(WorkflowModelResetPassword.WF_PROP_USERNAME_ACTIVITI, userName); + ParameterCheck.mandatoryString(WorkflowModelResetPassword.WF_PROP_PASSWORD_ACTIVITI, password); + + if (LOGGER.isDebugEnabled()) + { + LOGGER.debug("Changing password for " + userName); + // Don't LOG the password. :) + } + + this.authenticationService.setAuthentication(userName, password.toCharArray()); + + // Now notify the user + final ClientApp clientApp = getClientAppConfig(clientName); + Map emailTemplateModel = Collections.singletonMap(FTL_USER_NAME, userName); + + final String templatePath = emailHelper.getEmailTemplate(clientName, + getConfirmResetPasswordEmailTemplate(clientApp), + fallbackEmailTemplatePath); + + ResetPasswordEmailDetails emailRequest = new ResetPasswordEmailDetails() + .setUserName(userName) + .setUserEmail(userEmail) + .setTemplatePath(templatePath) + .setTemplateAssetsUrl(clientApp.getTemplateAssetsUrl()) + .setEmailSubject(emailSubject) + .setTemplateModel(emailTemplateModel); + + sendEmail(emailRequest); + } + + @Override + public void sendEmail(ResetPasswordEmailDetails emailRequest) + { + // Prepare the email + Map templateModel = new HashMap<>(); + // Replace '${shareUrl}' placeholder if it does exist. + final String templateAssetsUrl = getUrl(emailRequest.getTemplateAssetsUrl(), ClientAppConfig.PROP_TEMPLATE_ASSETS_URL); + templateModel.put(FTL_TEMPLATE_ASSETS_URL, templateAssetsUrl); + if (emailRequest.getTemplateModel() != null) + { + templateModel.putAll(emailRequest.getTemplateModel()); + } + + Map actionParams = new HashMap<>(8); + String fromEmail = emailRequest.getFromEmail(); + if(StringUtils.isEmpty(fromEmail)) + { + fromEmail = this.defaultEmailSender; + } + actionParams.put(MailActionExecuter.PARAM_FROM, fromEmail); + actionParams.put(MailActionExecuter.PARAM_TO, emailRequest.getUserEmail()); + actionParams.put(MailActionExecuter.PARAM_SUBJECT, emailRequest.getEmailSubject()); + // Pick the template + actionParams.put(MailActionExecuter.PARAM_TEMPLATE, emailRequest.getTemplatePath()); + actionParams.put(MailActionExecuter.PARAM_TEMPLATE_MODEL, (Serializable) templateModel); + + final Locale locale = emailHelper.getUserLocaleOrDefault(emailRequest.getUserName()); + actionParams.put(MailActionExecuter.PARAM_LOCALE, locale); + + actionParams.put(MailActionExecuter.PARAM_IGNORE_SEND_FAILURE, true); + actionParams.put(MailActionExecuter.PARAM_IGNORE_SEND_FAILURE, emailRequest.ignoreSendFailure); + // Now send the email + Action mailAction = actionService.createAction(MailActionExecuter.NAME, actionParams); + actionService.executeAction(mailAction, null, false, true); + } + + private String getUrl(String url, String propName) + { + if (url == null) + { + LOGGER.warn("The url for the property [" + propName + "] is not configured."); + return ""; + } + + if (url.endsWith("/")) + { + url = url.substring(0, url.length() - 1); + } + return UrlUtil.replaceShareUrlPlaceholder(url, sysAdminParams); + } + + protected String getResetPasswordEmailTemplate(ClientApp clientApp) + { + return clientApp.getProperty("requestResetPasswordTemplatePath"); + } + + protected String getConfirmResetPasswordEmailTemplate(ClientApp clientApp) + { + return clientApp.getProperty("confirmResetPasswordTemplatePath"); + } + + /** + * This method creates a URL for the 'reset password' link which appears in the email + */ + protected String createResetPasswordUrl(ClientApp clientApp, final String id, final String key) + { + StringBuilder sb = new StringBuilder(100); + + String pageUrl = clientApp.getProperty("resetPasswordPageUrl"); + if (StringUtils.isEmpty(pageUrl)) + { + sb.append(UrlUtil.getShareUrl(sysAdminParams)); + + LOGGER.warn("'resetPasswordPageUrl' property is not set for the client [" + clientApp.getName() + + "]. The default base url of Share will be used [" + sb.toString() + "]"); + } + else + { + // We pass an empty string as we know that the pageUrl is not null + sb.append(getUrl(pageUrl, "")); + } + + sb.append("?key=").append(key) + .append("&id=").append(BPMEngineRegistry.createGlobalId(ActivitiConstants.ENGINE_ID, id)); + + return sb.toString(); + } + + /** + * @author Jamal Kaabi-Mofrad + */ + public static class ResetPasswordDetails + { + private String userId; + private String password; + private String workflowId; + private String workflowKey; + + public String getUserId() + { + return userId; + } + + public ResetPasswordDetails setUserId(String userId) + { + this.userId = userId; + return this; + } + + public String getPassword() + { + return password; + } + + public ResetPasswordDetails setPassword(String password) + { + this.password = password; + return this; + } + + public String getWorkflowId() + { + return workflowId; + } + + public ResetPasswordDetails setWorkflowId(String workflowId) + { + this.workflowId = workflowId; + return this; + } + + public String getWorkflowKey() + { + return workflowKey; + } + + public ResetPasswordDetails setWorkflowKey(String workflowKey) + { + this.workflowKey = workflowKey; + return this; + } + + @Override + public String toString() + { + final StringBuilder sb = new StringBuilder(100); + sb.append("ResetPasswordDetails [userId=").append(userId) + .append(", workflowId=").append(workflowId) + .append(", workflowKey=").append(workflowKey) + .append(']'); + return sb.toString(); + } + } + + /** + * @author Jamal Kaabi-Mofrad + */ + public static class ResetPasswordEmailDetails + { + private String userName; + private String userEmail; + private String fromEmail; + private String templatePath; + private String templateAssetsUrl; + private Map templateModel; + private String emailSubject; + private boolean ignoreSendFailure = true; + + public String getUserName() + { + return userName; + } + + public ResetPasswordEmailDetails setUserName(String userName) + { + this.userName = userName; + return this; + } + + public String getUserEmail() + { + return userEmail; + } + + public ResetPasswordEmailDetails setUserEmail(String userEmail) + { + this.userEmail = userEmail; + return this; + } + + public String getFromEmail() + { + return fromEmail; + } + + public ResetPasswordEmailDetails setFromEmail(String fromEmail) + { + this.fromEmail = fromEmail; + return this; + } + + public String getTemplatePath() + { + return templatePath; + } + + public ResetPasswordEmailDetails setTemplatePath(String templatePath) + { + this.templatePath = templatePath; + return this; + } + + public String getTemplateAssetsUrl() + { + return templateAssetsUrl; + } + + public ResetPasswordEmailDetails setTemplateAssetsUrl(String templateAssetsUrl) + { + this.templateAssetsUrl = templateAssetsUrl; + return this; + } + + public Map getTemplateModel() + { + return templateModel; + } + + public ResetPasswordEmailDetails setTemplateModel(Map templateModel) + { + this.templateModel = templateModel; + return this; + } + + public String getEmailSubject() + { + return emailSubject; + } + + public ResetPasswordEmailDetails setEmailSubject(String emailSubject) + { + this.emailSubject = emailSubject; + return this; + } + + public boolean isIgnoreSendFailure() + { + return ignoreSendFailure; + } + + public ResetPasswordEmailDetails setIgnoreSendFailure(boolean ignoreSendFailure) + { + this.ignoreSendFailure = ignoreSendFailure; + return this; + } + + @Override + public String toString() + { + final StringBuilder sb = new StringBuilder(250); + sb.append("ResetPasswordEmailDetails [userName=").append(userName) + .append(", userEmail=").append(userEmail) + .append(", fromEmail=").append(fromEmail) + .append(", templatePath=").append(templatePath) + .append(", templateAssetsUrl=").append(templateAssetsUrl) + .append(", templateModel=").append(templateModel) + .append(", emailSubject=").append(emailSubject) + .append(", ignoreSendFailure=").append(ignoreSendFailure) + .append(']'); + return sb.toString(); + } + } + + /** + * @author Jamal Kaabi-Mofrad + * @since 5.2.1 + */ + public static class ResetPasswordWorkflowException extends AlfrescoRuntimeException + { + private static final long serialVersionUID = -694208478609278943L; + + public ResetPasswordWorkflowException(String msgId) + { + super(msgId); + } + } + + /** + * @author Jamal Kaabi-Mofrad + * @since 5.2.1 + */ + public static class ResetPasswordWorkflowNotFoundException extends ResetPasswordWorkflowException + { + private static final long serialVersionUID = -7492264073778098895L; + + public ResetPasswordWorkflowNotFoundException(String msgId) + { + super(msgId); + } + } + + /** + * @author Jamal Kaabi-Mofrad + * @since 5.2.1 + */ + public static class InvalidResetPasswordWorkflowException extends ResetPasswordWorkflowException + { + private static final long serialVersionUID = -4685359036247580984L; + + public InvalidResetPasswordWorkflowException(String msgId) + { + super(msgId); + } + } + + /** + * @author Jamal Kaabi-Mofrad + * @since 5.2.1 + */ + public static class ResetPasswordWorkflowInvalidUserException extends ResetPasswordWorkflowException + { + private static final long serialVersionUID = -6524046975575636256L; + + public ResetPasswordWorkflowInvalidUserException(String msgId) + { + super(msgId); + } + } +} diff --git a/source/java/org/alfresco/repo/security/authentication/activiti/AbstractResetPasswordDelegate.java b/source/java/org/alfresco/repo/security/authentication/activiti/AbstractResetPasswordDelegate.java new file mode 100644 index 0000000000..8028d07526 --- /dev/null +++ b/source/java/org/alfresco/repo/security/authentication/activiti/AbstractResetPasswordDelegate.java @@ -0,0 +1,44 @@ +/* + * #%L + * Alfresco Remote API + * %% + * Copyright (C) 2005 - 2017 Alfresco Software Limited + * %% + * This file is part of the Alfresco software. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + * #L% + */ + +package org.alfresco.repo.security.authentication.activiti; + +import org.alfresco.repo.security.authentication.ResetPasswordService; +import org.alfresco.repo.workflow.activiti.BaseJavaDelegate; + +/** + * @author Jamal Kaabi-Mofrad + * @since 5.2.1 + */ +public abstract class AbstractResetPasswordDelegate extends BaseJavaDelegate +{ + protected ResetPasswordService resetPasswordService; + + public void setResetPasswordService(ResetPasswordService resetPasswordService) + { + this.resetPasswordService = resetPasswordService; + } +} diff --git a/source/java/org/alfresco/repo/security/authentication/activiti/PerformResetPasswordDelegate.java b/source/java/org/alfresco/repo/security/authentication/activiti/PerformResetPasswordDelegate.java new file mode 100644 index 0000000000..722aab3f62 --- /dev/null +++ b/source/java/org/alfresco/repo/security/authentication/activiti/PerformResetPasswordDelegate.java @@ -0,0 +1,48 @@ +/* + * #%L + * Alfresco Remote API + * %% + * Copyright (C) 2005 - 2016 Alfresco Software Limited + * %% + * This file is part of the Alfresco software. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + * #L% + */ + +package org.alfresco.repo.security.authentication.activiti; + +import org.activiti.engine.delegate.DelegateExecution; +import org.activiti.engine.delegate.JavaDelegate; + +/** + * This {@link JavaDelegate activiti delegate} is executed when a user resets his/her password. + * + * @author Jamal Kaabi-Mofrad + * @since 5.2.1 + */ +public class PerformResetPasswordDelegate extends AbstractResetPasswordDelegate +{ + private static final String EMAIL_SUBJECT_KEY = "reset-password-confirmation.email.subject"; + private static final String EMAIL_TEMPLATE_PATH = "alfresco/templates/reset-password-email-templates/reset-password-confirmation-email-template.ftl"; + + @Override + public void execute(DelegateExecution execution) throws Exception + { + resetPasswordService.performResetPassword(execution, EMAIL_TEMPLATE_PATH, EMAIL_SUBJECT_KEY); + } +} diff --git a/source/java/org/alfresco/repo/security/authentication/activiti/SendResetPasswordEmailDelegate.java b/source/java/org/alfresco/repo/security/authentication/activiti/SendResetPasswordEmailDelegate.java new file mode 100644 index 0000000000..5948f10e50 --- /dev/null +++ b/source/java/org/alfresco/repo/security/authentication/activiti/SendResetPasswordEmailDelegate.java @@ -0,0 +1,48 @@ +/* + * #%L + * Alfresco Remote API + * %% + * Copyright (C) 2005 - 2016 Alfresco Software Limited + * %% + * This file is part of the Alfresco software. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + * #L% + */ + +package org.alfresco.repo.security.authentication.activiti; + +import org.activiti.engine.delegate.DelegateExecution; +import org.activiti.engine.delegate.JavaDelegate; + +/** + * This {@link JavaDelegate activiti delegate} is executed when a user request password reset. + * + * @author Jamal Kaabi-Mofrad + * @since 5.2.1 + */ +public class SendResetPasswordEmailDelegate extends AbstractResetPasswordDelegate +{ + private static final String EMAIL_SUBJECT_KEY = "reset-password-request.email.subject"; + private static final String EMAIL_TEMPLATE_PATH = "alfresco/templates/reset-password-email-templates/reset-password-email-template.ftl"; + + @Override + public void execute(DelegateExecution execution) throws Exception + { + resetPasswordService.sendResetPasswordEmail(execution, EMAIL_TEMPLATE_PATH, EMAIL_SUBJECT_KEY); + } +} diff --git a/source/java/org/alfresco/repo/workflow/WorkflowModelResetPassword.java b/source/java/org/alfresco/repo/workflow/WorkflowModelResetPassword.java new file mode 100644 index 0000000000..c2795b06f0 --- /dev/null +++ b/source/java/org/alfresco/repo/workflow/WorkflowModelResetPassword.java @@ -0,0 +1,62 @@ +/* + * #%L + * Alfresco Remote API + * %% + * Copyright (C) 2005 - 2016 Alfresco Software Limited + * %% + * This file is part of the Alfresco software. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + * #L% + */ + +package org.alfresco.repo.workflow; + +import org.alfresco.service.namespace.QName; + +/** + * @author Jamal Kaabi-Mofrad + * @since 5.2.1 + */ +public interface WorkflowModelResetPassword +{ + // namespace + String NAMESPACE_URI = "http://www.alfresco.org/model/workflow/resetpassword/1.0"; + + // process name + String WORKFLOW_DEFINITION_NAME = "activiti$resetPassword"; + + // task names + String TASK_RESET_PASSWORD = "resetPasswordTask"; + + // timers + QName WF_PROP_TIMER_END = QName.createQName(NAMESPACE_URI, "endTimer"); + + // workflow properties + QName WF_PROP_USERNAME = QName.createQName(NAMESPACE_URI, "userName"); + QName WF_PROP_USER_EMAIL = QName.createQName(NAMESPACE_URI, "userEmail"); + QName WF_PROP_KEY = QName.createQName(NAMESPACE_URI, "key"); + QName WF_PROP_PASSWORD = QName.createQName(NAMESPACE_URI, "password"); + QName WF_PROP_CLIENT_NAME = QName.createQName(NAMESPACE_URI, "clientName"); + + // workflow execution context variable names + String WF_PROP_USERNAME_ACTIVITI = "resetpasswordwf_userName"; + String WF_PROP_USER_EMAIL_ACTIVITI = "resetpasswordwf_userEmail"; + String WF_PROP_KEY_ACTIVITI = "resetpasswordwf_key"; + String WF_PROP_PASSWORD_ACTIVITI = "resetpasswordwf_password"; + String WF_PROP_CLIENT_NAME_ACTIVITI = "resetpasswordwf_clientName"; +}