Commit 38b9dfbb authored by Matija Obreza's avatar Matija Obreza

Extracted TokenVerificationService

parent ebf42a71
......@@ -32,10 +32,10 @@ import org.genesys2.server.model.AuditedModel;
@Table(name = "verificationtoken")
public class VerificationToken extends AuditedModel {
@Column(length = 36, nullable = false, unique = true)
public String uuid;
private String uuid;
@Column(name = "secret", length = 4, nullable = false)
public String key;
private String key;
@Column(length = 36, nullable = false)
private String purpose;
......
......@@ -17,6 +17,7 @@
package org.genesys2.server.service;
import org.genesys2.server.model.impl.User;
import org.genesys2.server.service.TokenVerificationService.NoSuchVerificationTokenException;
public interface EMailVerificationService {
......@@ -26,7 +27,7 @@ public interface EMailVerificationService {
void cancelValidation(String tokenUuid);
boolean validateEMail(String tokenUuid, String key);
void validateEMail(String tokenUuid, String key) throws NoSuchVerificationTokenException;
boolean changePassword(String tokenUuid, String key, String password);
void changePassword(String tokenUuid, String key, String password) throws NoSuchVerificationTokenException;
}
/**
* Copyright 2014 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.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
package org.genesys2.server.service;
import org.genesys2.server.model.impl.VerificationToken;
public interface TokenVerificationService {
/**
* Generate a new verification token (uuid+key) for specified tokenPurpose
*
* @param tokenPurpose
* @param data
* @return
*/
VerificationToken generateToken(String tokenPurpose, String data);
/**
* Cancel (remove) token
*
* @param tokenUuid
* @throws NoSuchVerificationTokenException
*/
void cancel(String tokenUuid) throws NoSuchVerificationTokenException;
/**
* Check token validity, remove it from persistence
*
* @param purpose
* @param tokenUuid
* @param key
* @return The consumed token
* @throws NoSuchVerificationTokenException
*/
VerificationToken consumeToken(String purpose, String tokenUuid, String key) throws NoSuchVerificationTokenException;
public static class NoSuchVerificationTokenException extends Exception {
}
}
......@@ -19,17 +19,17 @@ package org.genesys2.server.service.impl;
import java.text.MessageFormat;
import java.util.Locale;
import org.apache.commons.lang.RandomStringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.genesys2.server.exception.UserException;
import org.genesys2.server.model.impl.Article;
import org.genesys2.server.model.impl.User;
import org.genesys2.server.model.impl.VerificationToken;
import org.genesys2.server.persistence.domain.VerificationTokenRepository;
import org.genesys2.server.service.ContentService;
import org.genesys2.server.service.EMailService;
import org.genesys2.server.service.EMailVerificationService;
import org.genesys2.server.service.TokenVerificationService;
import org.genesys2.server.service.TokenVerificationService.NoSuchVerificationTokenException;
import org.genesys2.server.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
......@@ -43,7 +43,7 @@ public class EMailVerificationServiceImpl implements EMailVerificationService {
private static final Log LOG = LogFactory.getLog(EMailVerificationServiceImpl.class);
@Autowired
private VerificationTokenRepository verificationTokenRepository;
private TokenVerificationService tokenVerificationService;
@Autowired
private EMailService emailService;
......@@ -61,7 +61,7 @@ public class EMailVerificationServiceImpl implements EMailVerificationService {
@Transactional
public void sendVerificationEmail(User user) {
// Generate new token
VerificationToken verificationToken = generateToken("email-verification", user.getUuid());
VerificationToken verificationToken = tokenVerificationService.generateToken("email-verification", user.getUuid());
Article article = contentService.getGlobalArticle("smtp.email-verification", Locale.ENGLISH);
String mailSubject = article.getTitle();
......@@ -74,7 +74,7 @@ public class EMailVerificationServiceImpl implements EMailVerificationService {
@Transactional
public void sendPasswordResetEmail(User user) {
// Generate new token
VerificationToken verificationToken = generateToken("email-password", user.getUuid());
VerificationToken verificationToken = tokenVerificationService.generateToken("email-password", user.getUuid());
Article article = contentService.getGlobalArticle("smtp.email-password", Locale.ENGLISH);
String mailSubject = article.getTitle();
......@@ -83,81 +83,34 @@ public class EMailVerificationServiceImpl implements EMailVerificationService {
emailService.sendMail(user.getEmail(), user.getName(), mailSubject, mailBody);
}
private VerificationToken generateToken(String tokenPurpose, String data) {
VerificationToken token = new VerificationToken();
token.setPurpose(tokenPurpose);
// Store data
token.setData(data);
token.setKey(RandomStringUtils.randomAlphanumeric(4).toUpperCase());
verificationTokenRepository.save(token);
return token;
}
@Override
@Transactional
public void cancelValidation(String tokenUuid) {
VerificationToken verificationToken = verificationTokenRepository.findByUuid(tokenUuid);
if (verificationToken == null) {
LOG.warn("Canceling verification token failed. No such verification token " + tokenUuid);
} else {
LOG.warn("Canceling verification token " + tokenUuid);
verificationTokenRepository.delete(verificationToken);
try {
tokenVerificationService.cancel(tokenUuid);
} catch (NoSuchVerificationTokenException e) {
// Silently cancel exception
}
}
@Override
@Transactional
public boolean validateEMail(String tokenUuid, String key) {
VerificationToken verificationToken = verificationTokenRepository.findByPurposeAndUuid("email-verification", tokenUuid);
if (verificationToken == null) {
LOG.warn("No such verification token " + tokenUuid + " key=" + key);
return false;
}
if (!verificationToken.getKey().equals(key)) {
LOG.error("Email verification key invalid for token=" + verificationToken.getUuid() + " providedKey=" + key);
return false;
}
try {
userService.userEmailValidated(verificationToken.getData());
// Remove token
verificationTokenRepository.delete(verificationToken);
return true;
} catch (NullPointerException e) {
LOG.error(e, e);
}
return false;
public void validateEMail(String tokenUuid, String key) throws NoSuchVerificationTokenException {
VerificationToken consumedToken = tokenVerificationService.consumeToken("email-verification", tokenUuid, key);
userService.userEmailValidated(consumedToken.getData());
}
@Override
@Transactional
public boolean changePassword(String tokenUuid, String key, String password) {
VerificationToken verificationToken = verificationTokenRepository.findByPurposeAndUuid("email-password", tokenUuid);
if (verificationToken == null) {
LOG.warn("No such verification token " + tokenUuid + " key=" + key);
return false;
}
if (!verificationToken.getKey().equals(key)) {
LOG.error("Password reset verification key invalid for token=" + verificationToken.getUuid() + " providedKey=" + key);
return false;
}
public void changePassword(String tokenUuid, String key, String password) throws NoSuchVerificationTokenException {
VerificationToken consumedToken = tokenVerificationService.consumeToken("email-password", tokenUuid, key);
try {
User user = userService.getUserByUuid(verificationToken.getData());
User user = userService.getUserByUuid(consumedToken.getData());
userService.updatePassword(user.getId(), password);
// Remove token
verificationTokenRepository.delete(verificationToken);
return true;
} catch (UserException e) {
LOG.error(e.getMessage(), e);
}
return false;
}
}
/**
* Copyright 2014 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.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
package org.genesys2.server.service.impl;
import org.apache.commons.lang.RandomStringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.genesys2.server.model.impl.VerificationToken;
import org.genesys2.server.persistence.domain.VerificationTokenRepository;
import org.genesys2.server.service.TokenVerificationService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
@Transactional(readOnly = true)
public class TokenVerificationServiceImpl implements TokenVerificationService {
private static final Log LOG = LogFactory.getLog(TokenVerificationServiceImpl.class);
@Autowired
private VerificationTokenRepository verificationTokenRepository;
@Override
@Transactional
public VerificationToken generateToken(String tokenPurpose, String data) {
VerificationToken token = new VerificationToken();
token.setPurpose(tokenPurpose);
// Store data
token.setData(data);
token.setKey(RandomStringUtils.randomAlphanumeric(4).toUpperCase());
token = verificationTokenRepository.save(token);
return token;
}
@Override
@Transactional
public void cancel(String tokenUuid) throws NoSuchVerificationTokenException {
VerificationToken verificationToken = verificationTokenRepository.findByUuid(tokenUuid);
if (verificationToken == null) {
LOG.warn("Canceling verification token failed. No such verification token " + tokenUuid);
throw new NoSuchVerificationTokenException();
} else {
LOG.warn("Canceling verification token " + tokenUuid);
verificationTokenRepository.delete(verificationToken);
}
}
@Override
@Transactional
public VerificationToken consumeToken(String purpose, String tokenUuid, String key) throws NoSuchVerificationTokenException {
VerificationToken verificationToken = verificationTokenRepository.findByPurposeAndUuid(purpose, tokenUuid);
if (verificationToken == null) {
LOG.warn("No such verification token " + tokenUuid + " key=" + key);
throw new NoSuchVerificationTokenException();
}
if (!verificationToken.getKey().equals(key)) {
LOG.error("Email verification key invalid for token=" + verificationToken.getUuid() + " providedKey=" + key);
throw new NoSuchVerificationTokenException();
}
// Consume token
verificationTokenRepository.delete(verificationToken);
return verificationToken;
}
}
package org.genesys2.server.mock.service;
import org.genesys2.server.model.impl.VerificationToken;
import org.genesys2.server.service.TokenVerificationService.NoSuchVerificationTokenException;
public interface TokenConsumerService {
void noExceptions(VerificationToken token) throws NoSuchVerificationTokenException;
void throwRuntimeException(VerificationToken token) throws NoSuchVerificationTokenException;
void noToken() throws NoSuchVerificationTokenException;
void throwException(VerificationToken token) throws Exception;
}
package org.genesys2.server.mock.service;
import org.genesys2.server.model.impl.VerificationToken;
import org.genesys2.server.service.TokenVerificationService;
import org.genesys2.server.service.TokenVerificationService.NoSuchVerificationTokenException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
/**
* Mock service to test verification token behavior with exceptions
*
* @author matijaobreza
*
*/
@Service
@Transactional(readOnly = true)
public class TokenConsumerServiceImpl implements TokenConsumerService {
@Autowired
private TokenVerificationService tokenVerificationService;
@Override
@Transactional
public void throwRuntimeException(VerificationToken token) throws NoSuchVerificationTokenException {
tokenVerificationService.consumeToken(token.getPurpose(), token.getUuid(), token.getKey());
throw new RuntimeException();
}
@Override
@Transactional(rollbackFor = Exception.class)
public void throwException(VerificationToken token) throws Exception {
tokenVerificationService.consumeToken(token.getPurpose(), token.getUuid(), token.getKey());
throw new Exception();
}
@Override
@Transactional
public void noExceptions(VerificationToken token) throws NoSuchVerificationTokenException {
tokenVerificationService.consumeToken(token.getPurpose(), token.getUuid(), token.getKey());
}
@Override
@Transactional
public void noToken() throws NoSuchVerificationTokenException {
tokenVerificationService.consumeToken("nopurpose", "no-such-uuid", "wrongkey");
throw new RuntimeException("Should not get here");
}
}
/**
* Copyright 2014 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.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
package org.genesys2.server.test;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import org.genesys2.server.mock.service.TokenConsumerService;
import org.genesys2.server.mock.service.TokenConsumerServiceImpl;
import org.genesys2.server.model.impl.VerificationToken;
import org.genesys2.server.service.TokenVerificationService;
import org.genesys2.server.service.TokenVerificationService.NoSuchVerificationTokenException;
import org.genesys2.server.service.impl.TokenVerificationServiceImpl;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
/**
* Tests for {@link TokenVerificationServiceImpl}
*
* @author matijaobreza
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = TokenVerificationServiceTest.Config.class, initializers = PropertyPlacholderInitializer.class)
public class TokenVerificationServiceTest {
@ComponentScan(basePackages = { "org.genesys2.server.persistence.domain" })
public static class Config extends JpaDataConfig {
@Bean
public TokenVerificationService tokenVerificationService() {
return new TokenVerificationServiceImpl();
}
@Bean
public TokenConsumerService tokenConsumerService() {
return new TokenConsumerServiceImpl();
}
}
@Autowired
private TokenVerificationService tokenVerificationService;
@Autowired
private TokenConsumerService tokenConsumerService;
@Test(expected = NoSuchVerificationTokenException.class)
public void consumeException() throws NoSuchVerificationTokenException {
tokenVerificationService.consumeToken("purpose1", "no-such-uuid", "wrongkey");
}
@Test(expected = NoSuchVerificationTokenException.class)
public void testGenerateAndFail() throws NoSuchVerificationTokenException {
VerificationToken t = tokenVerificationService.generateToken("purpose1", null);
assertTrue("ID should be assigned", t.getId() != null);
assertTrue("UUID should be assigned", t.getUuid() != null);
assertTrue("Data should be null", t.getData() == null);
tokenVerificationService.consumeToken("purpose1", t.getUuid(), "wrongkey");
}
@Test
public void testGenerateAndConsume() {
VerificationToken t = tokenVerificationService.generateToken("purpose1", null);
assertTrue("ID should be assigned", t.getId() != null);
assertTrue("UUID should be assigned", t.getUuid() != null);
assertTrue("Data should be null", t.getData() == null);
try {
tokenVerificationService.consumeToken("purpose1", t.getUuid(), "wrongkey");
fail("Exception not thrown");
} catch (NoSuchVerificationTokenException e) {
}
try {
VerificationToken ct = tokenVerificationService.consumeToken("purpose1", t.getUuid(), t.getKey());
assertTrue("Token not consumed", ct != null);
} catch (NoSuchVerificationTokenException e) {
fail("Token not found");
}
try {
tokenVerificationService.consumeToken("purpose1", t.getUuid(), t.getKey());
fail("Token still found");
} catch (NoSuchVerificationTokenException e) {
}
}
@Test(expected = NoSuchVerificationTokenException.class)
public void cancelException() throws NoSuchVerificationTokenException {
tokenVerificationService.cancel("no-such-uuid");
fail("Token should not be found");
}
@Test
public void testGenerateAndCancel() {
VerificationToken t = tokenVerificationService.generateToken("purpose1", null);
assertTrue("ID should be assigned", t.getId() != null);
assertTrue("UUID should be assigned", t.getUuid() != null);
assertTrue("Data should be null", t.getData() == null);
try {
tokenVerificationService.cancel(t.getUuid());
} catch (NoSuchVerificationTokenException e) {
fail("Token not canceled");
}
try {
tokenVerificationService.cancel(t.getUuid());
fail("Exception expected!");
} catch (NoSuchVerificationTokenException e) {
}
}
@Test
public void consumeTokenNoExceptions() {
VerificationToken t = tokenVerificationService.generateToken("purpose1", null);
assertTrue("Token not created", t != null);
try {
tokenConsumerService.noExceptions(t);
} catch (NoSuchVerificationTokenException e) {
fail("Token not found");
}
try {
tokenVerificationService.consumeToken(t.getPurpose(), t.getUuid(), t.getKey());
fail("Token should not be available");
} catch (NoSuchVerificationTokenException e) {
}
}
@Test
public void testTokenAfterRuntime() {
VerificationToken t = tokenVerificationService.generateToken("purpose1", null);
assertTrue("Token not created", t != null);
try {
tokenConsumerService.throwRuntimeException(t);
fail("RuntimeException expected");
} catch (NoSuchVerificationTokenException e) {
fail("Token not found");
} catch (RuntimeException e) {
// ok
}
try {
tokenVerificationService.consumeToken(t.getPurpose(), t.getUuid(), t.getKey());
} catch (NoSuchVerificationTokenException e) {
fail("Token should still be available");
}
}
@Test
public void testTokenException() {
VerificationToken t = tokenVerificationService.generateToken("purpose1", null);
assertTrue("Token not created", t != null);
try {
tokenConsumerService.throwException(t);
fail("Exception expected");
} catch (Exception e) {
// ok
}
try {
tokenVerificationService.consumeToken(t.getPurpose(), t.getUuid(), t.getKey());
} catch (NoSuchVerificationTokenException e) {
fail("Token should still be available");
}
}
@Test
public void noConsume() {
try {
tokenConsumerService.noToken();
fail("Token should not be found");
} catch (NoSuchVerificationTokenException e) {
}
}
}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.