Commit 3211dc28 authored by igoshin's avatar igoshin

Final version of user story "Email validation"

#10141
parent 12860fb1
......@@ -352,7 +352,14 @@
<artifactId>spring-aspects</artifactId>
<version>3.2.5.RELEASE</version>
</dependency>
</dependencies>
<dependency>
<groupId>javax.mail</groupId>
<artifactId>mail</artifactId>
<version>1.5.0-b01</version>
</dependency>
</dependencies>
<build>
<plugins>
......
/**
* Copyright 2013 Global Crop Diversity Trust
*
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
......@@ -17,16 +17,94 @@
package org.genesys2.server.service.impl;
import org.genesys2.server.service.EMailService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.MimeMessageHelper;
import org.springframework.mail.javamail.MimeMessagePreparator;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Service;
import javax.mail.internet.MimeMessage;
import java.util.Arrays;
@Service
public class EMailServiceImpl implements EMailService {
// TODO get SMTP properties from properties file
@Override
public void sendMail(String email, String name, String mailSubject, String mailBody) {
// TODO Auto-generated method stub
private Logger _logger = LoggerFactory.getLogger(getClass());
@Autowired
private JavaMailSender mailSender;
@Autowired
private ThreadPoolTaskExecutor executor;
@Value("${mail.async}")
private boolean async;
@Value("${mail.debug.message}")
private String debugMessage;
@Value("${mail.user.from}")
private String emailFrom;
@Override
public void sendMail(String email, String name, String mailSubject, String mailBody) {
sendSimpleEmail(mailSubject, mailBody, emailFrom, email);
}
public void sendSimpleEmail(final String subject, final String text, final String emailFrom, final String... emailTo) {
printDebugInfo(subject, text, emailFrom, emailTo);
final MimeMessagePreparator preparator = new MimeMessagePreparator() {
@SuppressWarnings("unchecked")
@Override
public void prepare(MimeMessage mimeMessage) throws Exception {
MimeMessageHelper message = new MimeMessageHelper(mimeMessage, "UTF-8");
message.setFrom(emailFrom);
message.setTo(emailTo);
message.setSubject(subject);
message.setText(text, true);
}
};
doSend(preparator);
}
protected void doSend(final MimeMessagePreparator preparator) {
//execute sender in separate thread
if (async) {
executor.submit(new Runnable() {
@Override
public void run() {
try {
mailSender.send(preparator);
} catch (Exception e) {
_logger.error(e.getMessage(), e);
}
}
});
} else {
mailSender.send(preparator);
}
}
protected void printDebugInfo(String subject, String text, String emailFrom, String[] emailTo) {
System.out.println(getDebugString(subject, text, emailFrom, emailTo));
}
}
protected String getDebugString(String subject, String text, String emailFrom, String[] emailTo) {
return String.format(debugMessage,
emailFrom,
Arrays.toString(emailTo),
subject,
text);
}
}
/**
* Copyright 2013 Global Crop Diversity Trust
*
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
......@@ -16,9 +16,6 @@
package org.genesys2.server.service.impl;
import java.text.MessageFormat;
import java.util.UUID;
import org.apache.commons.lang.RandomStringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
......@@ -33,66 +30,68 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.text.MessageFormat;
import java.util.UUID;
@Service
@Transactional(readOnly = true)
public class EMailVerificationServiceImpl implements EMailVerificationService {
private static final Log LOG = LogFactory.getLog(EMailVerificationServiceImpl.class);
@Autowired
private VerificationTokenRepository verificationTokenRepository;
@Autowired
private EMailService emailService;
@Autowired
private UserService userService;
@Transactional
public void sendVerificationEmail(User user) {
// Generate new token
VerificationToken verificationToken = generateToken("email-validation", user.getUuid());
// TODO use content services to generate email body
String mailSubject = "Genesys account verification";
String mailBody = MessageFormat.format("Click http://sandbox.genesys-pgr.org/profile/{0}/validate and enter key {1}", verificationToken.getUuid(),
verificationToken.getKey());
emailService.sendMail(user.getEmail(), user.getName(), mailSubject, mailBody);
}
private VerificationToken generateToken(String tokenPurpose, String dataUuid) {
VerificationToken token = new VerificationToken();
token.setPurpose(tokenPurpose);
// Store data
token.setData(dataUuid);
token.setUuid(UUID.nameUUIDFromBytes(dataUuid.getBytes()).toString());
token.setKey(RandomStringUtils.random(4));
verificationTokenRepository.save(token);
return token;
}
@Override
@Transactional
public boolean validateEMail(String tokenUuid, String key) {
VerificationToken verificationToken = verificationTokenRepository.findByUuidAndKey(tokenUuid, key);
if (verificationToken == null) {
LOG.warn("No such verification token " + tokenUuid + " key=" + key);
return false;
}
try {
userService.userEmailValidated(verificationToken.getData());
// FIXME Move to finally?
// Remove token
verificationTokenRepository.delete(verificationToken);
return true;
} catch (UserException e) {
LOG.error(e.getMessage(), e);
}
return false;
}
private static final Log LOG = LogFactory.getLog(EMailVerificationServiceImpl.class);
@Autowired
private VerificationTokenRepository verificationTokenRepository;
@Autowired
private EMailService emailService;
@Autowired
private UserService userService;
@Transactional
public void sendVerificationEmail(User user) {
// Generate new token
VerificationToken verificationToken = generateToken("email-validation", user.getUuid());
String mailSubject = "Genesys account verification";
String mailBody = MessageFormat.format("Click http://sandbox.genesys-pgr.org/profile/{0}/validate and enter key {1}", verificationToken.getUuid(),
verificationToken.getKey());
emailService.sendMail(user.getEmail(), user.getName(), mailSubject, mailBody);
}
private VerificationToken generateToken(String tokenPurpose, String dataUuid) {
VerificationToken token = new VerificationToken();
token.setPurpose(tokenPurpose);
// Store data
token.setData(dataUuid);
token.setUuid(UUID.nameUUIDFromBytes(dataUuid.getBytes()).toString());
token.setKey(RandomStringUtils.randomAlphanumeric(4));
verificationTokenRepository.save(token);
return token;
}
@Override
@Transactional
public boolean validateEMail(String tokenUuid, String key) {
VerificationToken verificationToken = verificationTokenRepository.findByUuidAndKey(tokenUuid, key);
if (verificationToken == null) {
LOG.warn("No such verification token " + tokenUuid + " key=" + key);
return false;
}
try {
userService.userEmailValidated(verificationToken.getData());
// Remove token
verificationTokenRepository.delete(verificationToken);
return true;
} catch (UserException e) {
LOG.error(e.getMessage(), e);
}
return false;
}
}
......@@ -16,11 +16,6 @@
package org.genesys2.server.service.impl;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.genesys2.server.exception.NoUserFoundException;
......@@ -40,11 +35,21 @@ import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@Service
@Transactional(readOnly = true)
public class UserServiceImpl implements UserService {
......@@ -296,7 +301,21 @@ public class UserServiceImpl implements UserService {
}
// Since it's a set, we can just add
userRoles.add(UserRole.VALIDATEDUSER);
updateUser(user);
addRole(UserRole.VALIDATEDUSER.getName());
updateUser(user);
LOG.info("Ensured VALIDATEDUSER role for user " + user);
}
private void addRole(String role){
List<GrantedAuthority> authorities=new ArrayList<>();
SimpleGrantedAuthority simpleGrantedAuthority=new SimpleGrantedAuthority(role);
authorities.add(simpleGrantedAuthority);
Object principal= SecurityContextHolder.getContext().getAuthentication().getPrincipal();
Authentication authentication=new UsernamePasswordAuthenticationToken(principal,principal,authorities);
SecurityContextHolder.getContext().setAuthentication(authentication);
}
}
/**
* Copyright 2013 Global Crop Diversity Trust
*
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
......@@ -27,6 +27,7 @@ import org.genesys2.spring.ResourceNotFoundException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.validation.Validator;
......@@ -39,100 +40,117 @@ import org.springframework.web.bind.annotation.RequestParam;
@RequestMapping("/profile")
public class UserProfileController extends BaseController {
@Autowired
private UserService userService;
@Autowired
private Validator validator;
@Autowired
private TeamService teamService;
@Autowired
private ContentService contentService;
@Autowired
private EMailVerificationService emailVerificationService;
@Value("${captcha.privateKey}")
private String captchaPrivateKey;
@Value("${captcha.publicKey}")
private String captchaPublicKey;
@RequestMapping
@PreAuthorize("isAuthenticated()")
public String welcome(ModelMap model) {
User user = userService.getMe();
return "redirect:/profile/" + user.getUuid();
}
@RequestMapping("/{uuid:.+}")
public String someProfile(ModelMap model, @PathVariable("uuid") String uuid) {
User user = userService.getUserByUuid(uuid);
if (user == null) {
throw new ResourceNotFoundException();
}
model.addAttribute("user", user);
model.addAttribute("teams", teamService.listUserTeams(user));
return "/user/profile";
}
@RequestMapping("/{uuid:.+}/edit")
@PreAuthorize("hasRole('ADMINISTRATOR') || principal.user.email == #email")
public String edit(ModelMap model, @PathVariable("uuid") String uuid) {
someProfile(model, uuid);
return "/user/edit";
}
@RequestMapping(value = "/{tokenUuid:.+}/validate", method = RequestMethod.GET)
public String validateEmail(ModelMap model, @PathVariable("tokenUuid") String tokenUuid) {
// TODO Fill model
// TODO Fix /WEB-INF/jsp/user/validateemail.jsp
return "/user/validateemail";
}
@RequestMapping(value = "/{tokenUuid:.+}/validate", method = RequestMethod.POST)
public String validateEmail2(ModelMap model, @PathVariable("tokenUuid") String tokenUuid, @RequestParam("key") String key) {
if (emailVerificationService.validateEMail(tokenUuid, key)) {
// Valid
// TODO Change current security context if user is logged in
// redirect
return "redirect:/profile";
} else {
// Not valid
return "/user/validateemail";
}
}
@PreAuthorize("isAuthenticated()")
@RequestMapping(value = "/update", method = { RequestMethod.POST })
public String updateMe(ModelMap model, @RequestParam("name") String name, @RequestParam("pwd1") String pwd1, @RequestParam("pwd2") String pwd2) {
User user = userService.getMe();
if (user == null) {
throw new ResourceNotFoundException();
}
userService.updateData(user.getId(), name);
if (StringUtils.isNotBlank(pwd1)) {
if (pwd1.equals(pwd2)) {
try {
_logger.info("Updating password for " + user);
userService.updatePassword(user.getId(), pwd1);
_logger.warn("Password updated for " + user);
} catch (UserException e) {
_logger.error(e.getMessage(), e);
}
} else {
_logger.warn("Passwords didn't match for " + user);
}
}
return "redirect:/profile";
}
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private UserService userService;
@Autowired
private Validator validator;
@Autowired
private TeamService teamService;
@Autowired
private ContentService contentService;
@Autowired
private EMailVerificationService emailVerificationService;
@Value("${captcha.privateKey}")
private String captchaPrivateKey;
@Value("${captcha.publicKey}")
private String captchaPublicKey;
@RequestMapping
@PreAuthorize("isAuthenticated()")
public String welcome(ModelMap model) {
User user = userService.getMe();
return "redirect:/profile/" + user.getUuid();
}
@RequestMapping("/{uuid:.+}")
public String someProfile(ModelMap model, @PathVariable("uuid") String uuid) {
User user = userService.getUserByUuid(uuid);
if (user == null) {
throw new ResourceNotFoundException();
}
model.addAttribute("user", user);
model.addAttribute("teams", teamService.listUserTeams(user));
return "/user/profile";
}
@RequestMapping("/{uuid:.+}/edit")
@PreAuthorize("hasRole('ADMINISTRATOR') || principal.user.email == #email")
public String edit(ModelMap model, @PathVariable("uuid") String uuid) {
someProfile(model, uuid);
return "/user/edit";
}
@RequestMapping(value = "/{tokenUuid:.+}/validate", method = RequestMethod.GET)
public String validateEmail(ModelMap model, @PathVariable("tokenUuid") String tokenUuid) {
model.addAttribute("tokenUuid",tokenUuid);
return "/user/validateemail";
}
@RequestMapping(value = "/{uuid}/send", method = RequestMethod.GET)
public String sendEmail(ModelMap model, @PathVariable("uuid") String uuid) {
User user = userService.getUserByUuid(uuid);
emailVerificationService.sendVerificationEmail(user);
model.addAttribute("tokenUuid", uuid);
return "redirect:/profile/" + user.getUuid();
}
@RequestMapping(value = "/{tokenUuid:.+}/validate", method = RequestMethod.POST)
public String validateEmail2(ModelMap model, @PathVariable("tokenUuid") String tokenUuid, @RequestParam("key") String key) {
if (emailVerificationService.validateEMail(tokenUuid, key)) {
// Valid
return "redirect:/profile";
} else {
// Not valid
model.addAttribute("tokenUuid", tokenUuid);
model.addAttribute("error", "error");
return "/user/validateemail";
}
}
@PreAuthorize("isAuthenticated()")
@RequestMapping(value = "/update", method = {RequestMethod.POST})
public String updateMe(ModelMap model, @RequestParam("name") String name, @RequestParam("pwd1") String pwd1, @RequestParam("pwd2") String pwd2) {
User user = userService.getMe();
if (user == null) {
throw new ResourceNotFoundException();
}
userService.updateData(user.getId(), name);
if (StringUtils.isNotBlank(pwd1)) {
if (pwd1.equals(pwd2)) {
try {
_logger.info("Updating password for " + user);
userService.updatePassword(user.getId(), pwd1);
_logger.warn("Password updated for " + user);
} catch (UserException e) {
_logger.error(e.getMessage(), e);
}
} else {
_logger.warn("Passwords didn't match for " + user);
}
}
return "redirect:/profile";
}
}
......@@ -320,4 +320,7 @@ team.leave-team=Leave team
team.team-members=Team members
team.page.profile.title=Team: {0}
team.page.list.title=All teams
validate.email.key=Enter key
validate.email=Email validation
validate.email.invalid.key=Invalid key
......@@ -317,4 +317,7 @@ team.leave-team=مغادرة الفريق
team.team-members=أعضاء الفريق
team.page.profile.title=فريق\: {0}
team.page.list.title=جميع الفرق
validate.email.key=Enter key
validate.email=Email validation
validate.email.invalid.key=Invalid key
......@@ -317,4 +317,7 @@ team.leave-team=Team verlassen
team.team-members=Teammitglieder
team.page.profile.title=Team\: {0}
team.page.list.title=Alle Teams
validate.email.key=Enter key
validate.email=Email validation
validate.email.invalid.key=Invalid key
......@@ -13,3 +13,6 @@
# See the License for the specific language governing permissions and