From cded2f354d9559f0601f369fee42e8e00ccb5c16 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 --- config/alfresco/public-rest-context.xml | 1 + source/java/org/alfresco/rest/api/People.java | 25 ++++- .../alfresco/rest/api/impl/PeopleImpl.java | 94 ++++++++++++++++++- .../org/alfresco/rest/api/model/Client.java | 60 ++++++++++++ .../rest/api/model/PasswordReset.java | 91 ++++++++++++++++++ .../rest/api/people/PeopleEntityResource.java | 41 ++++++-- 6 files changed, 297 insertions(+), 15 deletions(-) create mode 100644 source/java/org/alfresco/rest/api/model/Client.java create mode 100644 source/java/org/alfresco/rest/api/model/PasswordReset.java 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