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
This commit is contained in:
Jamal Kaabi-Mofrad
2017-03-16 19:39:24 +00:00
parent c9c30849f9
commit b96832b0fd
17 changed files with 2172 additions and 4 deletions

View File

@@ -42,4 +42,5 @@
<import resource="classpath*:alfresco/repo-clients-apps-context.xml" />
<import resource="classpath*:alfresco/domain/*-context.xml" />
<import resource="classpath*:alfresco/smartfolder-context.xml" />
<import resource="classpath*:alfresco/reset-password-context.xml" />
</beans>

View File

@@ -215,6 +215,14 @@
<prop key="mimetype">text/xml</prop>
<prop key="redeploy">false</prop>
</props>
<!-- Activiti reset password workflow definition -->
<props>
<prop key="engineId">activiti</prop>
<prop key="location">alfresco/workflow/reset-password_processdefinition.bpmn20.xml</prop>
<prop key="mimetype">text/xml</prop>
<prop key="redeploy">false</prop>
</props>
</list>
</property>
<property name="models">
@@ -223,6 +231,7 @@
<value>alfresco/workflow/invitation-nominated-workflow-model.xml</value>
<value>alfresco/workflow/invitation-moderated-workflow-model.xml</value>
<value>alfresco/workflow/publishingWorkflowModel.xml</value>
<value>alfresco/workflow/reset-password-workflow-model.xml</value>
</list>
</property>
<property name="labels">

View File

@@ -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
# 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=

View File

@@ -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.

View File

@@ -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

View File

@@ -0,0 +1,40 @@
<?xml version='1.0' encoding='UTF-8'?>
<!DOCTYPE beans PUBLIC '-//SPRING//DTD BEAN//EN' 'http://www.springframework.org/dtd/spring-beans.dtd'>
<beans>
<bean id="resetPasswordService" class="org.alfresco.repo.security.authentication.ResetPasswordServiceImpl">
<property name="workflowService" ref="WorkflowService" />
<property name="activitiHistoryService" ref="activitiHistoryService" />
<property name="actionService" ref="actionService" />
<property name="nodeService" ref="nodeService" />
<property name="personService" ref="personService" />
<property name="sysAdminParams" ref="sysAdminParams"/>
<property name="activitiTaskService" ref="activitiTaskService" />
<property name="authenticationService" ref="AuthenticationService" />
<property name="clientAppConfig" ref="clientAppConfig" />
<property name="emailHelper" ref="emailHelper" />
<property name="timerEnd" value="${system.reset-password.endTimer}" />
<property name="defaultEmailSender" value="${system.email.sender.default}" />
</bean>
<bean id="resetPaswwordResourceBundles" class="org.alfresco.i18n.ResourceBundleBootstrapComponent">
<property name="resourceBundles">
<list>
<value>alfresco.messages.reset-password-messages</value>
</list>
</property>
</bean>
<bean id="baseResetPasswordDelegate" parent="baseJavaDelegate" abstract="true">
<property name="resetPasswordService" ref="resetPasswordService"/>
</bean>
<bean id="SendResetPasswordEmailDelegate" parent="baseResetPasswordDelegate"
class="org.alfresco.repo.security.authentication.activiti.SendResetPasswordEmailDelegate">
</bean>
<bean id="PerformResetPasswordDelegate" parent="baseResetPasswordDelegate"
class="org.alfresco.repo.security.authentication.activiti.PerformResetPasswordDelegate">
</bean>
</beans>

View File

@@ -0,0 +1,450 @@
<html>
<head>
<style type="text/css">
td {
font-family: 'Helvetica Neue', Arial, sans-serif;
}
body {
-webkit-font-smoothing: antialiased;
-webkit-text-size-adjust: none;
width: 100% ! important;
height: 100% !important;
color: #727174;
font-weight: 400;
font-size: 18px;
margin: 0;
}
h1 {
margin: 10px 0;
}
h2 {
color: #727174;
font-weight: 600;
font-size: 22px;
}
a {
color: #0c79bf;
text-decoration: underline;
}
a.linkone {
color: #0c79bf;
text-decoration: underline;
}
.appleLinks a {
color: #0c79bf;
}
.appleLinksWhite a {
color: #0c79bf;
}
.force-full-width {
width: 100% !important;
}
.body-padding {
padding: 0 75px
}
.force-width-80 {
width: 80% !important;
}
p {
Margin-bottom: 1em;
}
.button {
text-align: center;
font-size: 18px;
font-family: sans-serif;
font-weight: 400;
-webkit-font-smoothing: antialiased;
-webkit-text-size-adjust: none;
}
.button a {
color: #ffffff;
text-decoration: none;
}
img {
max-width: 600px;
outline: none;
text-decoration: none;
-ms-interpolation-mode: bicubic;
}
a img {
border: none;
}
table {
border-collapse: collapse;
mso-table-lspace: 0pt;
mso-table-rspace: 0pt;
}
#outlook a {
padding: 0;
}
.ReadMsgBody {
width: 100%;
}
.ExternalClass {
width: 100%;
}
.backgroundTable {
margin: 0 auto;
padding: 0;
width: 100% !important;
}
table td {
border-collapse: collapse;
}
.ExternalClass * {
line-height: 115%;
}
.ExternalClass {
vertical-align: middle
}
/*]]>*/
</style>
<style type="text/css" media="screen">
@media screen and (-ms-high-contrast: active), (-ms-high-contrast: none) { {
@-ms-viewport {
width: 320px;
}
@viewport {
width: 320px;
}
}
</style>
<style type="text/css" media="screen">
@media screen {
* {
font-family: 'Helvetica Neue', 'Arial', 'sans-serif' !important;
}
.w280 {
width: 280px !important;
}
}
</style>
<style type="text/css" media="only screen and (max-width: 480px)">
@media only screen and (max-width: 480px) {
.full {
display: block;
width: 100%;
}
table[class*="w320"] {
width: 320px !important;
}
td[class*="w320"] {
width: 280px !important;
padding-left: 20px !important;
padding-right: 20px !important;
}
img[class*="w320"] {
width: 250px !important;
height: 67px !important;
}
td[class*="mobile-spacing"] {
padding-top: 10px !important;
padding-bottom: 10px !important;
}
*[class*="mobile-hide"] {
display: none !important;
}
*[class*="mobile-br"] {
font-size: 8px !important;
}
*[class*="mobile-brh"] {
font-size: 1px !important;
}
td[class*="mobile-w20"] {
width: 20px !important;
}
img[class*="mobile-w20"] {
width: 20px !important;
}
td[class*="mobile-center"] {
text-align: center !important;
}
table[class*="w100p"] {
width: 100% !important;
}
td[class*="activate-now"] {
padding-right: 0 !important;
padding-top: 20px !important;
}
td[class*="mobile-resize"] {
font-size: 22px !important;
padding-left: 15px !important;
}
}
</style>
</head>
<body offset="0" class="body" style="padding:0; margin:0; display:block; background:#f3f4f4; -webkit-text-size-adjust:none; " bgcolor="#EEEEEE">
<table align="center" cellpadding="0" cellspacing="0" width="100%" height="100%">
<tr>
<td align="center" valign="top" style="background-color:#f3f4f4; " width="100%">
<table cellspacing="0" cellpadding="0" width="600" class="w320">
<tr>
<td align="center" valign="top">
<!--Snippet Block-->
<table border="0" cellspacing="0" cellpadding="0" align="center" width="100%">
<tr>
<td>
<table border="0" cellspacing="0" cellpadding="10" width="100%" align="center">
<tbody>
<tr>
<td align="left">
<table align="center" width="100%" border="0" cellspacing="0" cellpadding="0">
<tr>
<td align="right">
<div style="display:none;font-size:1px;color:#333333;line-height:1px;max-height:0px;max-width:0px;opacity:0;overflow:hidden;">
-
</div>
</td>
</tr>
</table>
</td>
</tr>
</tbody>
</table>
</td>
</tr>
</table>
<!--Header Block-->
<table bgcolor="#FFFFFF" border="0" cellspacing="0" cellpadding="0" align="center" width="100%"
style="border-left:solid 1px #dedee4; border-right:solid 1px #dedee4; border-bottom:solid 1px #dedee4; border-top:solid 1px #dedee4; width:100%!important; min-width:100%;">
<tr style="width:100%;">
<td>
<table border="0" cellspacing="0" cellpadding="10" align="center" width="100%">
<tr>
<td align="left" valign="middle"><a style="color: #ffffff; text-decoration: none;" href=
"http://go.alfresco.com/a0100K050L0000KZjI2d00U" target="_blank"
><span style="font-family:'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 24px; color: #727174;"><img
style="border:none; display:block; border-collapse:collapse; outline:none; text-decoration:none;"
src="${template_assets_url}/Alfresco_Logo_gray.png" border="0"
alt="Alfresco" width="122" height="38"></span></a></td>
</tr>
</table>
</td>
<td align="right" bgcolor="#FFFFFF">
<table border="0" cellspacing="0" cellpadding="10" align="center" width="100%">
<tr>
<td align="right" valign="middle"><span
style="font-family:'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; font-weight:200; color: #727174;">make business flow</span>
</td>
</tr>
</table>
</td>
</tr>
</table>
<!--Break-->
<table width="100%">
<tr>
<td height="10">
<div class="mobile-br">&nbsp;</div>
</td>
</tr>
</table>
<!--Body Block 1-->
<!--Banner Block-->
<table cellspacing="0" cellpadding="0" width="100%" style="border-left:solid 1px #dedee4; border-right:solid 1px #dedee4; ">
<tr>
<td style="border-collapse:collapse; ">
<table cellspacing="0" cellpadding="0" width="100%" style="background-color:#0c79bf; border:none; ">
<tr>
<td style="border-collapse:collapse; ">
<div class="mktEditable" id="Banner Image 1"><span
style="font-family:'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 24px; color: #ffffff;"><img
src="${template_assets_url}/Product_Banner_1.jpg" alt="Alfresco Products" style="width:100%; border:none;
display:block; border-collapse:collapse; outline:none; text-decoration:none;"></span></div>
</td>
</tr>
</table>
</td>
</tr>
</table>
<!--Copy-->
<table cellspacing="0" cellpadding="0" align="center" bgcolor="#FFFFFF"
style="border-left:solid 1px #dedee4; border-right:solid 1px #dedee4; border-bottom:solid 1px #dedee4; " width="100%">
<tr align="center">
<td style="background-color:#ffffff; " align="center">
<table class="force-width-80" cellspacing="0" cellpadding="12" align="center" bgcolor="#FFFFFF" width="80%">
<tbody>
<tr>
<td align="left">
<!--Headline Text-->
<div class="mktEditable" id="Headline Text 1" align="left" style="vertical-align:middle; "><span
style="color: #727174; margin: 0px; font-family: Gotham, 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 22px; font-weight: 600; text-align: left; text-decoration: none; vertical-align:
middle;"><p>${message("templates.reset-password-confirm-email.ftl.title")}</p></span></div>
<div class="mktEditable" id="Body Text 1" align="left"><span
style="color:#727174; font-family:Gotham, 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size:18px;font-weight:400; text-align:left; text-decoration:none; -webkit-text-size-adjust:none;">
<p>${message("templates.generic-email.ftl.salutation", userName)?html}</p>
<p>${message("templates.reset-password-confirm-email.ftl.detail")}</p>
</span>
</div>
<!--Break-->
<table width="100%">
<tr>
<td height="5">
<div class="mobile-br">&nbsp;</div>
</td>
</tr>
</table>
<!--Button-->
<table style="margin:0 auto; " cellspacing="0" cellpadding="0" width="100%">
<tr>
<td style="text-align:center; margin:0 auto; ">
<div><!--[if mso]>
<v:rect xmlns:v="urn:schemas-microsoft-com:vml"
xmlns:w="urn:schemas-microsoft-com:office:word"
style="height:45px;v-text-anchor:middle;width:220px;" stroke="f"
fillcolor="#47AA2">
<w:anchorlock/>
<center>
<![endif]-->
<span style="color:#727174; font-family:Gotham, 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size:12px;font-weight:400; text-align:left; text-decoration:none; -webkit-text-size-adjust:none;">
<p>${message("templates.reset-password-confirm-email.ftl.note")}</p>
<span style="color:#727174; font-family:Gotham, 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size:12px;font-weight:400; text-align:left; text-decoration:none; -webkit-text-size-adjust:none;">
<hr>
</span>
<!--[if mso]>
</center>
</v:rect>
<![endif]--></div>
</td>
</tr>
</table>
<!--Break-->
<table width="100%">
<tr>
<td height="5">
<div class="mobile-br">&nbsp;</div>
</td>
</tr>
</table>
</td>
</tr>
</tbody>
</table>
</td>
</tr>
</table>
<!--Break-->
<table width="100%">
<tr>
<td height="10">
<div class="mobile-br">&nbsp;</div>
</td>
</tr>
</table>
<!--Footer Block-->
<table border="0" cellspacing="0" cellpadding="0" align="center" width="100%">
<tr>
<td align="center">
<table cellspacing="0" cellpadding="0" border="0" align="center">
<tr>
<td cellspacing="0" cellpadding="0" border="0" align="center"><a
style="color: #ffffff; text-decoration: none;"
href="http://go.alfresco.com/a0100K060L0000RZeI2o00U" target="_blank"><img
src="${template_assets_url}/Alfresco_Icon_Social_Twitter.png" width="23" height="23"
border="0"></a>&nbsp; <a style="color: #ffffff; text-decoration: none;"
href="http://go.alfresco.com/G6I0Zo100S0fL0UK0000020"
target="_blank"><img src="${template_assets_url}/Alfresco_Icon_Social_LinkedIn.png"
width="23" height="23" border="0"></a>&nbsp; <a
style="color:
#ffffff; text-decoration: none;" href="http://go.alfresco.com/TT2Lo00060000Ig10K00UZ0" target="_blank"><img
src="${template_assets_url}/Alfresco_Icon_Social_Facebook.png" width="23" height="23" border="0"></a>&nbsp; <a
style="color: #ffffff; text-decoration: none;"
href="http://go.alfresco.com/v00002oh0UKZL0U6I010000" target="_blank"><img
src="${template_assets_url}/Alfresco_Icon_Social_Google.png" width="23" height="23" border="0"></a></td>
<td cellspacing="0" cellpadding="0" border="0" align="center" width="10"></td>
<td cellspacing="0" cellpadding="0" border="0" align="center"><a style="color: #333333;"
href="http://go.alfresco.com/a0100K050L0000KZjI2d00U"
target="_blank"><span
style="font-family: Gotham, 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 10px; color: #727174;"><img
src="${template_assets_url}/AlfrescoGlobal_Logo_BW.png" border="0" alt="alfresco.com" width="92" height="29"><br></span></a>
</td>
</tr>
</table>
<a style="color: #0c79bf; text-decoration: underline;" href="http://www.alfresco.com/company/contact"
target="_blank"><span
style="font-family: Gotham, 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 10px; color: #0c79bf;">${message("templates.generic-email.ftl.contact_us")}</span></a>
<span style="font-family: Gotham, 'Helvetica Neue', Helvetica, Arial,
sans-serif; font-size: 10px; color: #b3b3b8;">&copy; ${date?string["yyyy"]} Alfresco Software, Inc.
${message("templates.generic-email.ftl.copy_right")}</span><br>
<span style="font-family: Gotham, 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 10px; color: #b3b3b8;">Bridge Ave, The Place Maidenhead SL6 1AF United Kingdom</span><br>
<span style="font-family: Gotham, 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 10px; color: #b3b3b8;">1825 S Grant St, Suite 900 San Mateo, CA 94402 USA</span><br>
</td>
</tr>
</table>
<!--Break-->
<table width="100%">
<tr>
<td height="10">
<div class="mobile-br">&nbsp;</div>
</td>
</tr>
</table>
</td>
</tr>
</table>
</td>
</tr>
</table>
<!--Litmus Tracking-->
<table style="display: none;">
<tbody>
<tr>
<td style="width: 0px; display: none; overflow: hidden; max-height: 0px;">{{my.Litmus_Code}}</td>
</tr>
</tbody>
</table>
</body>
</html>

View File

@@ -0,0 +1,455 @@
<html>
<head>
<style type="text/css">
td {
font-family: 'Helvetica Neue', Arial, sans-serif;
}
body {
-webkit-font-smoothing: antialiased;
-webkit-text-size-adjust: none;
width: 100% ! important;
height: 100% !important;
color: #727174;
font-weight: 400;
font-size: 18px;
margin: 0;
}
h1 {
margin: 10px 0;
}
h2 {
color: #727174;
font-weight: 600;
font-size: 22px;
}
a {
color: #0c79bf;
text-decoration: underline;
}
a.linkone {
color: #0c79bf;
text-decoration: underline;
}
.appleLinks a {
color: #0c79bf;
}
.appleLinksWhite a {
color: #0c79bf;
}
.force-full-width {
width: 100% !important;
}
.body-padding {
padding: 0 75px
}
.force-width-80 {
width: 80% !important;
}
p {
Margin-bottom: 1em;
}
.button {
text-align: center;
font-size: 18px;
font-family: sans-serif;
font-weight: 400;
-webkit-font-smoothing: antialiased;
-webkit-text-size-adjust: none;
}
.button a {
color: #ffffff;
text-decoration: none;
}
img {
max-width: 600px;
outline: none;
text-decoration: none;
-ms-interpolation-mode: bicubic;
}
a img {
border: none;
}
table {
border-collapse: collapse;
mso-table-lspace: 0pt;
mso-table-rspace: 0pt;
}
#outlook a {
padding: 0;
}
.ReadMsgBody {
width: 100%;
}
.ExternalClass {
width: 100%;
}
.backgroundTable {
margin: 0 auto;
padding: 0;
width: 100% !important;
}
table td {
border-collapse: collapse;
}
.ExternalClass * {
line-height: 115%;
}
.ExternalClass {
vertical-align: middle
}
/*]]>*/
</style>
<style type="text/css" media="screen">
@media screen and (-ms-high-contrast: active), (-ms-high-contrast: none) { {
@-ms-viewport {
width: 320px;
}
@viewport {
width: 320px;
}
}
</style>
<style type="text/css" media="screen">
@media screen {
* {
font-family: 'Helvetica Neue', 'Arial', 'sans-serif' !important;
}
.w280 {
width: 280px !important;
}
}
</style>
<style type="text/css" media="only screen and (max-width: 480px)">
@media only screen and (max-width: 480px) {
.full {
display: block;
width: 100%;
}
table[class*="w320"] {
width: 320px !important;
}
td[class*="w320"] {
width: 280px !important;
padding-left: 20px !important;
padding-right: 20px !important;
}
img[class*="w320"] {
width: 250px !important;
height: 67px !important;
}
td[class*="mobile-spacing"] {
padding-top: 10px !important;
padding-bottom: 10px !important;
}
*[class*="mobile-hide"] {
display: none !important;
}
*[class*="mobile-br"] {
font-size: 8px !important;
}
*[class*="mobile-brh"] {
font-size: 1px !important;
}
td[class*="mobile-w20"] {
width: 20px !important;
}
img[class*="mobile-w20"] {
width: 20px !important;
}
td[class*="mobile-center"] {
text-align: center !important;
}
table[class*="w100p"] {
width: 100% !important;
}
td[class*="activate-now"] {
padding-right: 0 !important;
padding-top: 20px !important;
}
td[class*="mobile-resize"] {
font-size: 22px !important;
padding-left: 15px !important;
}
}
</style>
</head>
<body offset="0" class="body" style="padding:0; margin:0; display:block; background:#f3f4f4; -webkit-text-size-adjust:none; " bgcolor="#EEEEEE">
<table align="center" cellpadding="0" cellspacing="0" width="100%" height="100%">
<tr>
<td align="center" valign="top" style="background-color:#f3f4f4; " width="100%">
<table cellspacing="0" cellpadding="0" width="600" class="w320">
<tr>
<td align="center" valign="top">
<!--Snippet Block-->
<table border="0" cellspacing="0" cellpadding="0" align="center" width="100%">
<tr>
<td>
<table border="0" cellspacing="0" cellpadding="10" width="100%" align="center">
<tbody>
<tr>
<td align="left">
<table align="center" width="100%" border="0" cellspacing="0" cellpadding="0">
<tr>
<td align="right">
<div style="display:none;font-size:1px;color:#333333;line-height:1px;max-height:0px;max-width:0px;opacity:0;overflow:hidden;">
-
</div>
</td>
</tr>
</table>
</td>
</tr>
</tbody>
</table>
</td>
</tr>
</table>
<!--Header Block-->
<table bgcolor="#FFFFFF" border="0" cellspacing="0" cellpadding="0" align="center" width="100%"
style="border-left:solid 1px #dedee4; border-right:solid 1px #dedee4; border-bottom:solid 1px #dedee4; border-top:solid 1px #dedee4; width:100%!important; min-width:100%;">
<tr style="width:100%;">
<td>
<table border="0" cellspacing="0" cellpadding="10" align="center" width="100%">
<tr>
<td align="left" valign="middle"><a style="color: #ffffff; text-decoration: none;" href=
"http://go.alfresco.com/a0100K050L0000KZjI2d00U" target="_blank"
><span style="font-family:'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 24px; color: #727174;"><img
style="border:none; display:block; border-collapse:collapse; outline:none; text-decoration:none;"
src="${template_assets_url}/Alfresco_Logo_gray.png" border="0"
alt="Alfresco" width="122" height="38"></span></a></td>
</tr>
</table>
</td>
<td align="right" bgcolor="#FFFFFF">
<table border="0" cellspacing="0" cellpadding="10" align="center" width="100%">
<tr>
<td align="right" valign="middle"><span
style="font-family:'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 16px; font-weight:200; color: #727174;">make business flow</span>
</td>
</tr>
</table>
</td>
</tr>
</table>
<!--Break-->
<table width="100%">
<tr>
<td height="10">
<div class="mobile-br">&nbsp;</div>
</td>
</tr>
</table>
<!--Body Block 1-->
<!--Banner Block-->
<table cellspacing="0" cellpadding="0" width="100%" style="border-left:solid 1px #dedee4; border-right:solid 1px #dedee4; ">
<tr>
<td style="border-collapse:collapse; ">
<table cellspacing="0" cellpadding="0" width="100%" style="background-color:#0c79bf; border:none; ">
<tr>
<td style="border-collapse:collapse; ">
<div class="mktEditable" id="Banner Image 1"><span
style="font-family:'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 24px; color: #ffffff;"><img
src="${template_assets_url}/Product_Banner_1.jpg" alt="Alfresco Products" style="width:100%; border:none;
display:block; border-collapse:collapse; outline:none; text-decoration:none;"></span></div>
</td>
</tr>
</table>
</td>
</tr>
</table>
<!--Copy-->
<table cellspacing="0" cellpadding="0" align="center" bgcolor="#FFFFFF"
style="border-left:solid 1px #dedee4; border-right:solid 1px #dedee4; border-bottom:solid 1px #dedee4; " width="100%">
<tr align="center">
<td style="background-color:#ffffff; " align="center">
<table class="force-width-80" cellspacing="0" cellpadding="12" align="center" bgcolor="#FFFFFF" width="80%">
<tbody>
<tr>
<td align="left">
<!--Headline Text-->
<div class="mktEditable" id="Headline Text 1" align="left" style="vertical-align:middle; "><span
style="color: #727174; margin: 0px; font-family: Gotham, 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 22px; font-weight: 600; text-align: left; text-decoration: none; vertical-align:
middle;"><p>${message("templates.reset-password-email.ftl.title")}</p></span></div>
<div class="mktEditable" id="Body Text 1" align="left">
<span style="color:#727174; font-family:Gotham, 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size:18px;font-weight:400; text-align:left; text-decoration:none; -webkit-text-size-adjust:none;">
<p>${message("templates.reset-password-email.ftl.detail")}</p>
</span>
</div>
<!--Break-->
<table width="100%">
<tr>
<td height="5">
<div class="mobile-br">&nbsp;</div>
</td>
</tr>
</table>
<!--Button-->
<table style="margin:0 auto; " cellspacing="0" cellpadding="0" width="100%">
<tr>
<td style="text-align:center; margin:0 auto; ">
<div><!--[if mso]>
<v:rect xmlns:v="urn:schemas-microsoft-com:vml"
xmlns:w="urn:schemas-microsoft-com:office:word"
style="height:45px;v-text-anchor:middle;width:220px;" stroke="f"
fillcolor="#47AA2">
<w:anchorlock/>
<center>
<![endif]-->
<div class="mktEditable" id="Button Text"><a href="${reset_password_url}" style="background-color:#47aa42; color:#ffffff; display:inline-block; font-family:Gotham, 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size:18px; font-weight:400; line-height:45px; text-align:center; text-decoration:none; width:220px;
-webkit-text-size-adjust:none;">${message("templates.reset-password-email.ftl.reset_password_button")}</a></div>
<span style="color:#727174; font-family:Gotham, 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size:12px;font-weight:400; text-align:left; text-decoration:none; -webkit-text-size-adjust:none;">
<p>${message("templates.reset-password-email.ftl.ignore_message")}</p>
<span style="color:#727174; font-family:Gotham, 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size:12px;font-weight:400; text-align:left; text-decoration:none; -webkit-text-size-adjust:none;">
<hr>
<p>${message("templates.reset-password-email.ftl.having_trouble_clicking_button")}</p>
<p><a href="${reset_password_url}">${reset_password_url}</a></p>
</span>
<!--[if mso]>
</center>
</v:rect>
<![endif]--></div>
</td>
</tr>
</table>
<!--Break-->
<table width="100%">
<tr>
<td height="5">
<div class="mobile-br">&nbsp;</div>
</td>
</tr>
</table>
</td>
</tr>
</tbody>
</table>
</td>
</tr>
</table>
<!--Break-->
<table width="100%">
<tr>
<td height="10">
<div class="mobile-br">&nbsp;</div>
</td>
</tr>
</table>
<!--Footer Block-->
<table border="0" cellspacing="0" cellpadding="0" align="center" width="100%">
<tr>
<td align="center">
<table cellspacing="0" cellpadding="0" border="0" align="center">
<tr>
<td cellspacing="0" cellpadding="0" border="0" align="center"><a
style="color: #ffffff; text-decoration: none;"
href="http://go.alfresco.com/a0100K060L0000RZeI2o00U" target="_blank"><img
src="${template_assets_url}/Alfresco_Icon_Social_Twitter.png" width="23" height="23"
border="0"></a>&nbsp; <a style="color: #ffffff; text-decoration: none;"
href="http://go.alfresco.com/G6I0Zo100S0fL0UK0000020"
target="_blank"><img
src="${template_assets_url}/Alfresco_Icon_Social_LinkedIn.png" width="23" height="23" border="0"></a>&nbsp;
<a style="color:
#ffffff; text-decoration: none;" href="http://go.alfresco.com/TT2Lo00060000Ig10K00UZ0" target="_blank"><img
src="${template_assets_url}/Alfresco_Icon_Social_Facebook.png" width="23" height="23"
border="0"></a>&nbsp; <a style="color: #ffffff; text-decoration: none;"
href="http://go.alfresco.com/v00002oh0UKZL0U6I010000"
target="_blank"><img
src="${template_assets_url}/Alfresco_Icon_Social_Google.png" width="23" height="23"
border="0"></a></td>
<td cellspacing="0" cellpadding="0" border="0" align="center" width="10"></td>
<td cellspacing="0" cellpadding="0" border="0" align="center"><a style="color: #333333;"
href="http://go.alfresco.com/a0100K050L0000KZjI2d00U"
target="_blank"><span
style="font-family: Gotham, 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 10px; color: #727174;"><img
src="${template_assets_url}/AlfrescoGlobal_Logo_BW.png" border="0" alt="alfresco.com" width="92"
height="29"><br></span></a></td>
</tr>
</table>
<a style="color: #0c79bf; text-decoration: underline;" href="http://www.alfresco.com/company/contact"
target="_blank"><span
style="font-family: Gotham, 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 10px; color: #0c79bf;">${message("templates.generic-email.ftl.contact_us")}</span></a>
<span style="font-family: Gotham, 'Helvetica Neue', Helvetica, Arial,
sans-serif; font-size: 10px; color: #b3b3b8;">&copy; ${date?string["yyyy"]} Alfresco Software, Inc.
${message("templates.generic-email.ftl.copy_right")}</span><br>
<span style="font-family: Gotham, 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 10px; color: #b3b3b8;">Bridge Ave, The Place Maidenhead SL6 1AF United Kingdom</span><br>
<span style="font-family: Gotham, 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 10px; color: #b3b3b8;">1825 S Grant St, Suite 900 San Mateo, CA 94402 USA</span><br>
</td>
</tr>
</table>
<!--Break-->
<table width="100%">
<tr>
<td height="10">
<div class="mobile-br">&nbsp;</div>
</td>
</tr>
</table>
</td>
</tr>
</table>
</td>
</tr>
</table>
<!--Litmus Tracking-->
<table style="display: none;">
<tbody>
<tr>
<td style="width: 0px; display: none; overflow: hidden; max-height: 0px;">{{my.Litmus_Code}}</td>
</tr>
</tbody>
</table>
</body>
</html>

View File

@@ -0,0 +1,64 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Reset Password Workflow Model -->
<model name="resetpasswordwf:reset-password-workflow-model" xmlns="http://www.alfresco.org/model/dictionary/1.0">
<imports>
<import uri="http://www.alfresco.org/model/dictionary/1.0" prefix="d" />
<import uri="http://www.alfresco.org/model/bpm/1.0" prefix="bpm" />
</imports>
<namespaces>
<namespace uri="http://www.alfresco.org/model/workflow/resetpassword/1.0" prefix="resetpasswordwf" />
</namespaces>
<types>
<type name="resetpasswordwf:requestPasswordResetTask">
<parent>bpm:startTask</parent>
<mandatory-aspects>
<aspect>resetpasswordwf:resetPasswordInitialProperties</aspect>
</mandatory-aspects>
</type>
<type name="resetpasswordwf:sendResetPasswordEmailTask">
<parent>bpm:workflowTask</parent>
<mandatory-aspects>
<aspect>resetpasswordwf:resetPasswordInitialProperties</aspect>
</mandatory-aspects>
</type>
<type name="resetpasswordwf:resetPasswordTask">
<parent>bpm:workflowTask</parent>
<mandatory-aspects>
<aspect>resetpasswordwf:resetPasswordInitialProperties</aspect>
</mandatory-aspects>
</type>
<type name="resetpasswordwf:performResetPassword">
<parent>bpm:workflowTask</parent>
<mandatory-aspects>
<aspect>resetpasswordwf:resetPasswordInitialProperties</aspect>
</mandatory-aspects>
</type>
</types>
<aspects>
<aspect name="resetpasswordwf:resetPasswordInitialProperties">
<properties>
<property name="resetpasswordwf:userName">
<type>d:text</type>
</property>
<property name="resetpasswordwf:userEmail">
<type>d:text</type>
</property>
<property name="resetpasswordwf:key">
<type>d:text</type>
</property>
<property name="resetpasswordwf:clientName">
<type>d:text</type>
</property>
</properties>
</aspect>
</aspects>
</model>

View File

@@ -0,0 +1,45 @@
<?xml version="1.0" encoding="UTF-8" ?>
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:activiti="http://activiti.org/bpmn"
xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI"
typeLanguage="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://alfresco.org">
<process isExecutable="true" id="resetPassword" name="On Premise Reset Password process">
<startEvent id="start" activiti:formKey="resetpasswordwf:requestPasswordResetTask"/>
<sequenceFlow id="flow1" sourceRef="start" targetRef="sendResetPasswordEmailTask"/>
<serviceTask id="sendResetPasswordEmailTask" name="Send Reset Password Email" activiti:delegateExpression="${SendResetPasswordEmailDelegate}"
activiti:formKey="resetpasswordwf:sendResetPasswordEmailTask"/>
<sequenceFlow id="flow2" sourceRef="sendResetPasswordEmailTask" targetRef="resetPasswordTask"/>
<!-- The password reset has been requested and is now waiting for the user to complete by clicking on the link in the email. -->
<!-- Note that we do not store the password as an execution variable for security reasons. -->
<userTask id="resetPasswordTask" name="Password Reset Pending" activiti:formKey="resetpasswordwf:resetPasswordTask"/>
<!-- After 'system.reset-password.endTimer' of waiting for user to reset password, end the process -->
<boundaryEvent id="endProcessTimer" cancelActivity="true" attachedToRef="resetPasswordTask">
<timerEventDefinition>
<timeDuration>${resetpasswordwf_endTimer}</timeDuration>
</timerEventDefinition>
</boundaryEvent>
<sequenceFlow sourceRef="endProcessTimer" targetRef="expired"/>
<sequenceFlow sourceRef="resetPasswordTask" targetRef="performResetPassword"/>
<!-- The user has submitted the necessary data to reset the password. -->
<serviceTask id="performResetPassword" name="Perform Reset Password" activiti:delegateExpression="${PerformResetPasswordDelegate}"
activiti:formKey="resetpasswordwf:passwordReset"/>
<sequenceFlow sourceRef="performResetPassword" targetRef="end"/>
<endEvent id="end"/>
<endEvent id="expired"/>
</process>
</definitions>

View File

@@ -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.

View File

@@ -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 <http://www.gnu.org/licenses/>.
* #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);
}

View File

@@ -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 <http://www.gnu.org/licenses/>.
* #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<QName, Serializable> 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<WorkflowTask> 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<QName, Serializable> 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<QName, Serializable> 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<String, Object> 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<String, Serializable> 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<String, Object> 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<Task> 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<String, Serializable> 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<String, Serializable> 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<String, Serializable> 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<String, Serializable> 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<String, Serializable> getTemplateModel()
{
return templateModel;
}
public ResetPasswordEmailDetails setTemplateModel(Map<String, Serializable> 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);
}
}
}

View File

@@ -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 <http://www.gnu.org/licenses/>.
* #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;
}
}

View File

@@ -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 <http://www.gnu.org/licenses/>.
* #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);
}
}

View File

@@ -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 <http://www.gnu.org/licenses/>.
* #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);
}
}

View File

@@ -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 <http://www.gnu.org/licenses/>.
* #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";
}