From f234e7a0e556d6f6ca5ea890fbef118d09a6e436 Mon Sep 17 00:00:00 2001 From: jensp Date: Sat, 2 Apr 2016 16:42:52 +0000 Subject: [PATCH] CCM NG: OneTimeAuthTokenCleaner: Uses javax.ejb.TimerService to run a cleanup task to remove expired one time auth tokens. git-svn-id: https://svn.libreccm.org/ccm/ccm_ng@3972 8810af33-2d31-482b-a856-94f89814c4df --- .../libreccm/security/OneTimeAuthManager.java | 3 +- .../security/OneTimeAuthTokenCleaner.java | 80 ++++ .../security/ChallengeManagerTest.java | 436 ++++++++++++++++++ 3 files changed, 518 insertions(+), 1 deletion(-) create mode 100644 ccm-core/src/main/java/org/libreccm/security/OneTimeAuthTokenCleaner.java create mode 100644 ccm-core/src/test/java/org/libreccm/security/ChallengeManagerTest.java diff --git a/ccm-core/src/main/java/org/libreccm/security/OneTimeAuthManager.java b/ccm-core/src/main/java/org/libreccm/security/OneTimeAuthManager.java index 3a9e797dc..9cc5cc09b 100644 --- a/ccm-core/src/main/java/org/libreccm/security/OneTimeAuthManager.java +++ b/ccm-core/src/main/java/org/libreccm/security/OneTimeAuthManager.java @@ -22,13 +22,14 @@ import java.time.LocalDateTime; import java.time.ZoneOffset; import java.util.Date; import java.util.List; -import java.util.Optional; + import org.libreccm.configuration.ConfigurationManager; import javax.enterprise.context.RequestScoped; import javax.inject.Inject; import javax.persistence.EntityManager; import javax.persistence.TypedQuery; + import org.apache.commons.lang.RandomStringUtils; /** diff --git a/ccm-core/src/main/java/org/libreccm/security/OneTimeAuthTokenCleaner.java b/ccm-core/src/main/java/org/libreccm/security/OneTimeAuthTokenCleaner.java new file mode 100644 index 000000000..a3d80bc7a --- /dev/null +++ b/ccm-core/src/main/java/org/libreccm/security/OneTimeAuthTokenCleaner.java @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2016 LibreCCM Foundation. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA + */ +package org.libreccm.security; + +import org.libreccm.configuration.ConfigurationManager; + +import java.util.List; + +import javax.annotation.PostConstruct; +import javax.annotation.Resource; +import javax.ejb.Startup; +import javax.ejb.Stateless; +import javax.ejb.Timeout; +import javax.ejb.TimerConfig; +import javax.ejb.TimerService; +import javax.inject.Inject; +import javax.inject.Singleton; +import javax.persistence.EntityManager; +import javax.persistence.TypedQuery; + +/** + * + * @author Jens Pelzetter + */ +@Singleton +@Startup +public class OneTimeAuthTokenCleaner { + + @Resource + private TimerService timerService; + + @Inject + private ConfigurationManager configurationManager; + + @Inject + EntityManager entityManager; + + @Inject + private OneTimeAuthManager oneTimeAuthManager; + + @PostConstruct + public void init() { + final OneTimeAuthConfig config = configurationManager.findConfiguration( + OneTimeAuthConfig.class); + + final long interval = config.getTokenValid() * 1000; + + timerService.createIntervalTimer(interval, interval, new TimerConfig()); + } + + @Timeout + public void cleanupTokens() { + final TypedQuery query = entityManager.createQuery( + "SELECT t FROM OneTimeAuthToken t", OneTimeAuthToken.class); + final List tokens = query.getResultList(); + + tokens.stream() + .filter((token) -> (!oneTimeAuthManager.isValid(token))) + .forEach((token) -> { + oneTimeAuthManager.invalidate(token); + }); + } + +} diff --git a/ccm-core/src/test/java/org/libreccm/security/ChallengeManagerTest.java b/ccm-core/src/test/java/org/libreccm/security/ChallengeManagerTest.java new file mode 100644 index 000000000..904540918 --- /dev/null +++ b/ccm-core/src/test/java/org/libreccm/security/ChallengeManagerTest.java @@ -0,0 +1,436 @@ +/* + * Copyright (C) 2016 LibreCCM Foundation. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA + */ +package org.libreccm.security; + +import org.jboss.arquillian.container.test.api.Deployment; +import org.jboss.arquillian.container.test.api.ShouldThrowException; +import org.jboss.arquillian.junit.Arquillian; +import org.jboss.arquillian.junit.InSequence; +import org.jboss.arquillian.persistence.CreateSchema; +import org.jboss.arquillian.persistence.PersistenceTest; +import org.jboss.arquillian.persistence.ShouldMatchDataSet; +import org.jboss.arquillian.persistence.UsingDataSet; +import org.jboss.arquillian.transaction.api.annotation.TransactionMode; +import org.jboss.arquillian.transaction.api.annotation.Transactional; +import org.jboss.shrinkwrap.api.IllegalArchivePathException; +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.EmptyAsset; +import org.jboss.shrinkwrap.api.spec.WebArchive; +import org.jboss.shrinkwrap.resolver.api.maven.Maven; +import org.jboss.shrinkwrap.resolver.api.maven.PomEquippedResolveStage; +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.experimental.categories.Category; +import org.junit.runner.RunWith; +import org.libreccm.tests.categories.IntegrationTest; + +import java.io.File; + +import javax.inject.Inject; +import javax.servlet.ServletContext; + +import static org.hamcrest.Matchers.*; +import static org.junit.Assert.*; + +/** + * + * @author Jens Pelzetter + */ +@Category(IntegrationTest.class) +@RunWith(Arquillian.class) +@PersistenceTest +@Transactional(TransactionMode.COMMIT) +@CreateSchema("create_ccm_core_schema.sql") +public class ChallengeManagerTest { + + @Inject + private ChallengeManager challengeManager; + + @Inject + private UserRepository userRepository; + + @Inject + private UserManager userManager; + + @Inject + private ServletContext servletContext; + + public ChallengeManagerTest() { + } + + @BeforeClass + public static void setUpClass() { + } + + @AfterClass + public static void tearDownClass() { + } + + @Before + public void setUp() { + } + + @After + public void tearDown() { + } + + @Deployment + public static WebArchive createDeployment() { + final PomEquippedResolveStage pom = Maven + .resolver() + .loadPomFromFile("pom.xml"); + final PomEquippedResolveStage dependencies = pom. + importCompileAndRuntimeDependencies(); + final File[] libs = dependencies.resolve().withTransitivity().asFile(); + + for (File lib : libs) { + System.err.printf("Adding file '%s' to test archive...%n", + lib.getName()); + } + + return ShrinkWrap + .create(WebArchive.class, + "LibreCCM-org.libreccm.security.ChallengeManagerTest.war") + .addPackage(org.libreccm.security.OneTimeAuthManager.class. + getPackage()) + .addPackage(org.libreccm.core.CcmObject.class.getPackage()) + .addPackage(org.libreccm.categorization.Categorization.class. + getPackage()) + .addPackage(org.libreccm.cdi.utils.CdiUtil.class.getPackage()) + .addPackage( + org.libreccm.configuration.ConfigurationManager.class. + getPackage()) + .addClass(com.arsdigita.kernel.KernelConfig.class) + .addClass(com.arsdigita.kernel.security.SecurityConfig.class) + .addPackage(org.libreccm.l10n.LocalizedString.class.getPackage()) + .addPackage(org.libreccm.web.CcmApplication.class.getPackage()) + .addPackage(org.libreccm.workflow.Workflow.class.getPackage()) + .addPackage(org.libreccm.jpa.EntityManagerProducer.class. + getPackage()) + .addPackage(org.libreccm.jpa.utils.MimeTypeConverter.class. + getPackage()) + .addPackage(org.libreccm.testutils.EqualsVerifier.class. + getPackage()) + .addPackage(org.libreccm.tests.categories.IntegrationTest.class. + getPackage()) + .addAsLibraries(libs) + .addAsResource("test-persistence.xml", + "META-INF/persistence.xml") + .addAsWebInfResource("test-web.xml", "WEB-INF/web.xml") + .addAsWebInfResource(EmptyAsset.INSTANCE, "WEB-INF/beans.xml"); + } + + @Test + @InSequence(10) + public void isManagerInjected() { + assertThat(challengeManager, is(not(nullValue()))); + } + + @Test + @InSequence(20) + public void isServletContextInjected() { + assertThat(servletContext, is(not(nullValue()))); + } + + @Test + @UsingDataSet("datasets/org/libreccm/security/ChallengeManagerTest/data.xml") + @ShouldMatchDataSet( + value = "datasets/org/libreccm/security/ChallengeManagerTest/" + + "after-create-email-verification.xml", + excludeColumns = {"token_id", "token", "valid_until"}) + @InSequence(1100) + public void createEmailVerification() { + final String path = String.format("%s/%s/register/verify-email", + servletContext.getVirtualServerName(), + servletContext.getContextPath()); + final String expected = String.format( + "Please follow the following link to finish the email verfication " + + "process:\n" + + "\n" + + "%s" + + "\n\n" + + "Please be aware that your verification token expires" + + "at", + path); + + final User user = userRepository.findByName("mmuster"); + final String mail = challengeManager.createEmailVerification(user); + + assertThat(mail, is(not(nullValue()))); + assertThat(mail.isEmpty(), is(false)); + assertThat(mail.startsWith(expected), is(true)); + } + + @Test(expected = IllegalArgumentException.class) + @UsingDataSet("datasets/org/libreccm/security/ChallengeManagerTest/data.xml") + @ShouldThrowException(IllegalArgumentException.class) + @InSequence(1200) + public void createEmailVerificationNullUser() { + challengeManager.createEmailVerification(null); + } + + @Test + @UsingDataSet( + "datasets/org/libreccm/security/ChallengeManagerTest/finish-email-verification.xml") + @ShouldMatchDataSet( + value = "datasets/org/libreccm/security/ChallengeManagerTest/" + + "after-finish-email-verification.xml") + @InSequence(1300) + public void finishEmailVerification() throws ChallengeFailedException { + final User user = userRepository.findByName("mmuster"); + challengeManager.finishEmailVerification( + user, + "biXOpuxIPXuRgx9jhk1PzZVIeKGaTmg2qTKoTQ4tl9iiweQ0e5mfmdFI1KjDwjPi"); + } + + @Test(expected = IllegalArgumentException.class) + @UsingDataSet( + "datasets/org/libreccm/security/ChallengeManagerTest/finish-email-verification.xml") + @ShouldMatchDataSet( + value = "datasets/org/libreccm/security/ChallengeManagerTest/" + + "data.xml") + @ShouldThrowException(IllegalArgumentException.class) + @InSequence(1400) + public void finishEmailVerificationNullUser() + throws ChallengeFailedException { + + challengeManager.finishEmailVerification( + null, + "biXOpuxIPXuRgx9jhk1PzZVIeKGaTmg2qTKoTQ4tl9iiweQ0e5mfmdFI1KjDwjPi"); + } + + @Test(expected = IllegalArgumentException.class) + @UsingDataSet( + "datasets/org/libreccm/security/ChallengeManagerTest/finish-email-verification.xml") + @ShouldMatchDataSet( + value = "datasets/org/libreccm/security/ChallengeManagerTest/" + + "data.xml") + @ShouldThrowException(IllegalArgumentException.class) + @InSequence(1500) + public void finishEmailVerificationNullToken() + throws ChallengeFailedException { + + final User user = userRepository.findByName("mmuster"); + challengeManager.finishEmailVerification( + user, null); + } + + @Test + @UsingDataSet("datasets/org/libreccm/security/ChallengeManagerTest/data.xml") + @ShouldMatchDataSet( + value = "datasets/org/libreccm/security/ChallengeManagerTest/" + + "after-create-account-activation.xml", + excludeColumns = {"token_id", "token", "valid_until"}) + @InSequence(2100) + public void createAccountActivation() { + final String path = String.format("%s/%s/register/activate-account", + servletContext.getVirtualServerName(), + servletContext.getContextPath()); + final String expected = String.format( + "Please follow the following link to enable your new account:\n" + + "\n" + + "%s" + + "\n\n" + + "Please be aware that you must activate your account before", + path); + + final User user = userRepository.findByName("mmuster"); + final String mail = challengeManager.createAccountActivation(user); + + assertThat(mail, is(not(nullValue()))); + assertThat(mail.isEmpty(), is(false)); + assertThat(mail.startsWith(expected), is(true)); + } + + @Test(expected = IllegalArgumentException.class) + @UsingDataSet("datasets/org/libreccm/security/ChallengeManagerTest/data.xml") + @ShouldThrowException(IllegalArgumentException.class) + @InSequence(2200) + public void createAccountActivationNullUser() { + challengeManager.createAccountActivation(null); + } + + @Test + @UsingDataSet("datasets/org/libreccm/security/ChallengeManagerTest/" + + "finish-account-activation.xml") + @ShouldMatchDataSet( + value = "datasets/org/libreccm/security/ChallengeManagerTest/" + + "after-finish-account-activation.xml") + @InSequence(2300) + public void finishAccountActivation() throws ChallengeFailedException { + final User user = userRepository.findByName("mmuster"); + challengeManager.finishAccountActivation( + user, + "biXOpuxIPXuRgx9jhk1PzZVIeKGaTmg2qTKoTQ4tl9iiweQ0e5mfmdFI1KjDwjPi"); + } + + @Test(expected = IllegalArgumentException.class) + @UsingDataSet("datasets/org/libreccm/security/ChallengeManagerTest/" + + "finish-account-activation.xml") + @ShouldMatchDataSet( + value = "datasets/org/libreccm/security/ChallengeManagerTest/" + + "finish-account-activation.xml") + @ShouldThrowException(IllegalArgumentException.class) + @InSequence(2400) + public void finishAccountActivationNullUser() throws + ChallengeFailedException { + challengeManager.finishAccountActivation( + null, + "biXOpuxIPXuRgx9jhk1PzZVIeKGaTmg2qTKoTQ4tl9iiweQ0e5mfmdFI1KjDwjPi"); + } + + @Test(expected = IllegalArgumentException.class) + @UsingDataSet("datasets/org/libreccm/security/ChallengeManagerTest/" + + "finish-account-activation.xml") + @ShouldMatchDataSet( + value = "datasets/org/libreccm/security/ChallengeManagerTest/" + + "finish-account-activation.xml") + @ShouldThrowException(IllegalArgumentException.class) + @InSequence(2400) + public void finishAccountActivationNullToken() throws + ChallengeFailedException { + + final User user = userRepository.findByName("mmuster"); + challengeManager.finishAccountActivation( + user, null); + } + + @Test + @UsingDataSet("datasets/org/libreccm/security/ChallengeManagerTest/data.xml") + @ShouldMatchDataSet( + value = "datasets/org/libreccm/security/ChallengeManagerTest/" + + "after-create-password-recover.xml", + excludeColumns = {"token_id", "token", "valid_until"}) + @InSequence(3100) + public void createPasswordRecover() { + final String path = String.format("%s/%s/register/recover-password", + servletContext.getVirtualServerName(), + servletContext.getContextPath()); + final String expected = String.format( + "Please follow the following link to complete the password recover " + + "process:\n" + + "\n" + + "%s" + + "\n\n" + + "Please be aware that you must complete the process until", + path); + + final User user = userRepository.findByName("mmuster"); + final String mail = challengeManager.createPasswordRecover(user); + + assertThat(mail, is(not(nullValue()))); + assertThat(mail.isEmpty(), is(false)); + assertThat(mail.startsWith(expected), is(true)); + } + + @Test(expected = IllegalArgumentException.class) + @UsingDataSet("datasets/org/libreccm/security/ChallengeManagerTest/data.xml") + @ShouldThrowException(IllegalArgumentException.class) + @InSequence(3200) + public void createPasswordRecoverNullUser() { + challengeManager.createPasswordRecover(null); + } + + @Test + @UsingDataSet("datasets/org/libreccm/security/ChallengeManagerTest/" + + "finish-password-recover.xml") + @ShouldMatchDataSet( + value = "datasets/org/libreccm/security/ChallengeManagerTest/" + + "after-finish-password-recover.xml", + excludeColumns = "password") + @InSequence(3300) + public void finishPasswordRecover() throws ChallengeFailedException { + final User user = userRepository.findByName("mmuster"); + challengeManager.finishPasswordRecover( + user, + "biXOpuxIPXuRgx9jhk1PzZVIeKGaTmg2qTKoTQ4tl9iiweQ0e5mfmdFI1KjDwjPi", + "new-password"); + + final User after = userRepository.findByName("mmuster"); + assertThat(userManager.verifyPassword(after, "new-password"), is(true)); + } + + @Test(expected = IllegalArgumentException.class) + @UsingDataSet("datasets/org/libreccm/security/ChallengeManagerTest/" + + "finish-password-recover.xml") + @ShouldMatchDataSet( + value = "datasets/org/libreccm/security/ChallengeManagerTest/" + + "finish-password-recover.xml") + @ShouldThrowException(IllegalArgumentException.class) + @InSequence(3400) + public void finishPasswordRecoverNullUser() throws ChallengeFailedException { + challengeManager.finishPasswordRecover( + null, + "biXOpuxIPXuRgx9jhk1PzZVIeKGaTmg2qTKoTQ4tl9iiweQ0e5mfmdFI1KjDwjPi", + "new-password"); + } + + @Test(expected = IllegalArgumentException.class) + @UsingDataSet("datasets/org/libreccm/security/ChallengeManagerTest/" + + "finish-password-recover.xml") + @ShouldMatchDataSet( + value = "datasets/org/libreccm/security/ChallengeManagerTest/" + + "finish-password-recover.xml") + @ShouldThrowException(IllegalArgumentException.class) + @InSequence(3400) + public void finishPasswordRecoverNullToken() + throws ChallengeFailedException { + final User user = userRepository.findByName("mmuster"); + challengeManager.finishPasswordRecover( + user, null, "new-password"); + } + + @Test(expected = IllegalArgumentException.class) + @UsingDataSet("datasets/org/libreccm/security/ChallengeManagerTest/" + + "finish-password-recover.xml") + @ShouldMatchDataSet( + value = "datasets/org/libreccm/security/ChallengeManagerTest/" + + "finish-password-recover.xml") + @ShouldThrowException(IllegalArgumentException.class) + @InSequence(3500) + public void finishPasswordRecoverNullPassword() + throws ChallengeFailedException { + final User user = userRepository.findByName("mmuster"); + challengeManager.finishPasswordRecover( + user, + "biXOpuxIPXuRgx9jhk1PzZVIeKGaTmg2qTKoTQ4tl9iiweQ0e5mfmdFI1KjDwjPi", + null); + } + + @Test(expected = IllegalArgumentException.class) + @UsingDataSet("datasets/org/libreccm/security/ChallengeManagerTest/" + + "finish-password-recover.xml") + @ShouldMatchDataSet( + value = "datasets/org/libreccm/security/ChallengeManagerTest/" + + "finish-password-recover.xml") + @ShouldThrowException(IllegalArgumentException.class) + @InSequence(3600) + public void finishPasswordRecoverEmptyPassword() + throws ChallengeFailedException { + final User user = userRepository.findByName("mmuster"); + challengeManager.finishPasswordRecover( + user, + "biXOpuxIPXuRgx9jhk1PzZVIeKGaTmg2qTKoTQ4tl9iiweQ0e5mfmdFI1KjDwjPi", + ""); + } + +}