diff --git a/config/alfresco/public-rest-context.xml b/config/alfresco/public-rest-context.xml
index dd54cdc101..7988c8709b 100644
--- a/config/alfresco/public-rest-context.xml
+++ b/config/alfresco/public-rest-context.xml
@@ -652,6 +652,7 @@
+
diff --git a/source/java/org/alfresco/rest/api/People.java b/source/java/org/alfresco/rest/api/People.java
index ea58e68bdf..c319914adb 100644
--- a/source/java/org/alfresco/rest/api/People.java
+++ b/source/java/org/alfresco/rest/api/People.java
@@ -2,7 +2,7 @@
* #%L
* Alfresco Remote API
* %%
- * Copyright (C) 2005 - 2016 Alfresco Software Limited
+ * 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
@@ -25,6 +25,7 @@
*/
package org.alfresco.rest.api;
+import org.alfresco.rest.api.model.PasswordReset;
import org.alfresco.rest.api.model.Person;
import org.alfresco.rest.framework.resource.parameters.CollectionWithPagingInfo;
import org.alfresco.rest.framework.resource.parameters.Parameters;
@@ -35,15 +36,15 @@ import java.util.List;
public interface People
{
- String DEFAULT_USER = "-me-";
+ String DEFAULT_USER = "-me-";
String PARAM_INCLUDE_ASPECTNAMES = "aspectNames";
String PARAM_INCLUDE_PROPERTIES = "properties";
String PARAM_FIRST_NAME = "firstName";
String PARAM_LAST_NAME = "lastName";
String PARAM_ID = "id";
- String validatePerson(String personId);
- String validatePerson(String personId, boolean validateIsCurrentUser);
+ String validatePerson(String personId);
+ String validatePerson(String personId, boolean validateIsCurrentUser);
NodeRef getAvatar(String personId);
/**
@@ -85,4 +86,20 @@ public interface People
* @return CollectionWithPagingInfo
*/
CollectionWithPagingInfo getPeople(Parameters parameters);
+
+ /**
+ * Request password reset (an email will be sent to the registered email of the given {@code userId}).
+ * The API returns a 202 response for a valid, as well as the invalid (does not exist or disabled) userId
+ *
+ * @param userId the user id of the person requesting the password reset
+ * @param client the client name which is registered to send emails
+ */
+ void requestPasswordReset(String userId, String client);
+
+ /**
+ * Performs password reset
+ *
+ * @param passwordReset the password reset details
+ */
+ void resetPassword(String personId, PasswordReset passwordReset);
}
diff --git a/source/java/org/alfresco/rest/api/impl/PeopleImpl.java b/source/java/org/alfresco/rest/api/impl/PeopleImpl.java
index 830e49d77b..95bffde732 100644
--- a/source/java/org/alfresco/rest/api/impl/PeopleImpl.java
+++ b/source/java/org/alfresco/rest/api/impl/PeopleImpl.java
@@ -2,7 +2,7 @@
* #%L
* Alfresco Remote API
* %%
- * Copyright (C) 2005 - 2016 Alfresco Software Limited
+ * 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
@@ -31,13 +31,20 @@ import org.alfresco.query.PagingResults;
import org.alfresco.repo.security.authentication.AuthenticationException;
import org.alfresco.repo.security.authentication.AuthenticationUtil;
import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork;
+import org.alfresco.repo.security.authentication.ResetPasswordService;
+import org.alfresco.repo.security.authentication.ResetPasswordServiceImpl.InvalidResetPasswordWorkflowException;
+import org.alfresco.repo.security.authentication.ResetPasswordServiceImpl.ResetPasswordDetails;
+import org.alfresco.repo.security.authentication.ResetPasswordServiceImpl.ResetPasswordWorkflowInvalidUserException;
+import org.alfresco.repo.security.authentication.ResetPasswordServiceImpl.ResetPasswordWorkflowNotFoundException;
import org.alfresco.rest.api.Nodes;
import org.alfresco.rest.api.People;
import org.alfresco.rest.api.Sites;
+import org.alfresco.rest.api.model.PasswordReset;
import org.alfresco.rest.api.model.Person;
import org.alfresco.rest.framework.core.exceptions.ConstraintViolatedException;
import org.alfresco.rest.framework.core.exceptions.EntityNotFoundException;
import org.alfresco.rest.framework.core.exceptions.InvalidArgumentException;
+import org.alfresco.rest.framework.core.exceptions.NotFoundException;
import org.alfresco.rest.framework.core.exceptions.PermissionDeniedException;
import org.alfresco.rest.framework.resource.parameters.CollectionWithPagingInfo;
import org.alfresco.rest.framework.resource.parameters.Paging;
@@ -57,6 +64,8 @@ import org.alfresco.service.cmr.usage.ContentUsageService;
import org.alfresco.service.namespace.NamespaceService;
import org.alfresco.service.namespace.QName;
import org.alfresco.util.Pair;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
import java.io.Serializable;
import java.util.AbstractList;
@@ -76,6 +85,8 @@ import java.util.Set;
*/
public class PeopleImpl implements People
{
+ private static final Log LOGGER = LogFactory.getLog(PeopleImpl.class);
+
private static final List EXCLUDED_NS = Arrays.asList(
NamespaceService.SYSTEM_MODEL_1_0_URI,
"http://www.alfresco.org/model/user/1.0",
@@ -99,6 +110,7 @@ public class PeopleImpl implements People
protected ContentUsageService contentUsageService;
protected ContentService contentService;
protected ThumbnailService thumbnailService;
+ protected ResetPasswordService resetPasswordService;
private final static Map sort_params_to_qnames;
static
@@ -160,17 +172,24 @@ public class PeopleImpl implements People
this.thumbnailService = thumbnailService;
}
+ public void setResetPasswordService(ResetPasswordService resetPasswordService)
+ {
+ this.resetPasswordService = resetPasswordService;
+ }
+
/**
* Validate, perform -me- substitution and canonicalize the person ID.
*
* @param personId
* @return The validated and processed ID.
*/
+ @Override
public String validatePerson(String personId)
{
return validatePerson(personId, false);
}
+ @Override
public String validatePerson(final String requestedPersonId, boolean validateIsCurrentUser)
{
String personId = requestedPersonId;
@@ -237,7 +256,7 @@ public class PeopleImpl implements People
});
}
}
-
+
public boolean hasAvatar(NodeRef personNodeRef)
{
if(personNodeRef != null)
@@ -251,6 +270,7 @@ public class PeopleImpl implements People
}
}
+ @Override
public NodeRef getAvatar(String personId)
{
NodeRef avatar = null;
@@ -292,6 +312,7 @@ public class PeopleImpl implements People
* @throws NoSuchPersonException
* if personId does not exist
*/
+ @Override
public Person getPerson(String personId)
{
personId = validatePerson(personId);
@@ -311,6 +332,7 @@ public class PeopleImpl implements People
return person;
}
+ @Override
public CollectionWithPagingInfo getPeople(final Parameters parameters)
{
Paging paging = parameters.getPaging();
@@ -585,6 +607,7 @@ public class PeopleImpl implements People
}
}
+ @Override
public Person update(String personId, final Person person)
{
// Validate, perform -me- substitution and canonicalize the person ID.
@@ -737,4 +760,71 @@ public class PeopleImpl implements People
{
return authorityService.isAdminAuthority(authorityName);
}
+
+ @Override
+ public void requestPasswordReset(String userId, String client)
+ {
+ // Validate the userId and the client
+ checkRequiredField("userId", userId);
+ checkRequiredField("client", client);
+
+ // This is an un-authenticated API call so we wrap it to run as Admin
+ AuthenticationUtil.runAs(() -> {
+ try
+ {
+ resetPasswordService.requestReset(userId, client);
+ }
+ catch (ResetPasswordWorkflowInvalidUserException ex)
+ {
+ // we don't throw an exception.
+ // For security reason (prevent the attackers to determine that userId exists in the system or not),
+ // the endpoint returns a 202 response if the userId does not exist or
+ // if the user is disabled by an Administrator.
+ if (LOGGER.isDebugEnabled())
+ {
+ LOGGER.debug("Invalid user. " + ex.getMessage());
+ }
+ }
+
+ return null;
+ }, AuthenticationUtil.getAdminUserName());
+ }
+
+ @Override
+ public void resetPassword(String personId, final PasswordReset passwordReset)
+ {
+ checkResetPasswordData(passwordReset);
+ checkRequiredField("personId", personId);
+
+ ResetPasswordDetails resetDetails = new ResetPasswordDetails()
+ .setUserId(personId)
+ .setPassword(passwordReset.getPassword())
+ .setWorkflowId(passwordReset.getId())
+ .setWorkflowKey(passwordReset.getKey());
+ try
+ {
+ // This is an un-authenticated API call so we wrap it to run as Admin
+ AuthenticationUtil.runAs(() -> {
+ resetPasswordService.resetPassword(resetDetails);
+
+ return null;
+ }, AuthenticationUtil.getAdminUserName());
+
+ }
+ catch (InvalidResetPasswordWorkflowException iex)
+ {
+ throw new InvalidArgumentException(iex.getMessage());
+ }
+ catch (ResetPasswordWorkflowNotFoundException ex)
+ {
+ throw new NotFoundException(ex.getMessage());
+ }
+ }
+
+ private void checkResetPasswordData(PasswordReset data)
+ {
+ checkRequiredField("password", data.getPassword());
+ checkRequiredField("id", data.getId());
+ checkRequiredField("key", data.getKey());
+ }
}
diff --git a/source/java/org/alfresco/rest/api/model/Client.java b/source/java/org/alfresco/rest/api/model/Client.java
new file mode 100644
index 0000000000..bf6dec8dda
--- /dev/null
+++ b/source/java/org/alfresco/rest/api/model/Client.java
@@ -0,0 +1,60 @@
+/*
+ * #%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.rest.api.model;
+
+/**
+ * Representation of a client app.
+ *
+ * @author Jamal Kaabi-Mofrad
+ * @since 5.2.1
+ */
+public class Client
+{
+ /**
+ * client app name. Used to lookup the client
+ * that is registered to send emails so that
+ * client's specific configuration could be used.
+ */
+ protected String client;
+
+ public String getClient()
+ {
+ return client;
+ }
+
+ public Client setClient(String client)
+ {
+ this.client = client;
+ return this;
+ }
+
+ @Override
+ public String toString()
+ {
+ return "Client [ client=" + client + ']';
+ }
+}
diff --git a/source/java/org/alfresco/rest/api/model/PasswordReset.java b/source/java/org/alfresco/rest/api/model/PasswordReset.java
new file mode 100644
index 0000000000..48c987759f
--- /dev/null
+++ b/source/java/org/alfresco/rest/api/model/PasswordReset.java
@@ -0,0 +1,91 @@
+/*
+ * #%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.rest.api.model;
+
+/**
+ * Representation of a password reset.
+ *
+ * @author Jamal Kaabi-Mofrad
+ * @since 5.2.1
+ */
+public class PasswordReset
+{
+ /** new password */
+ private String password;
+ /** workflow Id */
+ private String id;
+ /** workflow Key */
+ private String key;
+
+ public PasswordReset()
+ {
+ }
+
+ public String getPassword()
+ {
+ return password;
+ }
+
+ public PasswordReset setPassword(String password)
+ {
+ this.password = password;
+ return this;
+ }
+
+ public String getId()
+ {
+ return id;
+ }
+
+ public PasswordReset setId(String id)
+ {
+ this.id = id;
+ return this;
+ }
+
+ public String getKey()
+ {
+ return key;
+ }
+
+ public PasswordReset setKey(String key)
+ {
+ this.key = key;
+ return this;
+ }
+
+ @Override
+ public String toString()
+ {
+ // we don't return the password for the obvious reason
+ final StringBuilder sb = new StringBuilder(100);
+ sb.append("PasswordReset [id=").append(id)
+ .append(", key=").append(key)
+ .append(']');
+ return sb.toString();
+ }
+}
diff --git a/source/java/org/alfresco/rest/api/people/PeopleEntityResource.java b/source/java/org/alfresco/rest/api/people/PeopleEntityResource.java
index 75cf2ec85f..df40c3fada 100644
--- a/source/java/org/alfresco/rest/api/people/PeopleEntityResource.java
+++ b/source/java/org/alfresco/rest/api/people/PeopleEntityResource.java
@@ -2,7 +2,7 @@
* #%L
* Alfresco Remote API
* %%
- * Copyright (C) 2005 - 2016 Alfresco Software Limited
+ * 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
@@ -27,8 +27,12 @@ package org.alfresco.rest.api.people;
import org.alfresco.model.ContentModel;
import org.alfresco.rest.api.People;
+import org.alfresco.rest.api.model.Client;
+import org.alfresco.rest.api.model.PasswordReset;
import org.alfresco.rest.api.model.Person;
+import org.alfresco.rest.framework.Operation;
import org.alfresco.rest.framework.WebApiDescription;
+import org.alfresco.rest.framework.WebApiNoAuth;
import org.alfresco.rest.framework.WebApiParam;
import org.alfresco.rest.framework.core.ResourceParameter;
import org.alfresco.rest.framework.core.exceptions.InvalidArgumentException;
@@ -36,11 +40,13 @@ import org.alfresco.rest.framework.resource.EntityResource;
import org.alfresco.rest.framework.resource.actions.interfaces.EntityResourceAction;
import org.alfresco.rest.framework.resource.parameters.CollectionWithPagingInfo;
import org.alfresco.rest.framework.resource.parameters.Parameters;
-import org.alfresco.util.ParameterCheck;
+import org.alfresco.rest.framework.webscripts.WithResponse;
+import org.alfresco.util.PropertyCheck;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.InitializingBean;
+import javax.servlet.http.HttpServletResponse;
import java.util.ArrayList;
import java.util.List;
@@ -57,17 +63,17 @@ public class PeopleEntityResource implements EntityResourceAction.ReadById