diff --git a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/conversion/IdentifierService.java b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/conversion/IdentifierService.java index 01491d5f722c9287d2b1115f692bd5f7e6a6e81e..d412bdefb02c9c5a4b5aa80e106393fe5ba087c1 100644 --- a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/conversion/IdentifierService.java +++ b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/conversion/IdentifierService.java @@ -16,7 +16,6 @@ package eu.europa.ec.edelivery.smp.conversion; import eu.europa.ec.edelivery.smp.identifiers.Identifier; import eu.europa.ec.edelivery.smp.identifiers.IdentifierFormatter; import eu.europa.ec.edelivery.smp.identifiers.types.EBCorePartyIdFormatterType; -import eu.europa.ec.edelivery.smp.identifiers.types.OasisSMPFormatterType; import eu.europa.ec.edelivery.smp.logging.SMPLogger; import eu.europa.ec.edelivery.smp.logging.SMPLoggerFactory; import eu.europa.ec.edelivery.smp.services.ConfigurationService; @@ -51,8 +50,8 @@ public class IdentifierService { /** * Update ParticipantIdentifierFormatter for non null values. Null values are ignored * - * @param caseInsensitiveSchemas - * @param mandatoryScheme + * @param caseInsensitiveSchemas list of schemas for which case insensitivity is required + * @param mandatoryScheme if true, scheme is mandatory */ public void configureParticipantIdentifierFormatter(List<String> caseInsensitiveSchemas, Boolean mandatoryScheme, Pattern allowedSchemeRegExp) { if (caseInsensitiveSchemas != null) { @@ -62,7 +61,7 @@ public class IdentifierService { } if (mandatoryScheme != null) { - participantIdentifierFormatter.setSchemeMandatory(mandatoryScheme.booleanValue()); + participantIdentifierFormatter.setSchemeMandatory(mandatoryScheme); } else { LOG.debug("Skip configure ParticipantIdentifierFormatter.mandatoryScheme for null value"); } @@ -74,6 +73,11 @@ public class IdentifierService { } } + /** + * Update DocumentIdentifierFormatter for non null values. Null values are ignored + * + * @param caseInsensitiveSchemas list of schemas for which case insensitivity is required + */ public void configureDocumentIdentifierFormatter(List<String> caseInsensitiveSchemas) { if (caseInsensitiveSchemas != null) { documentIdentifierFormatter.setCaseSensitiveSchemas(caseInsensitiveSchemas); diff --git a/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/data/dao/TestUtilsDao.java b/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/data/dao/TestUtilsDao.java index 7045423cdf18164033645b274016a22302831835..5a386e165a8ef715c90d7c9b4fb0970af4fdaa9c 100644 --- a/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/data/dao/TestUtilsDao.java +++ b/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/data/dao/TestUtilsDao.java @@ -11,13 +11,12 @@ import eu.europa.ec.edelivery.smp.data.model.doc.DBSubresource; import eu.europa.ec.edelivery.smp.data.model.ext.DBExtension; import eu.europa.ec.edelivery.smp.data.model.ext.DBResourceDef; import eu.europa.ec.edelivery.smp.data.model.ext.DBSubresourceDef; -import eu.europa.ec.edelivery.smp.data.model.user.DBDomainMember; -import eu.europa.ec.edelivery.smp.data.model.user.DBGroupMember; -import eu.europa.ec.edelivery.smp.data.model.user.DBResourceMember; -import eu.europa.ec.edelivery.smp.data.model.user.DBUser; +import eu.europa.ec.edelivery.smp.data.model.user.*; import eu.europa.ec.edelivery.smp.logging.SMPLogger; import eu.europa.ec.edelivery.smp.logging.SMPLoggerFactory; import eu.europa.ec.edelivery.smp.testutil.TestDBUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.crypto.bcrypt.BCrypt; import org.springframework.stereotype.Repository; import javax.persistence.EntityManager; @@ -37,6 +36,9 @@ import static org.junit.jupiter.api.Assertions.assertNotNull; */ @Repository public class TestUtilsDao { + + @Autowired + UserDao userDao; @PersistenceContext protected EntityManager memEManager; private static final SMPLogger LOG = SMPLoggerFactory.getLogger(TestUtilsDao.class); @@ -211,8 +213,16 @@ public class TestUtilsDao { return; } user1 = createDBUserByUsername(USERNAME_1); + DBCredential c1 = TestDBUtils.createDBCredentialForUser(user1, null, null, null); + c1.setValue(BCrypt.hashpw(USERNAME_1_PASSWORD, BCrypt.gensalt())); + user1.getUserCredentials().add(c1); user2 = createDBUserByCertificate(USER_CERT_2); user3 = createDBUserByUsername(USERNAME_3); + DBCredential c3 = TestDBUtils.createDBCredentialForUserAccessToken(user3, null, null, null); + c3.setValue(BCrypt.hashpw(USERNAME_3_AT_PASSWORD, BCrypt.gensalt())); + c3.setName(USERNAME_3_AT); + user3.getUserCredentials().add(c3); + user4 = createDBUserByUsername(USERNAME_4); user5 = createDBUserByUsername(USERNAME_5); @@ -229,6 +239,18 @@ public class TestUtilsDao { assertNotNull(user5.getId()); } + @Transactional + public void deactivateUser(String username) { + DBUser user = userDao.findUserByUsername(username).get(); + user.setActive(false); + persistFlushDetach(user); + } + + @Transactional + public void updateCredentials(DBCredential credential) { + merge(credential); + } + /** * Create domain members for * user1 on domain 1 as Admin diff --git a/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/services/AbstractServiceIntegrationTest.java b/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/services/AbstractServiceIntegrationTest.java index 595a631a6138a1f14f796b2f278f275e9acf5a66..708cc7f03a609540433cf2606f298658a20a44b5 100644 --- a/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/services/AbstractServiceIntegrationTest.java +++ b/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/services/AbstractServiceIntegrationTest.java @@ -10,6 +10,7 @@ import eu.europa.ec.edelivery.smp.data.dao.*; import eu.europa.ec.edelivery.smp.data.model.DBDomain; import eu.europa.ec.edelivery.smp.data.model.doc.DBResource; import eu.europa.ec.edelivery.smp.data.model.doc.DBSubresource; +import eu.europa.ec.edelivery.smp.data.model.user.DBCredential; import eu.europa.ec.edelivery.smp.data.model.user.DBUser; import eu.europa.ec.edelivery.smp.services.mail.MailService; import eu.europa.ec.edelivery.smp.services.spi.SmpXmlSignatureService; @@ -23,6 +24,7 @@ import org.apache.commons.io.FileUtils; import org.junit.Before; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.crypto.bcrypt.BCrypt; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.jdbc.Sql; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; @@ -83,6 +85,7 @@ public abstract class AbstractServiceIntegrationTest extends AbstractBaseDao { @Autowired DBAssertion dbAssertion; + @Before public void before() throws IOException { resetKeystore(); @@ -113,11 +116,14 @@ public abstract class AbstractServiceIntegrationTest extends AbstractBaseDao { domainDao.persistFlushDetach(testDomain01); DBUser u1 = TestDBUtils.createDBUserByUsername(TestConstants.USERNAME_1); + DBCredential c1 = TestDBUtils.createDBCredentialForUser(u1, null, null, null); + c1.setValue(BCrypt.hashpw(USERNAME_1_PASSWORD, BCrypt.gensalt())); DBUser u2 = TestDBUtils.createDBUserByCertificate(TestConstants.USER_CERT_2); DBUser u3 = TestDBUtils.createDBUserByUsername(TestConstants.USERNAME_2); userDao.persistFlushDetach(u1); userDao.persistFlushDetach(u2); userDao.persistFlushDetach(u3); + credentialDao.persistFlushDetach(c1); DBResource sg1d1 = TestDBUtils.createDBResource(TEST_SG_ID_1, TEST_SG_SCHEMA_1); DBSubresource sg1md1 = TestDBUtils.createDBSubresource(TEST_SG_ID_1, TEST_SG_SCHEMA_1, diff --git a/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/services/CredentialServiceTest.java b/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/services/CredentialServiceTest.java index 84f63355877067baaccbc2bce475dd7e3e2d8145..360a910a9844af4b4f373015be4acaea1a8413a4 100644 --- a/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/services/CredentialServiceTest.java +++ b/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/services/CredentialServiceTest.java @@ -1,59 +1,222 @@ package eu.europa.ec.edelivery.smp.services; + +import eu.europa.ec.edelivery.smp.data.model.user.DBCredential; +import eu.europa.ec.edelivery.smp.testutil.TestConstants; +import org.hamcrest.MatcherAssert; +import org.junit.Before; import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.authentication.BadCredentialsException; +import org.springframework.security.core.Authentication; +import org.springframework.test.context.junit4.SpringRunner; + +import java.time.OffsetDateTime; import static org.junit.Assert.*; -public class CredentialServiceTest { +@RunWith(SpringRunner.class) +public class CredentialServiceTest extends AbstractServiceIntegrationTest { + @Autowired + CredentialService testInstance; + @Before + public void beforeMethods() { + testUtilsDao.clearData(); + testUtilsDao.createUsers(); + } + @Test - public void authenticateByUsernamePassword() { + public void authenticateByUsernamePasswordTestBadUsername() { + // given + String username = "usernameNotExists"; + String password = "password"; + // when + BadCredentialsException result = assertThrows(BadCredentialsException.class, () -> testInstance.authenticateByUsernamePassword(username, password)); + MatcherAssert.assertThat(result.getMessage(), org.hamcrest.Matchers.startsWith("Login failed; Invalid userID or password!")); } @Test - public void authenticateByAuthenticationToken() { + public void authenticateByUsernamePasswordTestOk() { + // given + String username = TestConstants.USERNAME_1; + String password = TestConstants.USERNAME_1_PASSWORD; + // when + Authentication authentication = testInstance.authenticateByUsernamePassword(username, password); + // then + assertEquals(username, authentication.getName()); + assertTrue(authentication.isAuthenticated()); + assertEquals(1, authentication.getAuthorities().size()); + assertEquals("ROLE_USER", authentication.getAuthorities().iterator().next().getAuthority()); } @Test - public void isNotValidCredential() { + public void authenticateByUsernamePasswordTestBadPassword() { + // given + String username = TestConstants.USERNAME_1; + String password = "password"; + // when + BadCredentialsException result = assertThrows(BadCredentialsException.class, () -> testInstance.authenticateByUsernamePassword(username, password)); + MatcherAssert.assertThat(result.getMessage(), org.hamcrest.Matchers.startsWith("Login failed; Invalid userID or password!")); } @Test - public void authenticateByCertificateToken() { + public void authenticateByUsernamePasswordInactive() { + testUtilsDao.deactivateUser(TestConstants.USERNAME_1); + + // given + String username = TestConstants.USERNAME_1; + String password = TestConstants.USERNAME_1_PASSWORD; + // when then + BadCredentialsException result = assertThrows(BadCredentialsException.class, () -> testInstance.authenticateByUsernamePassword(username, password)); + MatcherAssert.assertThat(result.getMessage(), org.hamcrest.Matchers.startsWith("Login failed; Invalid userID or password!")); } @Test - public void validateCertificatePolicyMatchLegacy() { + public void authenticateByUsernameCredentialsInactive() { + DBCredential credential = testUtilsDao.getUser1().getUserCredentials().get(0); + credential.setActive(false); + testUtilsDao.updateCredentials(credential); + + // given + String username = TestConstants.USERNAME_1; + String password = TestConstants.USERNAME_1_PASSWORD; + // when then + BadCredentialsException result = assertThrows(BadCredentialsException.class, () -> testInstance.authenticateByUsernamePassword(username, password)); + MatcherAssert.assertThat(result.getMessage(), org.hamcrest.Matchers.startsWith("Login failed; Invalid userID or password!")); } @Test - public void delayResponse() { + public void authenticateByUsernameCredentialsSuspended() { + DBCredential credential = testUtilsDao.getUser1().getUserCredentials().get(0); + credential.setLastFailedLoginAttempt(OffsetDateTime.now()); + credential.setSequentialLoginFailureCount(100); + testUtilsDao.updateCredentials(credential); + + // given + String username = TestConstants.USERNAME_1; + String password = TestConstants.USERNAME_1_PASSWORD; + // when then + BadCredentialsException result = assertThrows(BadCredentialsException.class, () -> testInstance.authenticateByUsernamePassword(username, password)); + MatcherAssert.assertThat(result.getMessage(), org.hamcrest.Matchers.startsWith("The user credential is suspended. Please try again later or contact your administrator.")); } @Test - public void loginAttemptFailedAndThrowError() { + public void authenticateByUsernameCredentialsNotSuspendedAnymore() { + DBCredential credential = testUtilsDao.getUser1().getUserCredentials().get(0); + credential.setLastFailedLoginAttempt(OffsetDateTime.now().minusDays(100)); + credential.setSequentialLoginFailureCount(100); + testUtilsDao.updateCredentials(credential); + + // given + String username = TestConstants.USERNAME_1; + String password = TestConstants.USERNAME_1_PASSWORD; + // when then + Authentication authentication = testInstance.authenticateByUsernamePassword(username, password); + // then + assertEquals(username, authentication.getName()); } + + @Test - public void validateIfCredentialIsSuspended() { + public void authenticateByAccessTokenBadUsername() { + // given + String accessTokenName = "usernameNotExists"; + String accessTokenValue = "password"; + // when + BadCredentialsException result = assertThrows(BadCredentialsException.class, () -> testInstance.authenticateByAuthenticationToken(accessTokenName, accessTokenValue)); + MatcherAssert.assertThat(result.getMessage(), org.hamcrest.Matchers.startsWith("Login failed; Invalid userID or password!")); } @Test - public void getLoginMaxAttempts() { + public void authenticateByAccessTokenTestOk() { + // given + String accessTokenName = TestConstants.USERNAME_3_AT; + String accessTokenValue = TestConstants.USERNAME_3_AT_PASSWORD; + // when + Authentication authentication = testInstance.authenticateByAuthenticationToken(accessTokenName, accessTokenValue); + // then + assertEquals(TestConstants.USERNAME_3_AT, authentication.getName()); + assertTrue(authentication.isAuthenticated()); + assertEquals(1, authentication.getAuthorities().size()); + assertEquals("ROLE_WS_USER", authentication.getAuthorities().iterator().next().getAuthority()); } @Test - public void getLoginSuspensionTimeInSeconds() { + public void authenticateByAccessTokenBadPassword() { + // given + String accessTokenName = TestConstants.USERNAME_3_AT; + String accessTokenValue = "badPassword"; + + // when + BadCredentialsException result = assertThrows(BadCredentialsException.class, () -> testInstance.authenticateByAuthenticationToken(accessTokenName, accessTokenValue)); + MatcherAssert.assertThat(result.getMessage(), org.hamcrest.Matchers.startsWith("Login failed; Invalid userID or password!")); } @Test - public void getAlertBeforeUserSuspendedAlertMoment() { + public void authenticateByAccessTokenInactive() { + testUtilsDao.deactivateUser(TestConstants.USERNAME_3); + + String accessTokenName = TestConstants.USERNAME_3_AT; + String accessTokenValue = TestConstants.USERNAME_3_AT_PASSWORD; + + // when + BadCredentialsException result = assertThrows(BadCredentialsException.class, () -> testInstance.authenticateByAuthenticationToken(accessTokenName, accessTokenValue)); + MatcherAssert.assertThat(result.getMessage(), org.hamcrest.Matchers.startsWith("Login failed; Invalid userID or password!")); + } @Test - public void getLoginFailDelayInMilliSeconds() { + public void authenticateByAccessTokenCredentialsInactive() { + DBCredential credential = testUtilsDao.getUser3().getUserCredentials().get(0); + credential.setActive(false); + testUtilsDao.updateCredentials(credential); + + // given + String accessTokenName = TestConstants.USERNAME_3_AT; + String accessTokenValue = TestConstants.USERNAME_3_AT_PASSWORD; + + // when + BadCredentialsException result = assertThrows(BadCredentialsException.class, () -> testInstance.authenticateByAuthenticationToken(accessTokenName, accessTokenValue)); + MatcherAssert.assertThat(result.getMessage(), org.hamcrest.Matchers.startsWith("Login failed; Invalid userID or password!")); + + } + + @Test + public void authenticateByAccessTokenSuspended() { + DBCredential credential = testUtilsDao.getUser3().getUserCredentials().get(0); + credential.setLastFailedLoginAttempt(OffsetDateTime.now()); + credential.setSequentialLoginFailureCount(100); + testUtilsDao.updateCredentials(credential); + + // given + String accessTokenName = TestConstants.USERNAME_3_AT; + String accessTokenValue = TestConstants.USERNAME_3_AT_PASSWORD; + + // when + BadCredentialsException result = assertThrows(BadCredentialsException.class, () -> testInstance.authenticateByAuthenticationToken(accessTokenName, accessTokenValue)); + MatcherAssert.assertThat(result.getMessage(), org.hamcrest.Matchers.startsWith("The user credential is suspended. Please try again later or contact your administrator.")); } + + @Test + public void authenticateByAccessTokenCredentialsNotSuspendedAnymore() { + DBCredential credential = testUtilsDao.getUser3().getUserCredentials().get(0); + credential.setLastFailedLoginAttempt(OffsetDateTime.now().minusDays(100)); + credential.setSequentialLoginFailureCount(100); + testUtilsDao.updateCredentials(credential); + + // given + String accessTokenName = TestConstants.USERNAME_3_AT; + String accessTokenValue = TestConstants.USERNAME_3_AT_PASSWORD; + // when then + Authentication authentication = testInstance.authenticateByAuthenticationToken(accessTokenName, accessTokenValue); + // then + assertEquals(TestConstants.USERNAME_3_AT, authentication.getName()); + } + } diff --git a/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/testutil/TestConstants.java b/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/testutil/TestConstants.java index 7bc0eee99515e95647b64185976c00dc30c04bbe..8ae691e505d138b48cbe6ddccf40150ff67bd41c 100644 --- a/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/testutil/TestConstants.java +++ b/smp-server-library/src/test/java/eu/europa/ec/edelivery/smp/testutil/TestConstants.java @@ -45,8 +45,11 @@ public class TestConstants { public static final String TOKEN_PREFIX = "token-"; public static final String USERNAME_1 = "test-user_001"; + public static final String USERNAME_1_PASSWORD = "test-user_001"; public static final String USERNAME_2 = "test-user_002"; public static final String USERNAME_3 = "test-user_003"; + public static final String USERNAME_3_AT = "test-user_003-access-token"; + public static final String USERNAME_3_AT_PASSWORD = "test-user_003"; public static final String USERNAME_4 = "test-user_004"; public static final String USERNAME_5 = "test-user_005"; public static final String USERNAME_TOKEN_1 = TOKEN_PREFIX + USERNAME_1;