package org.molgenis.security.account;
import org.apache.commons.lang3.StringUtils;
import org.molgenis.auth.Group;
import org.molgenis.auth.GroupMember;
import org.molgenis.auth.GroupMemberFactory;
import org.molgenis.auth.User;
import org.molgenis.data.DataService;
import org.molgenis.data.MolgenisDataException;
import org.molgenis.data.settings.AppSettings;
import org.molgenis.security.core.runas.RunAsSystem;
import org.molgenis.security.user.MolgenisUserException;
import org.molgenis.security.user.UserService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.mail.MailException;
import org.springframework.mail.SimpleMailMessage;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.net.URI;
import java.util.List;
import java.util.UUID;
import static java.lang.String.format;
import static java.util.Arrays.asList;
import static java.util.Objects.requireNonNull;
import static org.molgenis.auth.GroupMemberMetaData.GROUP_MEMBER;
import static org.molgenis.auth.GroupMetaData.GROUP;
import static org.molgenis.auth.GroupMetaData.NAME;
import static org.molgenis.auth.UserMetaData.*;
@Service
public class AccountServiceImpl implements AccountService
{
private static final Logger LOG = LoggerFactory.getLogger(AccountServiceImpl.class);
private final DataService dataService;
private final JavaMailSender mailSender;
private final UserService userService;
private final AppSettings appSettings;
private final GroupMemberFactory groupMemberFactory;
@Autowired
public AccountServiceImpl(DataService dataService, JavaMailSender mailSender,
UserService userService, AppSettings appSettings,
GroupMemberFactory groupMemberFactory)
{
this.dataService = requireNonNull(dataService);
this.mailSender = requireNonNull(mailSender);
this.userService = requireNonNull(userService);
this.appSettings = requireNonNull(appSettings);
this.groupMemberFactory = requireNonNull(groupMemberFactory);
}
@Override
@RunAsSystem
@Transactional
public void createUser(User user, String baseActivationUri)
throws UsernameAlreadyExistsException, EmailAlreadyExistsException
{
// Check if username already exists
if (userService.getUser(user.getUsername()) != null)
{
throw new UsernameAlreadyExistsException("Username '" + user.getUsername() + "' already exists.");
}
// Check if email already exists
if (userService.getUserByEmail(user.getEmail()) != null)
{
throw new EmailAlreadyExistsException("Email '" + user.getEmail() + "' is already registered.");
}
// collect activation info
String activationCode = UUID.randomUUID().toString();
List<String> activationEmailAddresses;
if (appSettings.getSignUpModeration())
{
activationEmailAddresses = userService.getSuEmailAddresses();
if (activationEmailAddresses == null || activationEmailAddresses.isEmpty())
throw new MolgenisDataException("Administrator account is missing required email address");
}
else
{
String activationEmailAddress = user.getEmail();
if (activationEmailAddress == null || activationEmailAddress.isEmpty()) throw new MolgenisDataException(
"User '" + user.getUsername() + "' is missing required email address");
activationEmailAddresses = asList(activationEmailAddress);
}
// create user
user.setActivationCode(activationCode);
user.setActive(false);
dataService.add(USER, user);
LOG.debug("created user " + user.getUsername());
// add user to group
Group group = dataService.query(GROUP, Group.class).eq(NAME, ALL_USER_GROUP).findOne();
GroupMember groupMember = null;
if (group != null)
{
groupMember = groupMemberFactory.create();
groupMember.setGroup(group);
groupMember.setUser(user);
dataService.add(GROUP_MEMBER, groupMember);
}
// send activation email
URI activationUri = URI.create(baseActivationUri + '/' + activationCode);
try
{
SimpleMailMessage mailMessage = new SimpleMailMessage();
mailMessage.setTo(activationEmailAddresses.toArray(new String[] {}));
mailMessage.setSubject("User registration for " + appSettings.getTitle());
mailMessage.setText(createActivationEmailText(user, activationUri));
mailSender.send(mailMessage);
}
catch (MailException mce)
{
LOG.error("Could not send signup mail", mce);
if (groupMember != null)
{
dataService.delete(GROUP_MEMBER, groupMember);
}
dataService.delete(USER, user);
throw new MolgenisUserException(
"An error occurred. Please contact the administrator. You are not signed up!");
}
LOG.debug("send activation email for user " + user.getUsername() + " to " + StringUtils
.join(activationEmailAddresses, ','));
}
@Override
@RunAsSystem
public void activateUser(String activationCode)
{
User user = dataService.query(USER, User.class).eq(ACTIVE, false).and()
.eq(ACTIVATIONCODE, activationCode).findOne();
if (user != null)
{
user.setActive(true);
dataService.update(USER, user);
// send activated email to user
SimpleMailMessage mailMessage = new SimpleMailMessage();
mailMessage.setTo(user.getEmail());
mailMessage.setSubject("Your registration request for " + appSettings.getTitle());
mailMessage.setText(createActivatedEmailText(user, appSettings.getTitle()));
mailSender.send(mailMessage);
}
else
{
throw new MolgenisUserException("Invalid activation code or account already activated.");
}
}
@Override
@RunAsSystem
public void changePassword(String username, String newPassword)
{
User user = dataService.query(USER, User.class).eq(USERNAME, username).findOne();
if (user == null)
{
throw new MolgenisUserException(format("Unknown user [%s]", username));
}
user.setPassword(newPassword);
user.setChangePassword(false);
dataService.update(USER, user);
LOG.info("Changed password of user [{}]", username);
}
@Override
@RunAsSystem
public void resetPassword(String userEmail)
{
User user = dataService.query(USER, User.class).eq(EMAIL, userEmail).findOne();
if (user != null)
{
String newPassword = UUID.randomUUID().toString().substring(0, 8);
user.setPassword(newPassword);
user.setChangePassword(true);
dataService.update(USER, user);
// send password reseted email to user
SimpleMailMessage mailMessage = new SimpleMailMessage();
mailMessage.setTo(user.getEmail());
mailMessage.setSubject("Your new password request");
mailMessage.setText(createPasswordResettedEmailText(newPassword));
mailSender.send(mailMessage);
}
else
{
throw new MolgenisUserException("Invalid email address.");
}
}
private String createActivationEmailText(User user, URI activationUri)
{
StringBuilder strBuilder = new StringBuilder();
strBuilder.append("User registration for ").append(appSettings.getTitle()).append('\n');
strBuilder.append("User name: ").append(user.getUsername()).append(" Full name: ").append(user.getFirstName());
strBuilder.append(' ').append(user.getLastName()).append('\n');
strBuilder.append("In order to activate the user visit the following URL:").append('\n');
strBuilder.append(activationUri).append('\n').append('\n');
return strBuilder.toString();
}
private String createActivatedEmailText(User user, String appName)
{
StringBuilder strBuilder = new StringBuilder();
strBuilder.append("Dear ").append(user.getFirstName()).append(" ").append(user.getLastName()).append(",\n\n");
strBuilder.append("your registration request for ").append(appName).append(" was approved.\n");
strBuilder.append("Your account is now active.\n");
return strBuilder.toString();
}
private String createPasswordResettedEmailText(String newPassword)
{
StringBuilder strBuilder = new StringBuilder();
strBuilder.append("Somebody, probably you, requested a new password for ").append(appSettings.getTitle())
.append(".\n");
strBuilder.append("The new password is: ").append(newPassword).append('\n');
strBuilder.append("Note: we strongly recommend you reset your password after log-in!");
return strBuilder.toString();
}
}