Commit 8a812cdb authored by Piotr Gawron's avatar Piotr Gawron
Browse files

API call to request reset password implemented

parent 09618f2c
......@@ -358,6 +358,9 @@ public enum ConfigurationElementType {
DAPI_PASSWORD("Password to Data-API system", "", ConfigurationElementEditType.PASSWORD, false,
ConfigurationElementTypeGroup.DAPI),
MINERVA_ROOT("Minerva root url", "", ConfigurationElementEditType.URL, false,
ConfigurationElementTypeGroup.SERVER_CONFIGURATION),
;
/**
......
package lcsb.mapviewer.model.user;
import java.io.Serializable;
import java.util.Calendar;
import javax.persistence.*;
@Entity
public class ResetPasswordToken implements Serializable {
/**
*
*/
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
@Column(nullable = false)
private String token;
@ManyToOne
private User user;
@Column(nullable = false)
private Calendar expires;
public ResetPasswordToken(User user, String token, Calendar expires) {
this.token = token;
this.user = user;
this.expires = expires;
}
public String getToken() {
return token;
}
public User getUser() {
return user;
}
public Calendar getExpires() {
return expires;
}
}
package lcsb.mapviewer.persist.dao.user;
import org.springframework.stereotype.Repository;
import lcsb.mapviewer.model.user.ResetPasswordToken;
import lcsb.mapviewer.persist.dao.BaseDao;
@Repository
public class ResetPasswordTokenDao extends BaseDao<ResetPasswordToken> {
public ResetPasswordTokenDao() {
super(ResetPasswordToken.class);
}
public ResetPasswordToken getByToken(String token) {
return getByParameter("token", token);
}
}
CREATE SEQUENCE reset_password_token_sequence
INCREMENT 1
MINVALUE 1
MAXVALUE 9223372036854775807
START 1
CACHE 1;
CREATE TABLE reset_password_token_table
(
id integer NOT NULL DEFAULT nextval('reset_password_token_sequence'::regclass),
token varchar NOT NULL,
user_id integer not null,
expires timestamp without time zone,
CONSTRAINT reset_password_token_pk PRIMARY KEY (id),
CONSTRAINT reset_password_token_user_fk FOREIGN KEY (user_id)
REFERENCES user_table (id) MATCH SIMPLE
ON UPDATE NO ACTION ON DELETE NO ACTION
)
WITH (
OIDS=FALSE
);
......@@ -4,6 +4,8 @@ import java.io.IOException;
import java.util.*;
import java.util.stream.Collectors;
import javax.mail.MessagingException;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
......@@ -107,6 +109,25 @@ public class UserController extends BaseController {
return userRest.removeUser(login);
}
@PostMapping(value = "/{login}:requestResetPassword")
public Map<String, Object> requestResetPasswordToken(
@PathVariable(value = "login") String login) throws QueryException, MessagingException {
userRest.requestResetPassword(login);
Map<String, Object> result= new HashMap<>();
result.put("status", "OK");
return result;
}
@PostMapping(value = ":resetPassword")
public Map<String, Object> resetPassword(
@PathVariable(value = "token") String token, String password) throws IOException, QueryException {
userRest.resetPassword(token, password);
Map<String, Object> result= new HashMap<>();
result.put("status", "OK");
return result;
}
public IUserService getUserService() {
return userService;
}
......
......@@ -2,6 +2,8 @@ package lcsb.mapviewer.api.users;
import java.util.*;
import javax.mail.MessagingException;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
......@@ -15,12 +17,13 @@ import org.springframework.util.MultiValueMap;
import lcsb.mapviewer.api.*;
import lcsb.mapviewer.common.Configuration;
import lcsb.mapviewer.common.exception.InvalidArgumentException;
import lcsb.mapviewer.common.exception.*;
import lcsb.mapviewer.model.map.MiriamType;
import lcsb.mapviewer.model.security.Privilege;
import lcsb.mapviewer.model.security.PrivilegeType;
import lcsb.mapviewer.model.user.*;
import lcsb.mapviewer.model.user.annotator.*;
import lcsb.mapviewer.services.utils.EmailSender;
@Transactional
@Service
......@@ -33,9 +36,12 @@ public class UserRestImpl extends BaseRestImpl {
private Logger logger = LogManager.getLogger(UserRestImpl.class);
private PasswordEncoder passwordEncoder;
private EmailSender emailSender;
@Autowired
public UserRestImpl(PasswordEncoder passwordEncoder) {
public UserRestImpl(PasswordEncoder passwordEncoder, EmailSender emailSender) {
this.passwordEncoder = passwordEncoder;
this.emailSender = emailSender;
}
public Map<String, Object> getUser(String login, String columns)
......@@ -731,4 +737,28 @@ public class UserRestImpl extends BaseRestImpl {
return okStatus();
}
public void requestResetPassword(String login) throws MessagingException, QueryException {
User user = getUserService().getUserByLogin(login);
if (user == null) {
throw new ObjectNotFoundException("User does not exist");
}
if (user.getEmail() == null || user.getEmail().equals("")) {
throw new QueryException("User does not have email address defined");
}
if (getConfigurationService().getConfigurationValue(ConfigurationElementType.MINERVA_ROOT).trim().isEmpty()) {
throw new InvalidStateException("Cannot create token - minerva root url is not defined");
}
if (!emailSender.canSendEmails()) {
throw new InvalidStateException("Cannot create token - minerva cannot send emails");
}
getUserService().createResetPasswordToken(user);
}
public void resetPassword(String token, String password) {
throw new NotImplementedException();
}
}
package lcsb.mapviewer.services.impl;
import java.awt.*;
import java.awt.Color;
import java.util.*;
import java.util.List;
import javax.mail.MessagingException;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
......@@ -18,31 +19,40 @@ import lcsb.mapviewer.model.security.Privilege;
import lcsb.mapviewer.model.security.PrivilegeType;
import lcsb.mapviewer.model.user.*;
import lcsb.mapviewer.persist.dao.ProjectDao;
import lcsb.mapviewer.persist.dao.user.ResetPasswordTokenDao;
import lcsb.mapviewer.persist.dao.user.UserDao;
import lcsb.mapviewer.services.interfaces.*;
import lcsb.mapviewer.services.utils.EmailSender;
@Transactional
@Service
public class UserService implements IUserService {
private static Logger logger = LogManager.getLogger(UserService.class);
private UserDao userDao;
private ILdapService ldapService;
private IConfigurationService configurationService;
private IPrivilegeService privilegeService;
private ProjectDao projectDao;
private ResetPasswordTokenDao resetPasswordTokenDao;
private EmailSender emailSender;
@Autowired
public UserService(UserDao userDao,
ILdapService ldapService,
IConfigurationService configurationService,
IPrivilegeService privilegeService,
ProjectDao projectDao) {
ProjectDao projectDao,
ResetPasswordTokenDao resetPasswordTokenDao,
EmailSender emailSender) {
this.userDao = userDao;
this.ldapService = ldapService;
this.configurationService = configurationService;
this.privilegeService = privilegeService;
this.projectDao = projectDao;
this.resetPasswordTokenDao = resetPasswordTokenDao;
this.emailSender = emailSender;
}
@Override
......@@ -191,4 +201,26 @@ public class UserService implements IUserService {
}
}
@Override
public void createResetPasswordToken(User user) throws MessagingException {
Calendar expires = Calendar.getInstance();
expires.add(Calendar.DAY_OF_MONTH, 1);
ResetPasswordToken token = new ResetPasswordToken(user, UUID.randomUUID().toString(), expires);
resetPasswordTokenDao.add(token);
String content = "Dear " + user.getName() + " " + user.getSurname() + " (" + user.getLogin() + "),<br/><br/>" +
"We received request to reset your password. You can reset the password using this <a href=\""
+ configurationService.getConfigurationValue(ConfigurationElementType.MINERVA_ROOT)
+ "login.xhtml?resetPasswordToken=" + token.getToken()
+ "\">link</a>. Link will be valid for the next 24 hours.<br/><br/>"
+ "If you did not perform this action you can ignore the email.<br/>";
try {
emailSender.sendEmail("Minerva - Reset password", content, user.getEmail());
} catch (MessagingException e) {
resetPasswordTokenDao.delete(token);
throw e;
}
}
}
......@@ -2,6 +2,8 @@ package lcsb.mapviewer.services.interfaces;
import java.util.*;
import javax.mail.MessagingException;
import lcsb.mapviewer.commands.ColorExtractor;
import lcsb.mapviewer.model.security.PrivilegeType;
import lcsb.mapviewer.model.user.User;
......@@ -55,4 +57,6 @@ public interface IUserService {
ColorExtractor getColorExtractorForUser(User user);
Map<String, Boolean> ldapAccountExistsForLogin(Collection<User> logins);
void createResetPasswordToken(User user) throws MessagingException;
}
\ No newline at end of file
......@@ -11,6 +11,9 @@ import javax.mail.internet.*;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.sun.mail.smtp.SMTPTransport;
......@@ -25,6 +28,8 @@ import lcsb.mapviewer.services.interfaces.IConfigurationService;
* @author Piotr Gawron
*
*/
@Transactional
@Service
public class EmailSender {
/**
......@@ -99,6 +104,7 @@ public class EmailSender {
* configuration service that contains information required to access
* email account
*/
@Autowired
public EmailSender(IConfigurationService configurationService) {
this(configurationService.getConfigurationValue(ConfigurationElementType.EMAIL_ADDRESS),
configurationService.getConfigurationValue(ConfigurationElementType.EMAIL_LOGIN),
......@@ -283,7 +289,7 @@ public class EmailSender {
* @param emailAddress
* email address that should receive email
* @throws MessagingException
* thrown whene there is a problem with sending email
* thrown when there is a problem with sending email
*/
public void sendEmail(String subject, String message, String emailAddress) throws MessagingException {
if (emailAddress == null || emailAddress.equals("")) {
......@@ -296,4 +302,11 @@ public class EmailSender {
}
public boolean canSendEmails() {
if (smtpHost.equals(ConfigurationElementType.EMAIL_SMTP_SERVER.getDefaultValue())) {
return false;
}
return true;
}
}
package lcsb.mapviewer.web;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.mockito.Mockito;
import org.springframework.context.annotation.*;
import lcsb.mapviewer.services.utils.EmailSender;
@Profile("emailSenderProfile")
@Configuration
public class EmailSenderMockConfiguration {
Logger logger = LogManager.getLogger();
@Bean
@Primary
public EmailSender emailSender() throws Exception {
EmailSender mock = Mockito.mock(EmailSender.class);
Mockito.doReturn(true).when(mock).canSendEmails();
return mock;
}
}
......@@ -14,10 +14,12 @@ import org.apache.logging.log4j.Logger;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.mock.web.MockHttpSession;
import org.springframework.test.annotation.Rollback;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.web.servlet.RequestBuilder;
import org.springframework.transaction.annotation.Transactional;
......@@ -30,10 +32,13 @@ import lcsb.mapviewer.model.security.PrivilegeType;
import lcsb.mapviewer.model.user.ConfigurationElementType;
import lcsb.mapviewer.model.user.User;
import lcsb.mapviewer.persist.dao.ProjectDao;
import lcsb.mapviewer.persist.dao.user.ResetPasswordTokenDao;
import lcsb.mapviewer.services.interfaces.IConfigurationService;
import lcsb.mapviewer.services.interfaces.IUserService;
import lcsb.mapviewer.services.utils.EmailSender;
@RunWith(SpringJUnit4ClassRunner.class)
@ActiveProfiles("emailSenderProfile")
@Transactional
@Rollback
public class UserControllerIntegrationTest extends ControllerIntegrationTest {
......@@ -56,6 +61,12 @@ public class UserControllerIntegrationTest extends ControllerIntegrationTest {
@Autowired
private ProjectDao projectDao;
@Autowired
private ResetPasswordTokenDao resetPasswordTokenDao;
@Autowired
private EmailSender emailSender;
@Before
public void setup() {
user = createUser(TEST_USER_LOGIN, TEST_USER_PASSWORD);
......@@ -438,4 +449,79 @@ public class UserControllerIntegrationTest extends ControllerIntegrationTest {
.andExpect(status().isForbidden());
}
@Test
public void requestResetPassword() throws Exception {
long count = resetPasswordTokenDao.getCount();
configureServerForResetPasswordRequest();
RequestBuilder grantRequest = post("/users/" + BUILT_IN_TEST_ADMIN_LOGIN + ":requestResetPassword");
mockMvc.perform(grantRequest)
.andExpect(status().is2xxSuccessful());
assertEquals(count + 1, resetPasswordTokenDao.getCount());
}
@Test
public void requestResetPasswordWhenMinervaRootNotConfigured() throws Exception {
configureServerForResetPasswordRequest();
configurationService.setConfigurationValue(ConfigurationElementType.MINERVA_ROOT, "");
RequestBuilder grantRequest = post("/users/" + BUILT_IN_TEST_ADMIN_LOGIN + ":requestResetPassword");
mockMvc.perform(grantRequest)
.andExpect(status().is5xxServerError());
}
@Test
public void requestResetPasswordWhenMinervaCannotSendMails() throws Exception {
try {
configureServerForResetPasswordRequest();
RequestBuilder grantRequest = post("/users/" + BUILT_IN_TEST_ADMIN_LOGIN + ":requestResetPassword");
Mockito.doReturn(false).when(emailSender).canSendEmails();
mockMvc.perform(grantRequest)
.andExpect(status().is5xxServerError());
} finally {
Mockito.doReturn(true).when(emailSender).canSendEmails();
}
}
@Test
public void requestResetPasswordWhenUserWithoutEmail() throws Exception {
configureServerForResetPasswordRequest();
User user = userService.getUserByLogin(BUILT_IN_TEST_ADMIN_LOGIN);
user.setEmail("");
userService.updateUser(user);
RequestBuilder grantRequest = post("/users/" + BUILT_IN_TEST_ADMIN_LOGIN + ":requestResetPassword");
mockMvc.perform(grantRequest)
.andExpect(status().is4xxClientError());
}
@Test
public void requestResetPasswordForUnknownUser() throws Exception {
configureServerForResetPasswordRequest();
RequestBuilder grantRequest = post("/users/blabla:requestResetPassword");
mockMvc.perform(grantRequest)
.andExpect(status().is4xxClientError());
}
private void configureServerForResetPasswordRequest() {
User user = userService.getUserByLogin(BUILT_IN_TEST_ADMIN_LOGIN);
user.setEmail("test@test.xyz");
userService.updateUser(user);
configurationService.setConfigurationValue(ConfigurationElementType.MINERVA_ROOT, "http://localhost:8080/minerva/");
}
}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment