/*
* Copyright (c) 2007-2014 by Public Library of Science
*
* http://plos.org
* http://ambraproject.org
*
* 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.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.ambraproject.service.user;
import org.ambraproject.models.UserProfile;
import org.ambraproject.service.hibernate.HibernateServiceImpl;
import org.ambraproject.service.mailer.AmbraMailer;
import org.ambraproject.service.password.PasswordDigestService;
import org.ambraproject.util.TokenGenerator;
import org.apache.commons.lang.StringUtils;
import org.hibernate.criterion.DetachedCriteria;
import org.hibernate.criterion.Projections;
import org.hibernate.criterion.Restrictions;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Required;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.dao.support.DataAccessUtils;
import org.springframework.transaction.annotation.Transactional;
import java.util.HashMap;
import java.util.Map;
/**
* @author Alex Kudlick 9/24/12
*/
public class UserRegistrationServiceImpl extends HibernateServiceImpl implements UserRegistrationService {
private static final Logger log = LoggerFactory.getLogger(UserRegistrationServiceImpl.class);
private AmbraMailer ambraMailer;
private PasswordDigestService passwordDigestService;
@Required
public void setAmbraMailer(AmbraMailer ambraMailer) {
this.ambraMailer = ambraMailer;
}
@Required
public void setPasswordDigestService(PasswordDigestService passwordDigestService) {
this.passwordDigestService = passwordDigestService;
}
/**
* {@inheritDoc}
*
* @param userProfile
* @param password
*/
@Override
@Transactional
public Long registerUser(UserProfile userProfile, String password) throws DuplicateUserException {
int existingUserCount = DataAccessUtils.intResult(
hibernateTemplate.findByCriteria(
DetachedCriteria.forClass(UserProfile.class)
.add(Restrictions.eq("email", userProfile.getEmail()))
.setProjection(Projections.count("email")))
);
if (existingUserCount > 0) {
throw new DuplicateUserException(DuplicateUserException.Field.EMAIL);
}
existingUserCount = DataAccessUtils.intResult(
hibernateTemplate.findByCriteria(
DetachedCriteria.forClass(UserProfile.class)
.add(Restrictions.eq("displayName", userProfile.getDisplayName()))
.setProjection(Projections.count("displayName")))
);
if (existingUserCount > 0) {
throw new DuplicateUserException(DuplicateUserException.Field.DISPLAY_NAME);
}
try {
log.debug("Registering new user with email: {}", userProfile.getEmail());
userProfile.setPassword(passwordDigestService.generateDigest(password));
Long id = (Long) hibernateTemplate.save(userProfile);
ambraMailer.sendVerificationEmail(userProfile.getEmail(), userProfile.getVerificationToken());
return id;
} catch (DataIntegrityViolationException e) {
throw new IllegalArgumentException("Didn't provide required field for user profile", e);
}
}
@Override
public void verifyUser(String email, String verificationToken) throws NoSuchUserException,
UserAlreadyVerifiedException, VerificationTokenException {
if (StringUtils.isEmpty(email)) {
throw new IllegalArgumentException("Must supply an email");
}
if (StringUtils.isEmpty(verificationToken)) {
throw new IllegalArgumentException("Must supply a verification token");
}
UserProfile profile = (UserProfile) DataAccessUtils.uniqueResult(
hibernateTemplate.findByCriteria(DetachedCriteria.forClass(UserProfile.class)
.add(Restrictions.eq("email", email)))
);
if (profile == null) {
throw new NoSuchUserException("No user with email: " + email);
} else if (profile.getVerified()) {
throw new UserAlreadyVerifiedException("User " + email + " was already verified");
} else if (!verificationToken.equals(profile.getVerificationToken())) {
throw new VerificationTokenException("An invalid verification token was given for this user");
}
log.debug("Verifying user {}", email);
profile.setVerified(true);
hibernateTemplate.update(profile);
}
/**
* {@inheritDoc}
*/
@Override
@Transactional
public String resendVerificationEmail(String email) throws NoSuchUserException, UserAlreadyVerifiedException {
if (StringUtils.isEmpty(email)) {
throw new IllegalArgumentException("Must supply an email");
}
UserProfile profile = (UserProfile) DataAccessUtils.uniqueResult(
hibernateTemplate.findByCriteria(
DetachedCriteria.forClass(UserProfile.class)
.add(Restrictions.eq("email", email)))
);
if (profile == null) {
throw new NoSuchUserException("No user with the email: " + email);
}
if (profile.getVerified()) {
throw new UserAlreadyVerifiedException();
}
profile.setVerificationToken(TokenGenerator.getUniqueToken());
hibernateTemplate.update(profile);
log.debug("Resending verification email to {}", email);
ambraMailer.sendVerificationEmail(email, profile.getVerificationToken());
return profile.getVerificationToken();
}
/**
* {@inheritDoc}
*/
@Override
@Transactional
public String sendForgotPasswordMessage(String email) throws NoSuchUserException {
if (StringUtils.isEmpty(email)) {
throw new IllegalArgumentException("Must supply an email");
}
UserProfile profile = (UserProfile) DataAccessUtils.uniqueResult(
hibernateTemplate.findByCriteria(
DetachedCriteria.forClass(UserProfile.class)
.add(Restrictions.eq("email", email))
.add(Restrictions.eq("verified", true))));
if (profile == null) {
throw new NoSuchUserException("No user with the email: " + email);
}
log.debug("Sending forgotten newPassword message to {}", email);
String passwordResetToken = TokenGenerator.getUniqueToken();
profile.setVerificationToken(passwordResetToken);
hibernateTemplate.update(profile);
ambraMailer.sendForgotPasswordEmail(email, passwordResetToken);
return passwordResetToken;
}
/**
* {@inheritDoc}
*/
@Override
@Transactional(readOnly = true)
public boolean validateVerificationToken(String email, String verificationToken) {
if (StringUtils.isEmpty(email)) {
throw new IllegalArgumentException("Must supply an email");
}
if (StringUtils.isEmpty(verificationToken)) {
throw new IllegalArgumentException("Must supplay an verificationToken");
}
int count = DataAccessUtils.intResult(
hibernateTemplate.findByCriteria(
DetachedCriteria.forClass(UserProfile.class)
.add(Restrictions.eq("email", email))
.add(Restrictions.eq("verificationToken", verificationToken))
.setProjection(Projections.count("email"))
)
);
if (count > 1) {
throw new IllegalStateException("More than one user with email: " + email +
" and verification token: " + verificationToken);
}
return count == 1;
}
/**
* {@inheritDoc}
*/
@Override
@Transactional
public void removeVerificationToken(String email) {
UserProfile profile = (UserProfile) DataAccessUtils.uniqueResult(
hibernateTemplate.findByCriteria(
DetachedCriteria.forClass(UserProfile.class)
.add(Restrictions.eq("email", email))
)
);
if (profile == null) {
throw new IllegalArgumentException("Incorrect email: " + email);
}
log.debug("Resetting token for {}", email);
profile.setVerificationToken(null);
hibernateTemplate.update(profile);
}
/**
* {@inheritDoc}
*/
@Override
@Transactional
public void resetPassword(final String email, final String newPassword) {
for (Map.Entry<String, String> argument : new HashMap<String, String>() {{
put("email", email);
put("new password", newPassword);
}}.entrySet()) {
if (StringUtils.isEmpty(argument.getValue())) {
throw new IllegalArgumentException("Must supply a(n) " + argument.getKey());
}
}
UserProfile profile = (UserProfile) DataAccessUtils.uniqueResult(
hibernateTemplate.findByCriteria(
DetachedCriteria.forClass(UserProfile.class)
.add(Restrictions.eq("email", email)))
);
if (profile == null) {
throw new IllegalArgumentException("Incorrect email: " + email);
}
log.debug("Setting new password for {}", email);
profile.setPassword(passwordDigestService.generateDigest(newPassword));
hibernateTemplate.update(profile);
}
/**
* {@inheritDoc}
*/
@Override
@Transactional
public String sendEmailChangeMessage(final String oldEmail, final String newEmail, final String password)
throws NoSuchUserException, DuplicateUserException {
for (Map.Entry<String, String> argument : new HashMap<String, String>() {{
put("old email", oldEmail);
put("new email", newEmail);
put("password", password);
}}.entrySet()) {
if (StringUtils.isEmpty(argument.getValue())) {
throw new IllegalArgumentException("Must supply a(n) " + argument.getKey());
}
}
UserProfile profile = (UserProfile) DataAccessUtils.uniqueResult(
hibernateTemplate.findByCriteria(
DetachedCriteria.forClass(UserProfile.class)
.add(Restrictions.eq("email", oldEmail))
)
);
if (profile == null) {
throw new NoSuchUserException("No user with the email: " + oldEmail);
}
boolean validPassword = passwordDigestService.verifyPassword(password, profile.getPassword());
if (!validPassword) {
throw new SecurityException("Invalid password");
}
int existingUserCount = DataAccessUtils.intResult(
hibernateTemplate.findByCriteria(
DetachedCriteria.forClass(UserProfile.class)
.add(Restrictions.eq("email", newEmail).ignoreCase())
.setProjection(Projections.count("email")))
);
if (existingUserCount > 0) {
throw new DuplicateUserException(DuplicateUserException.Field.EMAIL);
}
log.debug("sending email change verification to {}", newEmail);
profile.setVerificationToken(TokenGenerator.getUniqueToken());
hibernateTemplate.update(profile);
ambraMailer.sendChangeEmailNotice(oldEmail, newEmail, profile.getVerificationToken());
return profile.getVerificationToken();
}
@Override
@Transactional
public void updateEmailAddress(final String oldEmail, final String newEmail, final String verificationToken) throws
NoSuchUserException, VerificationTokenException, DuplicateUserException {
for (Map.Entry<String, String> argument : new HashMap<String, String>() {{
put("old email", oldEmail);
put("new email", newEmail);
put("verification token", verificationToken);
}}.entrySet()) {
if (StringUtils.isEmpty(argument.getValue())) {
throw new IllegalArgumentException("Must supply a(n) " + argument.getKey());
}
}
UserProfile profile = (UserProfile) DataAccessUtils.uniqueResult(
hibernateTemplate.findByCriteria(
DetachedCriteria.forClass(UserProfile.class)
.add(Restrictions.eq("email", oldEmail))
)
);
if (profile == null) {
throw new NoSuchUserException("No user with the email: " + oldEmail);
} else if (!verificationToken.equals(profile.getVerificationToken())) {
throw new VerificationTokenException("An invalid verification token was given for this user");
}
int existingUserCount = DataAccessUtils.intResult(
hibernateTemplate.findByCriteria(
DetachedCriteria.forClass(UserProfile.class)
.add(Restrictions.eq("email", newEmail).ignoreCase())
.setProjection(Projections.count("email")))
);
if (existingUserCount > 0) {
throw new DuplicateUserException(DuplicateUserException.Field.EMAIL);
}
log.debug("Changing email for {} to {}", oldEmail, newEmail);
profile.setEmail(newEmail);
hibernateTemplate.update(profile);
}
}