diff --git a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/logging/SMPMessageCode.java b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/logging/SMPMessageCode.java index 0bf2341c134360e784fae906bf8665a1975d2160..7ae1577baf6ec830cb9aa14dbf2e7200c705ffdc 100644 --- a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/logging/SMPMessageCode.java +++ b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/logging/SMPMessageCode.java @@ -43,13 +43,14 @@ public enum SMPMessageCode implements MessageCode { BUS_INVALID_XML("BUS-030", "Invalid XML for {}. Error: [{}]"), SEC_UNSECURED_LOGIN_ALLOWED("SEC-001", "Unsecure login is allowed, no authentication will be performed"), - SEC_USER_AUTHENTICATED("SEC-002", "User {} is authenticated with role {}."), - SEC_USER_NOT_EXISTS("SEC-003", "User {} not exists."), - SEC_INVALID_PASSWORD("SEC-004", "User {} has invalid password."), - SEC_USER_CERT_NOT_EXISTS("SEC-005", "User certificate {} not exists."), - SEC_USER_CERT_INVALID("SEC-006", "User certificate {} is invalid: {}."), - SEC_USER_NOT_AUTHENTICATED("SEC-007", "User {}. Reason: {}."), - SEC_USER_SUSPENDED("SEC-008", "User {} is temporarily suspended."), + SEC_USER_AUTHENTICATED("SEC-002", "User [{}] is authenticated with role [{}]."), + SEC_USER_NOT_EXISTS("SEC-003", "User [{}] not exists."), + SEC_INVALID_PASSWORD("SEC-004", "User [{}] has invalid password."), + SEC_USER_CERT_NOT_EXISTS("SEC-005", "User certificate [{}] not exists."), + SEC_USER_CERT_INVALID("SEC-006", "User certificate [{}] is invalid: [{}]."), + SEC_USER_NOT_AUTHENTICATED("SEC-007", "User [{}]. Reason: [{}]."), + SEC_USER_SUSPENDED("SEC-008", "User [{}] is temporarily suspended."), + SEC_INVALID_TOKEN("SEC-009", "User [{}] has invalid token value for token id: [{}]."), ; diff --git a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/services/ui/UIUserService.java b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/services/ui/UIUserService.java index 3d5182446b7dc3014103d57c1e2b5c3dd9cdf551..5b5f2db650a86aae93142aae318d0ab34b2c02e0 100644 --- a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/services/ui/UIUserService.java +++ b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/services/ui/UIUserService.java @@ -149,7 +149,7 @@ public class UIUserService extends UIServiceBase<DBUser, UserRO> { public DBUser updateUserPassword(Long authorizedUserId, Long userToUpdateId, String currentPassword, String newPassword) { Pattern pattern = configurationService.getPasswordPolicyRexExp(); - if (!pattern.matcher(newPassword).matches()) { + if (pattern != null && !pattern.matcher(newPassword).matches()) { throw new SMPRuntimeException(ErrorCode.INVALID_REQUEST, "PasswordChange", configurationService.getPasswordPolicyValidationMessage()); } DBUser dbAuthorizedUser = userDao.find(authorizedUserId); diff --git a/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/auth/SMPAuthenticationProvider.java b/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/auth/SMPAuthenticationProvider.java index dabd7ab4c0af31146986ea3734353bb2ab197cb8..1f358c73f183f013a7a322f267667d9a3e5ef117 100644 --- a/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/auth/SMPAuthenticationProvider.java +++ b/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/auth/SMPAuthenticationProvider.java @@ -35,7 +35,10 @@ import java.text.DateFormat; import java.text.SimpleDateFormat; import java.time.OffsetDateTime; import java.time.temporal.ChronoUnit; -import java.util.*; +import java.util.Calendar; +import java.util.Collections; +import java.util.Date; +import java.util.Optional; import static java.util.Locale.US; @@ -125,8 +128,6 @@ public class SMPAuthenticationProvider implements AuthenticationProvider { */ public Authentication authenticateByCertificateToken(PreAuthenticatedCertificatePrincipal principal) { LOG.info("authenticateByCertificateToken:" + principal.getName()); - KeyStore truststore = truststoreService.getTrustStore(); - DBUser user; X509Certificate x509Certificate = principal.getCertificate(); String userToken = principal.getName(); @@ -137,7 +138,7 @@ public class SMPAuthenticationProvider implements AuthenticationProvider { truststoreService.validateCertificateWithTruststore(x509Certificate); } catch (CertificateException e) { String message = "Certificate is not trusted!"; - LOG.securityWarn(SMPMessageCode.SEC_USER_CERT_INVALID, userToken , message + LOG.securityWarn(SMPMessageCode.SEC_USER_CERT_INVALID, userToken, message + " The cert chain is not in truststore or either subject regexp or allowed cert policies does not match"); throw new BadCredentialsException(message); } @@ -238,7 +239,7 @@ public class SMPAuthenticationProvider implements AuthenticationProvider { */ public void validateIfTokenIsSuspended(DBUser user) { if (user.getSequentialTokenLoginFailureCount() == null - || user.getSequentialTokenLoginFailureCount() < 0) { + || user.getSequentialTokenLoginFailureCount() < 1) { LOG.trace("User has no previous failed attempts"); return; } @@ -249,14 +250,17 @@ public class SMPAuthenticationProvider implements AuthenticationProvider { } if (user.getLastTokenFailedLoginAttempt() == null) { - LOG.warn("Access token [{}] has failed attempts [{}] but null last Failed login attempt!", user.getUsername(), user.getLastFailedLoginAttempt()); + LOG.warn("Access token [{}] for user [{}] has failed attempts [{}] but null last Failed login attempt!", + user.getAccessTokenIdentifier(), user.getUsername(), user.getLastFailedLoginAttempt()); return; } // check if the last failed attempt is already expired. If yes just clear the attempts - if (configurationService.getAccessTokenLoginSuspensionTimeInSeconds() != null && configurationService.getAccessTokenLoginSuspensionTimeInSeconds() > 0 - && ChronoUnit.SECONDS.between(OffsetDateTime.now(), user.getLastTokenFailedLoginAttempt()) > configurationService.getAccessTokenLoginSuspensionTimeInSeconds()) { - LOG.warn("User [{}] suspension is expired! Clear failed login attempts and last failed login attempt", user.getUsername()); + if (configurationService.getAccessTokenLoginSuspensionTimeInSeconds() != null + && configurationService.getAccessTokenLoginSuspensionTimeInSeconds() > 0 + && ChronoUnit.SECONDS.between(user.getLastTokenFailedLoginAttempt(), OffsetDateTime.now()) > configurationService.getAccessTokenLoginSuspensionTimeInSeconds()) { + LOG.info("User token [{}] for user [{}] suspension is expired! Clear failed login attempts and last failed login attempt", + user.getAccessTokenIdentifier(), user.getUsername()); user.setLastTokenFailedLoginAttempt(null); user.setSequentialTokenLoginFailureCount(0); mUserDao.update(user); @@ -264,7 +268,8 @@ public class SMPAuthenticationProvider implements AuthenticationProvider { } if (user.getSequentialTokenLoginFailureCount() < configurationService.getAccessTokenLoginMaxAttempts()) { - LOG.warn("User [{}] failed login attempt [{}]! did not reach the max failed attempts [{}]", user.getUsername(), user.getSequentialTokenLoginFailureCount(), configurationService.getAccessTokenLoginMaxAttempts()); + LOG.warn("User token [{}] for user [{}] failed login attempt [{}] did not reach the max failed attempts [{}]", + user.getAccessTokenIdentifier(), user.getUsername(), user.getSequentialTokenLoginFailureCount(), configurationService.getAccessTokenLoginMaxAttempts()); return; } if (configurationService.getAlertBeforeUserSuspendedAlertMoment() == AlertSuspensionMomentEnum.AT_LOGON) { @@ -312,11 +317,11 @@ public class SMPAuthenticationProvider implements AuthenticationProvider { user.setLastTokenFailedLoginAttempt(null); mUserDao.update(user); } catch (java.lang.IllegalArgumentException ex) { - // password is not hashed; + // password is not hashed LOG.securityWarn(SMPMessageCode.SEC_INVALID_PASSWORD, ex, authenticationTokenId); throw new BadCredentialsException(LOGIN_FAILED_MESSAGE); } - // the webservice authentication with corresponding web-service authority; + // the webservice authentication with corresponding web-service authority SMPAuthority authority = SMPAuthority.getAuthorityByRoleName("WS_" + user.getRole()); // the webservice authentication does not support session set the session secret is null! SMPUserDetails userDetails = new SMPUserDetails(user, null, Collections.singletonList(authority)); @@ -335,12 +340,8 @@ public class SMPAuthenticationProvider implements AuthenticationProvider { user.setSequentialTokenLoginFailureCount(user.getSequentialTokenLoginFailureCount() != null ? user.getSequentialTokenLoginFailureCount() + 1 : 1); user.setLastTokenFailedLoginAttempt(OffsetDateTime.now()); mUserDao.update(user); - LOG.securityWarn(SMPMessageCode.SEC_INVALID_PASSWORD, user.getAccessTokenIdentifier()); + LOG.securityWarn(SMPMessageCode.SEC_INVALID_TOKEN, user.getUsername(), user.getAccessTokenIdentifier()); - user.setSequentialLoginFailureCount(user.getSequentialLoginFailureCount() != null ? user.getSequentialLoginFailureCount() + 1 : 1); - user.setLastFailedLoginAttempt(OffsetDateTime.now()); - mUserDao.update(user); - LOG.securityWarn(SMPMessageCode.SEC_INVALID_PASSWORD, user.getUsername()); if (user.getSequentialTokenLoginFailureCount() >= configurationService.getAccessTokenLoginMaxAttempts()) { LOG.info("User access token [{}] failed sequential attempt exceeded the max allowed attempts [{}]!", user.getAccessToken(), configurationService.getAccessTokenLoginMaxAttempts()); alertService.alertCredentialsSuspended(user, CredentialTypeEnum.ACCESS_TOKEN); @@ -353,10 +354,10 @@ public class SMPAuthenticationProvider implements AuthenticationProvider { @Override public boolean supports(Class<?> auth) { - LOG.info("Support authentication: " + auth); + LOG.info("Support authentication: [{}].", auth); boolean supportAuthentication = auth.equals(UsernamePasswordAuthenticationToken.class) || auth.equals(PreAuthenticatedAuthenticationToken.class); if (!supportAuthentication) { - LOG.warn("SMP does not support authentication type: " + auth); + LOG.warn("SMP does not support authentication type: [{}].", auth); } return supportAuthentication; } diff --git a/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/auth/SMPAuthenticationProviderForUI.java b/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/auth/SMPAuthenticationProviderForUI.java index 8409a4fceb51dfac90492da0e805f26329457e09..052722e1936b1472b8a7cbc5434d84b2f357a340 100644 --- a/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/auth/SMPAuthenticationProviderForUI.java +++ b/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/auth/SMPAuthenticationProviderForUI.java @@ -184,7 +184,7 @@ public class SMPAuthenticationProviderForUI implements AuthenticationProvider { } // check if the last failed attempt is already expired. If yes just clear the attempts if (configurationService.getLoginSuspensionTimeInSeconds() != null && configurationService.getLoginSuspensionTimeInSeconds() > 0 - && ChronoUnit.SECONDS.between(OffsetDateTime.now(), user.getLastFailedLoginAttempt()) > configurationService.getLoginSuspensionTimeInSeconds()) { + && ChronoUnit.SECONDS.between( user.getLastFailedLoginAttempt(),OffsetDateTime.now()) > configurationService.getLoginSuspensionTimeInSeconds()) { LOG.warn("User [{}] suspension is expired! Clear failed login attempts and last failed login attempt", user.getUsername()); user.setLastFailedLoginAttempt(null); user.setSequentialLoginFailureCount(0); diff --git a/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/ui/ResourceConstants.java b/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/ui/ResourceConstants.java index 83995035837ce67dffd86298852d378dac7a875f..8afe8219b95b07bc9c94033bba1cae8278ea2534 100644 --- a/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/ui/ResourceConstants.java +++ b/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/ui/ResourceConstants.java @@ -20,6 +20,7 @@ public class ResourceConstants { public static final String CONTEXT_PATH_PUBLIC_SERVICE_METADATA = CONTEXT_PATH_PUBLIC + "service-metadata"; public static final String CONTEXT_PATH_PUBLIC_SECURITY = CONTEXT_PATH_PUBLIC + "security"; public static final String CONTEXT_PATH_PUBLIC_SECURITY_AUTHENTICATION = CONTEXT_PATH_PUBLIC_SECURITY + "/authentication"; + public static final String CONTEXT_PATH_PUBLIC_SECURITY_USER = CONTEXT_PATH_PUBLIC_SECURITY + "/user"; //internal public static final String CONTEXT_PATH_INTERNAL_ALERT = CONTEXT_PATH_INTERNAL + "alert"; diff --git a/smp-webapp/src/test/java/eu/europa/ec/edelivery/smp/auth/SMPAuthenticationProviderForUITest.java b/smp-webapp/src/test/java/eu/europa/ec/edelivery/smp/auth/SMPAuthenticationProviderForUITest.java new file mode 100644 index 0000000000000000000000000000000000000000..bda211fe0ec87e6d106e7ef905e4b0ebea8b38f6 --- /dev/null +++ b/smp-webapp/src/test/java/eu/europa/ec/edelivery/smp/auth/SMPAuthenticationProviderForUITest.java @@ -0,0 +1,50 @@ +package eu.europa.ec.edelivery.smp.auth; + +import eu.europa.ec.edelivery.smp.data.dao.UserDao; +import eu.europa.ec.edelivery.smp.data.model.DBUser; +import eu.europa.ec.edelivery.smp.services.AlertService; +import eu.europa.ec.edelivery.smp.services.CRLVerifierService; +import eu.europa.ec.edelivery.smp.services.ConfigurationService; +import eu.europa.ec.edelivery.smp.services.ui.UITruststoreService; +import org.junit.Test; +import org.mockito.Mockito; +import org.springframework.core.convert.ConversionService; + +import java.time.OffsetDateTime; + +import static org.junit.Assert.*; +import static org.mockito.Mockito.doReturn; + +public class SMPAuthenticationProviderForUITest { + + UserDao mockUserDao = Mockito.mock(UserDao.class); + ConversionService mockConversionService = Mockito.mock(ConversionService.class); + CRLVerifierService mockCrlVerifierService = Mockito.mock(CRLVerifierService.class); + UITruststoreService mockTruststoreService = Mockito.mock(UITruststoreService.class); + ConfigurationService mockConfigurationService = Mockito.mock(ConfigurationService.class); + AlertService mocAlertService = Mockito.mock(AlertService.class); + SMPAuthenticationProviderForUI testInstance = new SMPAuthenticationProviderForUI(mockUserDao, + mockConversionService, + mockCrlVerifierService, + mocAlertService, + mockTruststoreService, + mockConfigurationService); + + @Test + public void testValidateIfTokenIsSuspendedReset(){ + int starFailCount = 5; + DBUser user = new DBUser(); + user.setUsername("TestToken"); + int suspensionSeconds =100; + + user.setLastFailedLoginAttempt(OffsetDateTime.now().minusSeconds(suspensionSeconds+10)); + user.setSequentialLoginFailureCount(starFailCount); + doReturn(suspensionSeconds).when(mockConfigurationService).getLoginSuspensionTimeInSeconds(); + doReturn(starFailCount).when(mockConfigurationService).getLoginMaxAttempts(); + + testInstance.validateIfUserAccountIsSuspended(user); + + assertEquals(0, (int)user.getSequentialLoginFailureCount()); + assertEquals(null, user.getLastFailedLoginAttempt()); + } +} \ No newline at end of file diff --git a/smp-webapp/src/test/java/eu/europa/ec/edelivery/smp/auth/SMPAuthenticationProviderTest.java b/smp-webapp/src/test/java/eu/europa/ec/edelivery/smp/auth/SMPAuthenticationProviderTest.java index ea662109b0210f2da615f19d06867d8bb3fe170b..b6501d15434638cd5a74a9a8104fbeaa2f18e509 100644 --- a/smp-webapp/src/test/java/eu/europa/ec/edelivery/smp/auth/SMPAuthenticationProviderTest.java +++ b/smp-webapp/src/test/java/eu/europa/ec/edelivery/smp/auth/SMPAuthenticationProviderTest.java @@ -2,6 +2,7 @@ package eu.europa.ec.edelivery.smp.auth; import eu.europa.ec.edelivery.smp.data.dao.UserDao; import eu.europa.ec.edelivery.smp.data.model.DBUser; +import eu.europa.ec.edelivery.smp.data.ui.enums.CredentialTypeEnum; import eu.europa.ec.edelivery.smp.services.AlertService; import eu.europa.ec.edelivery.smp.services.CRLVerifierService; import eu.europa.ec.edelivery.smp.services.ConfigurationService; @@ -15,12 +16,13 @@ import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.crypto.bcrypt.BCrypt; +import java.time.OffsetDateTime; import java.util.Calendar; import java.util.Optional; -import static org.junit.Assert.assertThat; +import static org.junit.Assert.*; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.*; /** * @author Joze Rihtarsic @@ -84,4 +86,55 @@ public class SMPAuthenticationProviderTest { Matchers.lessThan(50L)); } + @Test + public void testLoginAttemptForAccessTokenFailed(){ + int starFailCount = 2; + DBUser user = new DBUser(); + user.setSequentialTokenLoginFailureCount(starFailCount); + long starTime =Calendar.getInstance().getTimeInMillis(); + doReturn(100).when(mockConfigurationService).getAccessTokenLoginMaxAttempts(); + // when + BadCredentialsException error = assertThrows(BadCredentialsException.class, + () -> testInstance.loginAttemptForAccessTokenFailed(user,starTime)); + + assertEquals(SMPAuthenticationProvider.LOGIN_FAILED_MESSAGE, error.getMessage()); + assertEquals(starFailCount+1,(int)user.getSequentialTokenLoginFailureCount()); + verify(mocAlertService, times(1)).alertCredentialVerificationFailed(user, CredentialTypeEnum.ACCESS_TOKEN); + + } + + @Test + public void testLoginAttemptForAccessTokenSuspended(){ + int starFailCount = 5; + DBUser user = new DBUser(); + user.setSequentialTokenLoginFailureCount(starFailCount); + long starTime =Calendar.getInstance().getTimeInMillis(); + doReturn(5).when(mockConfigurationService).getAccessTokenLoginMaxAttempts(); + // when + BadCredentialsException error = assertThrows(BadCredentialsException.class, + () -> testInstance.loginAttemptForAccessTokenFailed(user,starTime)); + + assertEquals(SMPAuthenticationProvider.LOGIN_FAILED_MESSAGE, error.getMessage()); + assertEquals(starFailCount+1,(int)user.getSequentialTokenLoginFailureCount()); + verify(mocAlertService, times(1)).alertCredentialsSuspended(user, CredentialTypeEnum.ACCESS_TOKEN); + } + + @Test + public void testValidateIfTokenIsSuspendedReset(){ + int starFailCount = 5; + DBUser user = new DBUser(); + user.setUsername("TestToken"); + int suspensionSeconds =100; + + user.setLastTokenFailedLoginAttempt(OffsetDateTime.now().minusSeconds(suspensionSeconds+10)); + user.setSequentialTokenLoginFailureCount(starFailCount); + doReturn(suspensionSeconds).when(mockConfigurationService).getAccessTokenLoginSuspensionTimeInSeconds(); + doReturn(starFailCount).when(mockConfigurationService).getAccessTokenLoginMaxAttempts(); + + testInstance.validateIfTokenIsSuspended(user); + + assertEquals(0, (int)user.getSequentialTokenLoginFailureCount()); + assertEquals(null, user.getLastTokenFailedLoginAttempt()); + } + } \ No newline at end of file diff --git a/smp-webapp/src/test/java/eu/europa/ec/edelivery/smp/test/testutils/MockMvcUtils.java b/smp-webapp/src/test/java/eu/europa/ec/edelivery/smp/test/testutils/MockMvcUtils.java index 1f52cfbf24d3b7c8a66f043f7e4090700d27bac0..a95441d5031397723e5f3201b26300c942d4882d 100644 --- a/smp-webapp/src/test/java/eu/europa/ec/edelivery/smp/test/testutils/MockMvcUtils.java +++ b/smp-webapp/src/test/java/eu/europa/ec/edelivery/smp/test/testutils/MockMvcUtils.java @@ -1,6 +1,7 @@ package eu.europa.ec.edelivery.smp.test.testutils; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; import eu.europa.ec.edelivery.smp.data.ui.UserRO; import org.springframework.http.HttpHeaders; import org.springframework.mock.web.MockHttpSession; @@ -31,17 +32,19 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. * @since 4.2 */ public class MockMvcUtils { - static ObjectMapper mapper = new ObjectMapper(); - - private static final String SYS_ADMIN_USERNAME = "sys_admin"; - private static final String SYS_ADMIN_PASSWD = "test123"; - private static final String SMP_ADMIN_USERNAME = "smp_admin"; - private static final String SMP_ADMIN_PASSWD = "test123"; - private static final String SG_USER_USERNAME = "sg_admin"; - private static final String SG_USER_PASSWD = "test123"; - - private static final String SG_USER2_USERNAME = "test_user_hashed_pass"; - private static final String SG_USER2_PASSWD = "test123"; + static ObjectMapper mapper = new ObjectMapper(){{ + registerModule(new JavaTimeModule()); + }}; + + public static final String SYS_ADMIN_USERNAME = "sys_admin"; + public static final String SYS_ADMIN_PASSWD = "test123"; + public static final String SMP_ADMIN_USERNAME = "smp_admin"; + public static final String SMP_ADMIN_PASSWD = "test123"; + public static final String SG_USER_USERNAME = "sg_admin"; + public static final String SG_USER_PASSWD = "test123"; + + public static final String SG_USER2_USERNAME = "test_user_hashed_pass"; + public static final String SG_USER2_PASSWD = "test123"; public static RequestPostProcessor getHttpBasicSystemAdminCredentials() { diff --git a/smp-webapp/src/test/java/eu/europa/ec/edelivery/smp/ui/external/UserResourceIntegrationTest.java b/smp-webapp/src/test/java/eu/europa/ec/edelivery/smp/ui/external/UserResourceIntegrationTest.java index e129019134c04339d15fdce0168f8e217d209f6e..c6f8871d400a62c634ac6b2538445e081903fcc0 100644 --- a/smp-webapp/src/test/java/eu/europa/ec/edelivery/smp/ui/external/UserResourceIntegrationTest.java +++ b/smp-webapp/src/test/java/eu/europa/ec/edelivery/smp/ui/external/UserResourceIntegrationTest.java @@ -1,10 +1,8 @@ package eu.europa.ec.edelivery.smp.ui.external; import com.fasterxml.jackson.databind.ObjectMapper; -import eu.europa.ec.edelivery.smp.data.ui.CertificateRO; -import eu.europa.ec.edelivery.smp.data.ui.DeleteEntityValidation; -import eu.europa.ec.edelivery.smp.data.ui.ServiceResult; -import eu.europa.ec.edelivery.smp.data.ui.UserRO; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import eu.europa.ec.edelivery.smp.data.ui.*; import eu.europa.ec.edelivery.smp.test.SmpTestWebAppConfig; import eu.europa.ec.edelivery.smp.ui.ResourceConstants; import org.junit.Before; @@ -12,7 +10,6 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.mock.web.MockHttpSession; -import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.jdbc.Sql; import org.springframework.test.context.junit4.SpringRunner; @@ -22,11 +19,10 @@ import org.springframework.test.web.servlet.MvcResult; import org.springframework.web.context.WebApplicationContext; import javax.ws.rs.core.MediaType; -import java.util.Arrays; import java.util.UUID; import static eu.europa.ec.edelivery.smp.test.testutils.MockMvcUtils.*; -import static eu.europa.ec.edelivery.smp.ui.ResourceConstants.CONTEXT_PATH_INTERNAL_USER; +import static eu.europa.ec.edelivery.smp.ui.ResourceConstants.CONTEXT_PATH_PUBLIC_SECURITY_USER; import static org.junit.Assert.*; import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; import static org.springframework.test.context.jdbc.Sql.ExecutionPhase.BEFORE_TEST_METHOD; @@ -57,28 +53,10 @@ public class UserResourceIntegrationTest { @Before public void setup() { + mapper.registerModule(new JavaTimeModule()); mvc = initializeMockMvc(webAppContext); } - @Test - public void getUserList() throws Exception { - MockHttpSession session = loginWithSystemAdmin(mvc); - MvcResult result = mvc.perform(get(CONTEXT_PATH_INTERNAL_USER) - .session(session) - .with(csrf())) - .andExpect(status().isOk()).andReturn(); - ServiceResult res = mapper.readValue(result.getResponse().getContentAsString(), ServiceResult.class); - // then - assertNotNull(res); - assertEquals(10, res.getServiceEntities().size()); - res.getServiceEntities().forEach(sgMap -> { - UserRO sgro = mapper.convertValue(sgMap, UserRO.class); - assertNotNull(sgro.getUserId()); - assertNotNull(sgro.getUsername()); - assertNotNull(sgro.getRole()); - }); - } - @Test public void testUpdateCurrentUserOK() throws Exception { // login @@ -124,134 +102,55 @@ public class UserResourceIntegrationTest { } @Test - public void testUpdateUserList() throws Exception { - // given when - MockHttpSession session = loginWithSystemAdmin(mvc); - - SecurityMockMvcRequestPostProcessors.CsrfRequestPostProcessor csrf = csrf(); - MvcResult result = mvc.perform(get(CONTEXT_PATH_INTERNAL_USER) - .session(session) - .with(csrf)) - .andExpect(status().isOk()).andReturn(); - ServiceResult res = mapper.readValue(result.getResponse().getContentAsString(), ServiceResult.class); - assertNotNull(res); - assertFalse(res.getServiceEntities().isEmpty()); - UserRO userRO = mapper.convertValue(res.getServiceEntities().get(0), UserRO.class); - // then - userRO.setActive(!userRO.isActive()); - userRO.setEmailAddress("test@mail.com"); - userRO.setPassword(UUID.randomUUID().toString()); - if (userRO.getCertificate() == null) { - userRO.setCertificate(new CertificateRO()); - } - userRO.getCertificate().setCertificateId(UUID.randomUUID().toString()); - - mvc.perform(put(CONTEXT_PATH_INTERNAL_USER) - .session(session) - .with(csrf) - .contentType(MediaType.APPLICATION_JSON) - .content(mapper.writeValueAsString(Arrays.asList(userRO))) - ).andExpect(status().isOk()); - } - - @Test - public void testUpdateUserListWrongAuthentication() throws Exception { - // given when - MockHttpSession session = loginWithSystemAdmin(mvc); - MvcResult result = mvc.perform(get(CONTEXT_PATH_INTERNAL_USER) - .session(session) - .with(csrf())) - .andExpect(status().isOk()).andReturn(); - ServiceResult res = mapper.readValue(result.getResponse().getContentAsString(), ServiceResult.class); - assertNotNull(res); - assertFalse(res.getServiceEntities().isEmpty()); - UserRO userRO = mapper.convertValue(res.getServiceEntities().get(0), UserRO.class); - // then - userRO.setActive(!userRO.isActive()); - userRO.setEmailAddress("test@mail.com"); - userRO.setPassword(UUID.randomUUID().toString()); - if (userRO.getCertificate() == null) { - userRO.setCertificate(new CertificateRO()); - } - userRO.getCertificate().setCertificateId(UUID.randomUUID().toString()); - // anonymous - mvc.perform(put(CONTEXT_PATH_INTERNAL_USER) - .with(csrf()) - .contentType(MediaType.APPLICATION_JSON) - .content(mapper.writeValueAsString(Arrays.asList(userRO))) - ).andExpect(status().isUnauthorized()); - - MockHttpSession sessionSMPAdmin = loginWithSMPAdmin(mvc); - mvc.perform(put(CONTEXT_PATH_INTERNAL_USER) - .session(sessionSMPAdmin) - .with(csrf()) - .contentType(MediaType.APPLICATION_JSON) - .content(mapper.writeValueAsString(Arrays.asList(userRO))) - ).andExpect(status().isUnauthorized()); - - MockHttpSession sessionSGAdmin = loginWithServiceGroupUser(mvc); - mvc.perform(put(CONTEXT_PATH_INTERNAL_USER) - .session(sessionSGAdmin) - .with(csrf()) - .contentType(MediaType.APPLICATION_JSON) - .content(mapper.writeValueAsString(Arrays.asList(userRO))) - ).andExpect(status().isUnauthorized()); - } - - @Test - public void testValidateDeleteUserOK() throws Exception { + public void generateAccessTokenForUser() throws Exception { + MockHttpSession session = loginWithServiceGroupUser2(mvc); + UserRO userRO = getLoggedUserData(mvc, session); + assertNotNull(userRO); - // login - MockHttpSession session = loginWithSystemAdmin(mvc); - // get list - MvcResult result = mvc.perform(get(CONTEXT_PATH_INTERNAL_USER) + MvcResult result = mvc.perform(post(PATH_PUBLIC + "/" + userRO.getUserId()+"/generate-access-token") .with(csrf()) - .session(session)) - .andExpect(status().isOk()).andReturn(); - ServiceResult res = mapper.readValue(result.getResponse().getContentAsString(), ServiceResult.class); - assertNotNull(res); - assertFalse(res.getServiceEntities().isEmpty()); - UserRO userRO = mapper.convertValue(res.getServiceEntities().get(0), UserRO.class); + .session(session) + .contentType(MediaType.TEXT_PLAIN) + .content(SG_USER2_PASSWD) + ).andExpect(status().isOk()).andReturn(); - MvcResult resultDelete = mvc.perform(post(CONTEXT_PATH_INTERNAL_USER + "/validate-delete") + MvcResult resultUser = mvc.perform(get(CONTEXT_PATH_PUBLIC_SECURITY_USER ) .with(csrf()) .session(session) - .contentType(MediaType.APPLICATION_JSON) - .content("[\"" + userRO.getUserId() + "\"]")) - .andExpect(status().isOk()).andReturn(); - - DeleteEntityValidation dev = mapper.readValue(resultDelete.getResponse().getContentAsString(), DeleteEntityValidation.class); + ).andExpect(status().isOk()).andReturn(); - assertFalse(dev.getListIds().isEmpty()); - assertTrue(dev.getListDeleteNotPermitedIds().isEmpty()); - assertEquals(userRO.getUserId(), dev.getListIds().get(0)); + UserRO updateUserData = mapper.readValue(resultUser.getResponse().getContentAsString(), UserRO.class); + AccessTokenRO resAccessToken = mapper.readValue(result.getResponse().getContentAsString(), AccessTokenRO.class); + assertNotNull(resAccessToken); + assertNotEquals(userRO.getAccessTokenId(), resAccessToken.getIdentifier()); + assertNotEquals(userRO.getAccessTokenExpireOn(), resAccessToken.getExpireOn()); + assertEquals(updateUserData.getAccessTokenId(), resAccessToken.getIdentifier()); + assertEquals(updateUserData.getAccessTokenExpireOn(), resAccessToken.getExpireOn()); } @Test - public void testValidateDeleteLoggedUserNotOK() throws Exception { + public void changePassword() throws Exception { + String newPassword = "TESTtest1234!@#$"; - // login - MockHttpSession session = loginWithSystemAdmin(mvc); - // get list - MvcResult result = mvc.perform(get(CONTEXT_PATH_INTERNAL_USER) - .with(csrf()) - .session(session)) - .andExpect(status().isOk()).andReturn(); + MockHttpSession session = loginWithServiceGroupUser2(mvc); UserRO userRO = getLoggedUserData(mvc, session); + assertNotNull(userRO); + PasswordChangeRO newPass = new PasswordChangeRO(); + newPass.setUsername(SG_USER2_USERNAME); + newPass.setCurrentPassword(SG_USER2_PASSWD); + newPass.setNewPassword(newPassword); + assertNotEquals(newPassword, SG_USER2_PASSWD); - // note system credential has id 3! - MvcResult resultDelete = mvc.perform(post(CONTEXT_PATH_INTERNAL_USER + "/validate-delete") + mvc.perform(put(PATH_PUBLIC + "/" + userRO.getUserId()+"/change-password") .with(csrf()) .session(session) - .contentType(org.springframework.http.MediaType.APPLICATION_JSON) - .content("[\"" + userRO.getUserId() + "\"]")) - .andExpect(status().isOk()) - .andReturn(); - - DeleteEntityValidation res = mapper.readValue(resultDelete.getResponse().getContentAsString(), DeleteEntityValidation.class); + .contentType(MediaType.APPLICATION_JSON) + .content(mapper.writeValueAsString(newPass)) + ).andExpect(status().isOk()).andReturn(); - assertTrue(res.getListIds().isEmpty()); - assertEquals("Could not delete logged user!", res.getStringMessage()); + // test to login with new password + MockHttpSession sessionNew = loginWithCredentials(mvc, SG_USER2_USERNAME, newPassword); + assertNotNull(sessionNew); } } \ No newline at end of file diff --git a/smp-webapp/src/test/java/eu/europa/ec/edelivery/smp/ui/internal/UserAdminResourceIntegrationTest.java b/smp-webapp/src/test/java/eu/europa/ec/edelivery/smp/ui/internal/UserAdminResourceIntegrationTest.java new file mode 100644 index 0000000000000000000000000000000000000000..9c79954aecde3fa9015cbfda8cca9b0a5a230b6f --- /dev/null +++ b/smp-webapp/src/test/java/eu/europa/ec/edelivery/smp/ui/internal/UserAdminResourceIntegrationTest.java @@ -0,0 +1,272 @@ +package eu.europa.ec.edelivery.smp.ui.internal; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import eu.europa.ec.edelivery.smp.data.ui.*; +import eu.europa.ec.edelivery.smp.test.SmpTestWebAppConfig; +import eu.europa.ec.edelivery.smp.ui.ResourceConstants; +import org.apache.commons.lang3.StringUtils; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.mock.web.MockHttpSession; +import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.jdbc.Sql; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.test.context.web.WebAppConfiguration; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.MvcResult; +import org.springframework.web.context.WebApplicationContext; + +import javax.ws.rs.core.MediaType; +import java.util.Arrays; +import java.util.Map; +import java.util.UUID; + +import static eu.europa.ec.edelivery.smp.test.testutils.MockMvcUtils.*; +import static org.junit.Assert.*; +import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; +import static org.springframework.test.context.jdbc.Sql.ExecutionPhase.BEFORE_TEST_METHOD; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@RunWith(SpringRunner.class) +@WebAppConfiguration +@ContextConfiguration(classes = {SmpTestWebAppConfig.class}) +@Sql(scripts = { + "classpath:/cleanup-database.sql", + "classpath:/webapp_integration_test_data.sql"}, + executionPhase = BEFORE_TEST_METHOD) +public class UserAdminResourceIntegrationTest { + + private static final String PATH_INTERNAL = ResourceConstants.CONTEXT_PATH_INTERNAL_USER; + + @Autowired + private WebApplicationContext webAppContext; + + private MockMvc mvc; + + ObjectMapper mapper = new ObjectMapper(); + + @Before + public void setup() { + mapper.registerModule(new JavaTimeModule()); + mvc = initializeMockMvc(webAppContext); + } + + @Test + public void getUsers() throws Exception { + MockHttpSession session = loginWithSystemAdmin(mvc); + MvcResult result = mvc.perform(get(PATH_INTERNAL) + .session(session) + .with(csrf())) + .andExpect(status().isOk()).andReturn(); + ServiceResult res = mapper.readValue(result.getResponse().getContentAsString(), ServiceResult.class); + // then + assertNotNull(res); + assertEquals(10, res.getServiceEntities().size()); + res.getServiceEntities().forEach(sgMap -> { + UserRO sgro = mapper.convertValue(sgMap, UserRO.class); + assertNotNull(sgro.getUserId()); + assertNotNull(sgro.getUsername()); + assertNotNull(sgro.getRole()); + }); + } + + @Test + public void testUpdateUserList() throws Exception { + // given when + MockHttpSession session = loginWithSystemAdmin(mvc); + + SecurityMockMvcRequestPostProcessors.CsrfRequestPostProcessor csrf = csrf(); + MvcResult result = mvc.perform(get(PATH_INTERNAL) + .session(session) + .with(csrf)) + .andExpect(status().isOk()).andReturn(); + ServiceResult res = mapper.readValue(result.getResponse().getContentAsString(), ServiceResult.class); + assertNotNull(res); + assertFalse(res.getServiceEntities().isEmpty()); + UserRO userRO = mapper.convertValue(res.getServiceEntities().get(0), UserRO.class); + // then + userRO.setActive(!userRO.isActive()); + userRO.setEmailAddress("test@mail.com"); + userRO.setPassword(UUID.randomUUID().toString()); + if (userRO.getCertificate() == null) { + userRO.setCertificate(new CertificateRO()); + } + userRO.getCertificate().setCertificateId(UUID.randomUUID().toString()); + + mvc.perform(put(PATH_INTERNAL) + .session(session) + .with(csrf) + .contentType(MediaType.APPLICATION_JSON) + .content(mapper.writeValueAsString(Arrays.asList(userRO))) + ).andExpect(status().isOk()); + } + + @Test + public void testUpdateUserListWrongAuthentication() throws Exception { + // given when + MockHttpSession session = loginWithSystemAdmin(mvc); + MvcResult result = mvc.perform(get(PATH_INTERNAL) + .session(session) + .with(csrf())) + .andExpect(status().isOk()).andReturn(); + ServiceResult res = mapper.readValue(result.getResponse().getContentAsString(), ServiceResult.class); + assertNotNull(res); + assertFalse(res.getServiceEntities().isEmpty()); + UserRO userRO = mapper.convertValue(res.getServiceEntities().get(0), UserRO.class); + // then + userRO.setActive(!userRO.isActive()); + userRO.setEmailAddress("test@mail.com"); + userRO.setPassword(UUID.randomUUID().toString()); + if (userRO.getCertificate() == null) { + userRO.setCertificate(new CertificateRO()); + } + userRO.getCertificate().setCertificateId(UUID.randomUUID().toString()); + // anonymous + mvc.perform(put(PATH_INTERNAL) + .with(csrf()) + .contentType(MediaType.APPLICATION_JSON) + .content(mapper.writeValueAsString(Arrays.asList(userRO))) + ).andExpect(status().isUnauthorized()); + + MockHttpSession sessionSMPAdmin = loginWithSMPAdmin(mvc); + mvc.perform(put(PATH_INTERNAL) + .session(sessionSMPAdmin) + .with(csrf()) + .contentType(MediaType.APPLICATION_JSON) + .content(mapper.writeValueAsString(Arrays.asList(userRO))) + ).andExpect(status().isUnauthorized()); + + MockHttpSession sessionSGAdmin = loginWithServiceGroupUser(mvc); + mvc.perform(put(PATH_INTERNAL) + .session(sessionSGAdmin) + .with(csrf()) + .contentType(MediaType.APPLICATION_JSON) + .content(mapper.writeValueAsString(Arrays.asList(userRO))) + ).andExpect(status().isUnauthorized()); + } + + @Test + public void testValidateDeleteUserOK() throws Exception { + + // login + MockHttpSession session = loginWithSystemAdmin(mvc); + // get list + MvcResult result = mvc.perform(get(PATH_INTERNAL) + .with(csrf()) + .session(session)) + .andExpect(status().isOk()).andReturn(); + ServiceResult res = mapper.readValue(result.getResponse().getContentAsString(), ServiceResult.class); + assertNotNull(res); + assertFalse(res.getServiceEntities().isEmpty()); + UserRO userRO = mapper.convertValue(res.getServiceEntities().get(0), UserRO.class); + + MvcResult resultDelete = mvc.perform(post(PATH_INTERNAL + "/validate-delete") + .with(csrf()) + .session(session) + .contentType(MediaType.APPLICATION_JSON) + .content("[\"" + userRO.getUserId() + "\"]")) + .andExpect(status().isOk()).andReturn(); + + DeleteEntityValidation dev = mapper.readValue(resultDelete.getResponse().getContentAsString(), DeleteEntityValidation.class); + + assertFalse(dev.getListIds().isEmpty()); + assertTrue(dev.getListDeleteNotPermitedIds().isEmpty()); + assertEquals(userRO.getUserId(), dev.getListIds().get(0)); + } + + @Test + public void testValidateDeleteLoggedUserNotOK() throws Exception { + + // login + MockHttpSession session = loginWithSystemAdmin(mvc); + // get list + MvcResult result = mvc.perform(get(PATH_INTERNAL) + .with(csrf()) + .session(session)) + .andExpect(status().isOk()).andReturn(); + UserRO userRO = getLoggedUserData(mvc, session); + + // note system credential has id 3! + MvcResult resultDelete = mvc.perform(post(PATH_INTERNAL + "/validate-delete") + .with(csrf()) + .session(session) + .contentType(org.springframework.http.MediaType.APPLICATION_JSON) + .content("[\"" + userRO.getUserId() + "\"]")) + .andExpect(status().isOk()) + .andReturn(); + + DeleteEntityValidation res = mapper.readValue(resultDelete.getResponse().getContentAsString(), DeleteEntityValidation.class); + + assertTrue(res.getListIds().isEmpty()); + assertEquals("Could not delete logged user!", res.getStringMessage()); + } + + + @Test + public void generateAccessTokenForUser() throws Exception { + MockHttpSession sessionAdmin = loginWithSystemAdmin(mvc); + UserRO userROAdmin = getLoggedUserData(mvc, sessionAdmin); + + MvcResult resultUsers = mvc.perform(get(PATH_INTERNAL) + .session(sessionAdmin) + .with(csrf())) + .andExpect(status().isOk()).andReturn(); + ServiceResult res = mapper.readValue(resultUsers.getResponse().getContentAsString(), ServiceResult.class); + Map userROToUpdate = (Map) res.getServiceEntities().stream() + .filter(userMap -> + StringUtils.equals(SG_USER2_USERNAME, (String) ((Map) userMap).get("username"))).findFirst().get(); + + MvcResult result = mvc.perform(post(PATH_INTERNAL + "/" + userROAdmin.getUserId() + "/generate-access-token-for/" + userROToUpdate.get("userId")) + .with(csrf()) + .session(sessionAdmin) + .content(SYS_ADMIN_PASSWD) + ).andExpect(status().isOk()).andReturn(); + + + AccessTokenRO resAccessToken = mapper.readValue(result.getResponse().getContentAsString(), AccessTokenRO.class); + assertNotNull(resAccessToken); + assertNotNull(resAccessToken.getIdentifier()); + assertNotNull(resAccessToken.getValue()); + + } + + @Test + public void changePasswordForUser() throws Exception { + MockHttpSession sessionAdmin = loginWithSystemAdmin(mvc); + UserRO userROAdmin = getLoggedUserData(mvc, sessionAdmin); + + MvcResult resultUsers = mvc.perform(get(PATH_INTERNAL) + .session(sessionAdmin) + .with(csrf())) + .andExpect(status().isOk()).andReturn(); + ServiceResult res = mapper.readValue(resultUsers.getResponse().getContentAsString(), ServiceResult.class); + Map userROToUpdate = (Map) res.getServiceEntities().stream() + .filter(userMap -> + StringUtils.equals(SG_USER2_USERNAME, (String) ((Map) userMap).get("username"))).findFirst().get(); + String newPassword = "TESTtest1234!@#$"; + + + PasswordChangeRO newPass = new PasswordChangeRO(); + newPass.setUsername(SG_USER2_USERNAME); + newPass.setCurrentPassword(SYS_ADMIN_PASSWD); + newPass.setNewPassword(newPassword); + assertNotEquals(newPassword, SG_USER2_PASSWD); + + mvc.perform(put(PATH_INTERNAL + "/" + userROAdmin.getUserId() + "//change-password-for/" + userROToUpdate.get("userId")) + .with(csrf()) + .session(sessionAdmin) + .contentType(MediaType.APPLICATION_JSON) + .content(mapper.writeValueAsString(newPass)) + ).andExpect(status().isOk()).andReturn(); + + // test to login with new password + MockHttpSession sessionNew = loginWithCredentials(mvc, SG_USER2_USERNAME, newPassword); + assertNotNull(sessionNew); + } + +} \ No newline at end of file