Commit 3211dc28 authored by igoshin's avatar igoshin
Browse files

Final version of user story "Email validation"

#10141
parent 12860fb1
...@@ -352,7 +352,14 @@ ...@@ -352,7 +352,14 @@
<artifactId>spring-aspects</artifactId> <artifactId>spring-aspects</artifactId>
<version>3.2.5.RELEASE</version> <version>3.2.5.RELEASE</version>
</dependency> </dependency>
</dependencies>
<dependency>
<groupId>javax.mail</groupId>
<artifactId>mail</artifactId>
<version>1.5.0-b01</version>
</dependency>
</dependencies>
<build> <build>
<plugins> <plugins>
......
/** /**
* Copyright 2013 Global Crop Diversity Trust * Copyright 2013 Global Crop Diversity Trust
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
...@@ -17,16 +17,94 @@ ...@@ -17,16 +17,94 @@
package org.genesys2.server.service.impl; package org.genesys2.server.service.impl;
import org.genesys2.server.service.EMailService; 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 org.springframework.stereotype.Service;
import javax.mail.internet.MimeMessage;
import java.util.Arrays;
@Service @Service
public class EMailServiceImpl implements EMailService { public class EMailServiceImpl implements EMailService {
// TODO get SMTP properties from properties file
@Override private Logger _logger = LoggerFactory.getLogger(getClass());
public void sendMail(String email, String name, String mailSubject, String mailBody) {
// TODO Auto-generated method stub @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 * Copyright 2013 Global Crop Diversity Trust
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
...@@ -16,9 +16,6 @@ ...@@ -16,9 +16,6 @@
package org.genesys2.server.service.impl; package org.genesys2.server.service.impl;
import java.text.MessageFormat;
import java.util.UUID;
import org.apache.commons.lang.RandomStringUtils; import org.apache.commons.lang.RandomStringUtils;
import org.apache.commons.logging.Log; import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory; import org.apache.commons.logging.LogFactory;
...@@ -33,66 +30,68 @@ import org.springframework.beans.factory.annotation.Autowired; ...@@ -33,66 +30,68 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import java.text.MessageFormat;
import java.util.UUID;
@Service @Service
@Transactional(readOnly = true) @Transactional(readOnly = true)
public class EMailVerificationServiceImpl implements EMailVerificationService { public class EMailVerificationServiceImpl implements EMailVerificationService {
private static final Log LOG = LogFactory.getLog(EMailVerificationServiceImpl.class); private static final Log LOG = LogFactory.getLog(EMailVerificationServiceImpl.class);
@Autowired @Autowired
private VerificationTokenRepository verificationTokenRepository; private VerificationTokenRepository verificationTokenRepository;
@Autowired @Autowired
private EMailService emailService; private EMailService emailService;
@Autowired @Autowired
private UserService userService; private UserService userService;
@Transactional @Transactional
public void sendVerificationEmail(User user) { public void sendVerificationEmail(User user) {
// Generate new token // Generate new token
VerificationToken verificationToken = generateToken("email-validation", user.getUuid()); VerificationToken verificationToken = generateToken("email-validation", user.getUuid());
// TODO use content services to generate email body String mailSubject = "Genesys account verification";
String mailSubject = "Genesys account verification"; String mailBody = MessageFormat.format("Click http://sandbox.genesys-pgr.org/profile/{0}/validate and enter key {1}", verificationToken.getUuid(),
String mailBody = MessageFormat.format("Click http://sandbox.genesys-pgr.org/profile/{0}/validate and enter key {1}", verificationToken.getUuid(), verificationToken.getKey());
verificationToken.getKey());
emailService.sendMail(user.getEmail(), user.getName(), mailSubject, mailBody);
emailService.sendMail(user.getEmail(), user.getName(), mailSubject, mailBody); }
}
private VerificationToken generateToken(String tokenPurpose, String dataUuid) {
private VerificationToken generateToken(String tokenPurpose, String dataUuid) { VerificationToken token = new VerificationToken();
VerificationToken token = new VerificationToken(); token.setPurpose(tokenPurpose);
token.setPurpose(tokenPurpose); // Store data
// Store data token.setData(dataUuid);
token.setData(dataUuid); token.setUuid(UUID.nameUUIDFromBytes(dataUuid.getBytes()).toString());
token.setUuid(UUID.nameUUIDFromBytes(dataUuid.getBytes()).toString()); token.setKey(RandomStringUtils.randomAlphanumeric(4));
token.setKey(RandomStringUtils.random(4)); verificationTokenRepository.save(token);
verificationTokenRepository.save(token); return token;
return token; }
}
@Override
@Override @Transactional
@Transactional public boolean validateEMail(String tokenUuid, String key) {
public boolean validateEMail(String tokenUuid, String key) { VerificationToken verificationToken = verificationTokenRepository.findByUuidAndKey(tokenUuid, key);
VerificationToken verificationToken = verificationTokenRepository.findByUuidAndKey(tokenUuid, key); if (verificationToken == null) {
if (verificationToken == null) { LOG.warn("No such verification token " + tokenUuid + " key=" + key);
LOG.warn("No such verification token " + tokenUuid + " key=" + key); return false;
return false; }
}
try {
try { userService.userEmailValidated(verificationToken.getData());
userService.userEmailValidated(verificationToken.getData());
// FIXME Move to finally? // Remove token
// Remove token verificationTokenRepository.delete(verificationToken);
verificationTokenRepository.delete(verificationToken); return true;
return true;
} catch (UserException e) {
} catch (UserException e) { LOG.error(e.getMessage(), e);
LOG.error(e.getMessage(), e); }
}
return false;
return false; }
}
} }
...@@ -16,11 +16,6 @@ ...@@ -16,11 +16,6 @@
package org.genesys2.server.service.impl; 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.Log;
import org.apache.commons.logging.LogFactory; import org.apache.commons.logging.LogFactory;
import org.genesys2.server.exception.NoUserFoundException; import org.genesys2.server.exception.NoUserFoundException;
...@@ -40,11 +35,21 @@ import org.springframework.data.domain.PageImpl; ...@@ -40,11 +35,21 @@ import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Pageable;
import org.springframework.security.access.prepost.PreAuthorize; 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.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@Service @Service
@Transactional(readOnly = true) @Transactional(readOnly = true)
public class UserServiceImpl implements UserService { public class UserServiceImpl implements UserService {
...@@ -296,7 +301,21 @@ public class UserServiceImpl implements UserService { ...@@ -296,7 +301,21 @@ public class UserServiceImpl implements UserService {
} }
// Since it's a set, we can just add // Since it's a set, we can just add
userRoles.add(UserRole.VALIDATEDUSER); userRoles.add(UserRole.VALIDATEDUSER);
updateUser(user);
addRole(UserRole.VALIDATEDUSER.getName());
updateUser(user);
LOG.info("Ensured VALIDATEDUSER role for user " + 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 * Copyright 2013 Global Crop Diversity Trust
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
...@@ -27,6 +27,7 @@ import org.genesys2.spring.ResourceNotFoundException; ...@@ -27,6 +27,7 @@ import org.genesys2.spring.ResourceNotFoundException;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.stereotype.Controller; import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap; import org.springframework.ui.ModelMap;
import org.springframework.validation.Validator; import org.springframework.validation.Validator;
...@@ -39,100 +40,117 @@ import org.springframework.web.bind.annotation.RequestParam; ...@@ -39,100 +40,117 @@ import org.springframework.web.bind.annotation.RequestParam;
@RequestMapping("/profile") @RequestMapping("/profile")
public class UserProfileController extends BaseController { public class UserProfileController extends BaseController {
@Autowired @Autowired
private UserService userService; private AuthenticationManager authenticationManager;
@Autowired @Autowired
private Validator validator; private UserService userService;
@Autowired @Autowired
private TeamService teamService; private Validator validator;
@Autowired @Autowired
private ContentService contentService; private TeamService teamService;
@Autowired @Autowired
private EMailVerificationService emailVerificationService; private ContentService contentService;
@Value("${captcha.privateKey}") @Autowired
private String captchaPrivateKey; private EMailVerificationService emailVerificationService;
@Value("${captcha.publicKey}") @Value("${captcha.privateKey}")
private String captchaPublicKey; private String captchaPrivateKey;
@RequestMapping @Value("${captcha.publicKey}")
@PreAuthorize("isAuthenticated()") private String captchaPublicKey;
public String welcome(ModelMap model) {
User user = userService.getMe(); @RequestMapping
return "redirect:/profile/" + user.getUuid(); @PreAuthorize("isAuthenticated()")
} public String welcome(ModelMap model) {
User user = userService.getMe();
@RequestMapping("/{uuid:.+}") return "redirect:/profile/" + user.getUuid();
public String someProfile(ModelMap model, @PathVariable("uuid") String uuid) { }
User user = userService.getUserByUuid(uuid);
if (user == null) { @RequestMapping("/{uuid:.+}")
throw new ResourceNotFoundException(); public String someProfile(ModelMap model, @PathVariable("uuid") String uuid) {
} User user = userService.getUserByUuid(uuid);
if (user == null) {
model.addAttribute("user", user); throw new ResourceNotFoundException();
model.addAttribute("teams", teamService.listUserTeams(user)); }
return "/user/profile"; model.addAttribute("user", user);
} model.addAttribute("teams", teamService.listUserTeams(user));
@RequestMapping("/{uuid:.+}/edit") return "/user/profile";
@PreAuthorize("hasRole('ADMINISTRATOR') || principal.user.email == #email") }
public String edit(ModelMap model, @PathVariable("uuid") String uuid) {
someProfile(model, uuid); @RequestMapping("/{uuid:.+}/edit")
@PreAuthorize("hasRole('ADMINISTRATOR') || principal.user.email == #email")
return "/user/edit"; public String edit(ModelMap model, @PathVariable("uuid") String uuid) {
} someProfile(model, uuid);
@RequestMapping(value = "/{tokenUuid:.+}/validate", method = RequestMethod.GET) return "/user/edit";
public String validateEmail(ModelMap model, @PathVariable("tokenUuid") String tokenUuid) { }
// TODO Fill model
// TODO Fix /WEB-INF/jsp/user/validateemail.jsp @RequestMapping(value = "/{tokenUuid:.+}/validate", method = RequestMethod.GET)
return "/user/validateemail"; public String validateEmail(ModelMap model, @PathVariable("tokenUuid") String tokenUuid) {
}
model.addAttribute("tokenUuid",tokenUuid);
@RequestMapping(value = "/{tokenUuid:.+}/validate", method = RequestMethod.POST)
public String validateEmail2(ModelMap model, @PathVariable("tokenUuid") String tokenUuid, @RequestParam("key") String key) { return "/user/validateemail";
if (emailVerificationService.validateEMail(tokenUuid, key)) { }
// Valid
// TODO Change current security context if user is logged in @RequestMapping(value = "/{uuid}/send", method = RequestMethod.GET)
public String sendEmail(ModelMap model, @PathVariable("uuid") String uuid) {
// redirect
return "redirect:/profile"; User user = userService.getUserByUuid(uuid);
} else { emailVerificationService.sendVerificationEmail(user);
// Not valid
return "/user/validateemail"; model.addAttribute("tokenUuid", uuid);
} return "redirect:/profile/" + user.getUuid();
} }
@PreAuthorize("isAuthenticated()") @RequestMapping(value = "/{tokenUuid:.+}/validate", method = RequestMethod.POST)
@RequestMapping(value = "/update", method = { RequestMethod.POST }) public String validateEmail2(ModelMap model, @PathVariable("tokenUuid") String tokenUuid, @RequestParam("key") String key) {
public String updateMe(ModelMap model, @RequestParam("name") String name, @RequestParam("pwd1") String pwd1, @RequestParam("pwd2") String pwd2) { if (emailVerificationService.validateEMail(tokenUuid, key)) {
User user = userService.getMe();
if (user == null) {
throw new ResourceNotFoundException(); // Valid
} return "redirect:/profile";
userService.updateData(user.getId(), name); } else {
if (StringUtils.isNotBlank(pwd1)) { // Not valid
if (pwd1.equals(pwd2)) { model.addAttribute("tokenUuid", tokenUuid);
try { model.addAttribute("error", "error");
_logger.info("Updating password for " + user); return "/user/validateemail";
userService.updatePassword(user.getId(), pwd1);