-
+
\ No newline at end of file
diff --git a/iam-login-service/src/main/webapp/resources/iam/js/directive/password.directive.js b/iam-login-service/src/main/webapp/resources/iam/js/directive/password.directive.js
new file mode 100644
index 000000000..b949da8a8
--- /dev/null
+++ b/iam-login-service/src/main/webapp/resources/iam/js/directive/password.directive.js
@@ -0,0 +1,28 @@
+/*
+ * Copyright (c) Istituto Nazionale di Fisica Nucleare (INFN). 2016-2021
+ *
+ * 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.
+ */
+angular.module('passwordResetApp').directive('strongPassword', function () {
+ return {
+ require: 'ngModel',
+ link: function (scope, element, attrs, ngModel) {
+ ngModel.$parsers.unshift(function (viewValue) {
+ var passwordStrengthRegex = /^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[!"#$%&'()*+,-./:;<=>?@[\]\^ `{|}~]).{8,}([^\r\t\v\f\n]+)$/;
+ var isStrong = passwordStrengthRegex.test(viewValue);
+ ngModel.$setValidity('strongPassword', isStrong);
+ return viewValue;
+ });
+ }
+ };
+});
\ No newline at end of file
diff --git a/iam-login-service/src/main/webapp/resources/iam/js/service/passwordreset.service.js b/iam-login-service/src/main/webapp/resources/iam/js/service/passwordreset.service.js
index 81e539767..6c22b5d22 100644
--- a/iam-login-service/src/main/webapp/resources/iam/js/service/passwordreset.service.js
+++ b/iam-login-service/src/main/webapp/resources/iam/js/service/passwordreset.service.js
@@ -19,43 +19,42 @@ angular.module('passwordResetApp').factory('ResetPasswordService', ResetPassword
ResetPasswordService.$inject = ['$http', '$httpParamSerializerJQLike'];
-function ResetPasswordService($http, $httpParamSerializerJQLike){
-
+function ResetPasswordService($http, $httpParamSerializerJQLike) {
+
var service = {
- forgotPassword : forgotPassword,
- changePassword : changePassword,
+ forgotPassword: forgotPassword,
+ changePassword: changePassword,
};
-
+
return service;
-
- function forgotPassword(email){
-
+
+ function forgotPassword(email) {
+
var data = $httpParamSerializerJQLike({
- email: email
+ email: email
});
-
+
var config = {
- headers : {
- 'Content-Type' : 'application/x-www-form-urlencoded'
+ headers: {
+ 'Content-Type': 'application/x-www-form-urlencoded'
}
- }
-
+ }
+
return $http.post('/iam/password-reset/token', data, config);
-
+
};
-
- function changePassword(resetKey, newPassword){
- var data = $httpParamSerializerJQLike({
- token : resetKey,
- password : newPassword
- });
-
+
+ function changePassword(resetKey, newPassword) {
+
+ var data = JSON.stringify({ "updatedPassword": newPassword, "token": resetKey });
+
var config = {
- headers : {
- 'Content-Type': 'application/x-www-form-urlencoded'
+ headers: {
+ 'Accept': 'application/json',
+ 'Content-Type': 'application/json'
}
}
-
+
return $http.post('/iam/password-reset', data, config);
}
}
\ No newline at end of file
diff --git a/iam-login-service/src/test/java/it/infn/mw/iam/test/api/account/client/AccountClientEndpointTests.java b/iam-login-service/src/test/java/it/infn/mw/iam/test/api/account/client/AccountClientEndpointTests.java
new file mode 100644
index 000000000..e8da780e0
--- /dev/null
+++ b/iam-login-service/src/test/java/it/infn/mw/iam/test/api/account/client/AccountClientEndpointTests.java
@@ -0,0 +1,152 @@
+/**
+ * Copyright (c) Istituto Nazionale di Fisica Nucleare (INFN). 2016-2021
+ *
+ * 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 it.infn.mw.iam.test.api.account.client;
+
+import static org.hamcrest.Matchers.empty;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.not;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
+import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+
+import java.util.Date;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mitre.oauth2.model.ClientDetailsEntity;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.test.context.support.WithMockUser;
+import org.springframework.test.context.junit4.SpringRunner;
+import org.springframework.test.web.servlet.MockMvc;
+
+import it.infn.mw.iam.persistence.model.IamAccount;
+import it.infn.mw.iam.persistence.model.IamAccountClient;
+import it.infn.mw.iam.persistence.repository.IamAccountRepository;
+import it.infn.mw.iam.persistence.repository.client.IamAccountClientRepository;
+import it.infn.mw.iam.persistence.repository.client.IamClientRepository;
+import it.infn.mw.iam.test.util.WithMockOAuthUser;
+import it.infn.mw.iam.test.util.annotation.IamMockMvcIntegrationTest;
+import it.infn.mw.iam.test.util.oauth.MockOAuth2Filter;
+
+@RunWith(SpringRunner.class)
+@IamMockMvcIntegrationTest
+public class AccountClientEndpointTests {
+
+ @Autowired
+ private IamAccountRepository accountRepo;
+
+ @Autowired
+ private IamAccountClientRepository accountClientRepo;
+
+ @Autowired
+ private IamClientRepository clientRepo;
+
+ @Autowired
+ private MockMvc mvc;
+
+ @Autowired
+ private MockOAuth2Filter mockOAuth2Filter;
+
+ @Before
+ public void setup() {
+ mockOAuth2Filter.cleanupSecurityContext();
+ }
+
+ @After
+ public void cleanupOAuthUser() {
+ mockOAuth2Filter.cleanupSecurityContext();
+ }
+
+ private ClientDetailsEntity buildNewClient(String clientId) {
+ ClientDetailsEntity entity = new ClientDetailsEntity();
+ entity.setClientId(clientId);
+ entity.setActive(true);
+ entity.setClientName(clientId);
+ return clientRepo.save(entity);
+ }
+
+ private IamAccountClient addNewClientFor(IamAccount a, ClientDetailsEntity c) {
+ IamAccountClient accountClient = new IamAccountClient();
+ accountClient.setAccount(a);
+ accountClient.setClient(c);
+ accountClient.setCreationTime(new Date());
+ return accountClientRepo.save(accountClient);
+ }
+
+ private void getMyClientsWorksForAdmins() throws Exception {
+ mvc.perform(get("/iam/account/me/clients"))
+ .andDo(print())
+ .andExpect(status().isOk())
+ .andExpect(jsonPath("$.Resources", is(empty())));
+
+ IamAccount admin = accountRepo.findByUsername("admin").orElseThrow();
+ ClientDetailsEntity clientAdmin = buildNewClient("client-admin");
+ IamAccountClient accountClientAdmin = addNewClientFor(admin, clientAdmin);
+
+ mvc.perform(get("/iam/account/me/clients"))
+ .andDo(print())
+ .andExpect(status().isOk())
+ .andExpect(jsonPath("$.totalResults", is(1)))
+ .andExpect(jsonPath("$.Resources", not(empty())))
+ .andExpect(jsonPath("$.Resources[0].client_id", is("client-admin")));
+
+ IamAccount test = accountRepo.findByUsername("test").orElseThrow();
+ ClientDetailsEntity clientTest = buildNewClient("client-test");
+ IamAccountClient accountClientTest = addNewClientFor(test, clientTest);
+
+ mvc.perform(get("/iam/account/me/clients"))
+ .andDo(print())
+ .andExpect(status().isOk())
+ .andExpect(jsonPath("$.totalResults", is(1)))
+ .andExpect(jsonPath("$.Resources", not(empty())))
+ .andExpect(jsonPath("$.Resources[0].client_id", is("client-admin")));
+
+ accountClientRepo.delete(accountClientAdmin);
+ accountClientRepo.delete(accountClientTest);
+ clientRepo.delete(clientAdmin);
+ clientRepo.delete(clientTest);
+ }
+
+
+ @Test
+ public void anonymousAccessToMyClientsEndpointFailsTest() throws Exception {
+ mvc.perform(get("/iam/account/me/clients"))
+ .andDo(print())
+ .andExpect(status().isUnauthorized());
+ }
+
+ @Test
+ @WithMockUser(username = "admin", roles = {"ADMIN", "USER"})
+ public void myClientsWorksForAdminsTest() throws Exception {
+ getMyClientsWorksForAdmins();
+ }
+
+ @Test
+ @WithMockOAuthUser(scopes = {"iam.admin:read"}, user = "admin", authorities = {"ROLE_ADMIN", "ROLE_USER"})
+ public void myClientsWorksForAdminsWithTokenAndScopeAdminTest() throws Exception {
+ getMyClientsWorksForAdmins();
+ }
+
+ @Test
+ @WithMockOAuthUser(user = "admin", authorities = {"ROLE_ADMIN", "ROLE_USER"})
+ public void myClientsWorksForAdminsWithTokenTest() throws Exception {
+ getMyClientsWorksForAdmins();
+ }
+
+}
diff --git a/iam-login-service/src/test/java/it/infn/mw/iam/test/api/account/password/PasswordEncodingTests.java b/iam-login-service/src/test/java/it/infn/mw/iam/test/api/account/password/PasswordEncodingTests.java
index 068e2dc15..257a58ea9 100644
--- a/iam-login-service/src/test/java/it/infn/mw/iam/test/api/account/password/PasswordEncodingTests.java
+++ b/iam-login-service/src/test/java/it/infn/mw/iam/test/api/account/password/PasswordEncodingTests.java
@@ -36,6 +36,7 @@
import com.fasterxml.jackson.databind.ObjectMapper;
+import it.infn.mw.iam.api.account.password_reset.ResetPasswordDTO;
import it.infn.mw.iam.persistence.model.IamAccount;
import it.infn.mw.iam.persistence.repository.IamAccountRepository;
import it.infn.mw.iam.registration.PersistentUUIDTokenGenerator;
@@ -76,11 +77,35 @@ public void cleanupOAuthUser() {
mockOAuth2Filter.cleanupSecurityContext();
}
+ @Test
+ public void testNoValidResetToken() throws Exception {
+ String username = "password_encoded";
+
+ RegistrationRequestDto request = new RegistrationRequestDto();
+ request.setGivenname("Password encoded");
+ request.setFamilyname("Test");
+ request.setEmail("password_encoded@example.org");
+ request.setUsername(username);
+ request.setNotes("Some short notes...");
+
+ mvc
+ .perform(post("/registration/create").contentType(MediaType.APPLICATION_JSON)
+ .content(mapper.writeValueAsString(request)))
+ .andExpect(status().isOk())
+ .andReturn()
+ .getResponse()
+ .getContentAsString();
+
+ String confirmationKey = "NoValidToken";
+ mvc.perform(get("/registration/confirm/{token}", confirmationKey).contentType(APPLICATION_JSON))
+ .andExpect(status().isNotFound());
+
+ }
@Test
public void testPasswordEncoded() throws Exception {
String username = "password_encoded";
- String newPassword = "secure_password";
+ String newPassword = "Secure_P@ssw0rd!";
RegistrationRequestDto request = new RegistrationRequestDto();
request.setGivenname("Password encoded");
@@ -100,9 +125,7 @@ public void testPasswordEncoded() throws Exception {
request = mapper.readValue(rs, RegistrationRequestDto.class);
String confirmationKey = tokenGenerator.getLastToken();
- mvc
- .perform(get("/registration/confirm/{token}", confirmationKey)
- .contentType(APPLICATION_JSON))
+ mvc.perform(get("/registration/confirm/{token}", confirmationKey).contentType(APPLICATION_JSON))
.andExpect(status().isOk());
mvc.perform(post("/registration/approve/{uuid}", request.getUuid())
@@ -111,19 +134,20 @@ public void testPasswordEncoded() throws Exception {
String resetKey = tokenGenerator.getLastToken();
+ ResetPasswordDTO dto = new ResetPasswordDTO();
+ dto.setUpdatedPassword(newPassword);
+ dto.setToken(resetKey);
+
mvc
- .perform(post("/iam/password-reset").param("token", resetKey)
- .param("password", newPassword)
+ .perform(post("/iam/password-reset").content(mapper.writeValueAsString(dto))
.with(authentication(adminAuthentication()))
.contentType(MediaType.APPLICATION_JSON))
- .andExpect(MockMvcResultMatchers.status().isOk());
+ .andExpect(MockMvcResultMatchers.status().isCreated());
IamAccount account = iamAccountRepository.findByUuid(request.getAccountId())
.orElseThrow(() -> new AssertionError("Expected account not found"));
Assert.assertTrue(passwordEncoder.matches(newPassword, account.getPassword()));
-
-
}
}
diff --git a/iam-login-service/src/test/java/it/infn/mw/iam/test/api/account/password/PasswordResetTests.java b/iam-login-service/src/test/java/it/infn/mw/iam/test/api/account/password/PasswordResetTests.java
index 7c489a1a2..bf4440b31 100644
--- a/iam-login-service/src/test/java/it/infn/mw/iam/test/api/account/password/PasswordResetTests.java
+++ b/iam-login-service/src/test/java/it/infn/mw/iam/test/api/account/password/PasswordResetTests.java
@@ -15,6 +15,10 @@
*/
package it.infn.mw.iam.test.api.account.password;
+import static it.infn.mw.iam.util.RegexUtil.PASSWORD_REGEX_MESSAGE_ERROR;
+import static java.lang.String.format;
+import static org.springframework.http.MediaType.APPLICATION_JSON;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.head;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@@ -30,7 +34,14 @@
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.google.gson.JsonObject;
+
import it.infn.mw.iam.IamLoginService;
+import it.infn.mw.iam.api.account.password_reset.ResetPasswordDTO;
+import it.infn.mw.iam.api.common.error.NoSuchAccountError;
+import it.infn.mw.iam.persistence.model.IamAccount;
+import it.infn.mw.iam.persistence.repository.IamAccountRepository;
import it.infn.mw.iam.registration.PersistentUUIDTokenGenerator;
import it.infn.mw.iam.test.core.CoreControllerTestSupport;
import it.infn.mw.iam.test.notification.NotificationTestConfig;
@@ -39,7 +50,6 @@
import it.infn.mw.iam.test.util.notification.MockNotificationDelivery;
import it.infn.mw.iam.test.util.oauth.MockOAuth2Filter;
-
@RunWith(SpringRunner.class)
@IamMockMvcIntegrationTest
@SpringBootTest(classes = {IamLoginService.class, NotificationTestConfig.class,
@@ -47,7 +57,6 @@
@WithAnonymousUser
public class PasswordResetTests {
-
@Autowired
private PersistentUUIDTokenGenerator tokenGenerator;
@@ -60,6 +69,12 @@ public class PasswordResetTests {
@Autowired
private MockMvc mvc;
+ @Autowired
+ private ObjectMapper mapper;
+
+ @Autowired
+ private IamAccountRepository accountRepo;
+
@Before
public void setup() {
mockOAuth2Filter.cleanupSecurityContext();
@@ -75,7 +90,7 @@ public void tearDown() {
public void testChangePassword() throws Exception {
String testEmail = "test@iam.test";
- String newPassword = "secure_password";
+ String newPassword = "Secure_P@ssw0rd!";
mvc.perform(post("/iam/password-reset/token").param("email", testEmail))
.andExpect(status().isOk());
@@ -84,14 +99,90 @@ public void testChangePassword() throws Exception {
mvc.perform(head("/iam/password-reset/token/{token}", resetToken)).andExpect(status().isOk());
+ ResetPasswordDTO request = new ResetPasswordDTO();
+ request.setUpdatedPassword(newPassword);
+ request.setToken(resetToken);
+
mvc
- .perform(
- post("/iam/password-reset").param("token", resetToken).param("password", newPassword))
- .andExpect(status().isOk());
+ .perform(post("/iam/password-reset").contentType(APPLICATION_JSON)
+ .content(mapper.writeValueAsString(request)))
+ .andExpect(status().isCreated());
mvc.perform(head("/iam/password-reset/token/{token}", resetToken))
.andExpect(status().isNotFound());
+ }
+
+ @Test
+ public void testChangePasswordWeak() throws Exception {
+ String testEmail = "test@iam.test";
+
+ String newPassword = "weakpassword";
+
+ mvc.perform(post("/iam/password-reset/token").param("email", testEmail))
+ .andExpect(status().isOk());
+
+ String resetToken = tokenGenerator.getLastToken();
+
+ mvc.perform(head("/iam/password-reset/token/{token}", resetToken)).andExpect(status().isOk());
+
+ JsonObject jsonBody = new JsonObject();
+ jsonBody.addProperty("updatedPassword", newPassword);
+ jsonBody.addProperty("token", resetToken);
+
+ mvc
+ .perform(
+ post("/iam/password-reset").contentType(APPLICATION_JSON).content(jsonBody.toString()))
+ .andExpect(status().isBadRequest())
+ .andExpect(MockMvcResultMatchers.content()
+ .string("Invalid reset password: [resetPasswordDTO.updatedPassword : "
+ + PASSWORD_REGEX_MESSAGE_ERROR + "]"));
+ }
+
+ @Test
+ public void testChangePasswordWithTokenJustUsed() throws Exception {
+ String testEmail = "test@iam.test";
+ String newPassword = "Secure_P@ssw0rd!";
+
+ mvc.perform(post("/iam/password-reset/token").param("email", testEmail))
+ .andExpect(status().isOk());
+
+ String resetToken = tokenGenerator.getLastToken();
+
+ mvc.perform(head("/iam/password-reset/token/{token}", resetToken)).andExpect(status().isOk());
+
+ JsonObject jsonBody = new JsonObject();
+ jsonBody.addProperty("updatedPassword", newPassword);
+ jsonBody.addProperty("token", resetToken);
+
+ mvc
+ .perform(
+ post("/iam/password-reset").contentType(APPLICATION_JSON).content(jsonBody.toString()))
+ .andExpect(status().isCreated());
+
+ mvc
+ .perform(
+ post("/iam/password-reset").contentType(APPLICATION_JSON).content(jsonBody.toString()))
+ .andExpect(status().is4xxClientError());
+ }
+
+ @Test
+ public void testRedirectToResetPasswordPage() throws Exception {
+ String resetToken = tokenGenerator.getLastToken() + "
";
+ mvc.perform(get("/iam/password-reset/token/{token}", resetToken)).andExpect(status().isOk());
+ }
+
+ @Test
+ public void testRedirectToResetPasswordPageWithValidResetKey() throws Exception {
+ String resetToken = tokenGenerator.generateToken();
+ String testEmail = "test@iam.test";
+ IamAccount account = accountRepo.findByEmail(testEmail)
+ .orElseThrow(
+ () -> new NoSuchAccountError(format("No account found for email '%s'", testEmail)));
+ account.setResetKey(resetToken);
+ accountRepo.save(account);
+
+ mvc.perform(get("/iam/password-reset/token/{token}", resetToken)).andExpect(status().isOk());
}
@Test
@@ -99,28 +190,30 @@ public void testResetPasswordWithInvalidResetToken() throws Exception {
String resetToken = "abcdefghilmnopqrstuvz";
- mvc.perform(head("/iam/password-reset/token/{token}", resetToken)).andExpect(status().isNotFound());
-
+ mvc.perform(head("/iam/password-reset/token/{token}", resetToken))
+ .andExpect(status().isNotFound());
+
}
@Test
public void testCreatePasswordResetTokenReturnsOkForUnknownAddress() throws Exception {
String testEmail = "test@foo.bar";
-
+
mvc.perform(post("/iam/password-reset/token").param("email", testEmail))
- .andExpect(status().isOk());
+ .andExpect(status().isOk());
}
@Test
public void testEmailValidationForPasswordResetTokenCreation() throws Exception {
String invalidEmailAddress = "this_is_not_an_email";
-
+
mvc.perform(post("/iam/password-reset/token").param("email", invalidEmailAddress))
- .andExpect(status().isBadRequest())
- .andExpect(MockMvcResultMatchers.content().string("validation error: please specify a valid email address"));
-
+ .andExpect(status().isBadRequest())
+ .andExpect(MockMvcResultMatchers.content()
+ .string("validation error: please specify a valid email address"));
+
}
}
diff --git a/iam-login-service/src/test/java/it/infn/mw/iam/test/api/account/password/PasswordUpdateTests.java b/iam-login-service/src/test/java/it/infn/mw/iam/test/api/account/password/PasswordUpdateTests.java
index 12d0db259..7a5227358 100644
--- a/iam-login-service/src/test/java/it/infn/mw/iam/test/api/account/password/PasswordUpdateTests.java
+++ b/iam-login-service/src/test/java/it/infn/mw/iam/test/api/account/password/PasswordUpdateTests.java
@@ -52,10 +52,8 @@ public class PasswordUpdateTests {
private final String USER_USERNAME = "password_tester_user";
private final String USER_PASSWORD = "password";
- private final ScimName USER_NAME =
- ScimName.builder().givenName("TESTER").familyName("USER").build();
- private final ScimEmail USER_EMAIL =
- ScimEmail.builder().email("password_tester_user@test.org").build();
+ private final ScimName USER_NAME = ScimName.builder().givenName("TESTER").familyName("USER").build();
+ private final ScimEmail USER_EMAIL = ScimEmail.builder().email("password_tester_user@test.org").build();
@Autowired
private ScimUserProvisioning userService;
@@ -71,13 +69,13 @@ public static void init() {
public void testSetup() {
testUser = userService.create(ScimUser.builder()
- .active(true)
- .addEmail(USER_EMAIL)
- .name(USER_NAME)
- .displayName(USER_USERNAME)
- .userName(USER_USERNAME)
- .password(USER_PASSWORD)
- .build());
+ .active(true)
+ .addEmail(USER_EMAIL)
+ .name(USER_NAME)
+ .displayName(USER_USERNAME)
+ .userName(USER_USERNAME)
+ .password(USER_PASSWORD)
+ .build());
}
@After
@@ -90,88 +88,86 @@ private ValidatableResponse doPost(String accessToken, String currentPassword,
String newPassword) {
return RestAssured.given()
- .port(iamPort)
- .auth()
- .preemptive()
- .oauth2(accessToken)
- .formParam(PasswordUpdateController.CURRENT_PASSWORD, currentPassword)
- .formParam(PasswordUpdateController.UPDATED_PASSWORD, newPassword)
- .log()
- .all(true)
- .when()
- .post(PasswordUpdateController.BASE_URL)
- .then()
- .log()
- .all(true);
+ .port(iamPort)
+ .auth()
+ .preemptive()
+ .oauth2(accessToken)
+ .formParam(PasswordUpdateController.CURRENT_PASSWORD, currentPassword)
+ .formParam(PasswordUpdateController.UPDATED_PASSWORD, newPassword)
+ .log()
+ .all(true)
+ .when()
+ .post(PasswordUpdateController.BASE_URL)
+ .then()
+ .log()
+ .all(true);
}
private ValidatableResponse doPost(String currentPassword, String newPassword) {
return RestAssured.given()
- .port(iamPort)
- .formParam(PasswordUpdateController.CURRENT_PASSWORD, currentPassword)
- .formParam(PasswordUpdateController.UPDATED_PASSWORD, newPassword)
- .log()
- .all(true)
- .when()
- .post(PasswordUpdateController.BASE_URL)
- .then()
- .log()
- .all(true);
+ .port(iamPort)
+ .formParam(PasswordUpdateController.CURRENT_PASSWORD, currentPassword)
+ .formParam(PasswordUpdateController.UPDATED_PASSWORD, newPassword)
+ .log()
+ .all(true)
+ .when()
+ .post(PasswordUpdateController.BASE_URL)
+ .then()
+ .log()
+ .all(true);
}
@Test
public void testUpdatePassword() {
String currentPassword = "password";
- String newPassword = "secure_password";
-
+ String newPassword = "Secure_p@ssw0rd";
String accessToken = passwordTokenGetter().port(iamPort)
- .username(testUser.getUserName())
- .password(currentPassword)
- .getAccessToken();
+ .username(testUser.getUserName())
+ .password(currentPassword)
+ .getAccessToken();
doPost(accessToken, currentPassword, newPassword).statusCode(HttpStatus.OK.value());
passwordTokenGetter().port(iamPort)
- .username(testUser.getUserName())
- .password(newPassword)
- .getAccessToken();
+ .username(testUser.getUserName())
+ .password(newPassword)
+ .getAccessToken();
}
@Test
public void testUpdatePasswordFullAuthenticationRequired() {
String currentPassword = "password";
- String newPassword = "secure_password";
+ String newPassword = "Secure_P@ssw0rd!";
doPost(currentPassword, newPassword).statusCode(HttpStatus.UNAUTHORIZED.value())
- .body("error", equalTo("unauthorized"))
- .body("error_description",
- equalTo("Full authentication is required to access this resource"));
+ .body("error", equalTo("unauthorized"))
+ .body("error_description",
+ equalTo("Full authentication is required to access this resource"));
}
@Test
public void testUpdateWrongPasswordProvided() {
String currentPassword = "password";
- String newPassword = "secure_password";
+ String newPassword = "Secure_P@ssw0rd!";
String accessToken = passwordTokenGetter().port(iamPort)
- .username(testUser.getUserName())
- .password(currentPassword)
- .getAccessToken();
+ .username(testUser.getUserName())
+ .password(currentPassword)
+ .getAccessToken();
doPost(accessToken, "thisisnotthecurrentpassword", newPassword)
- .statusCode(HttpStatus.BAD_REQUEST.value())
- .body(equalTo("Wrong password provided"));
+ .statusCode(HttpStatus.BAD_REQUEST.value());
}
@Test
public void testUpdatePasswordForbiddenAccess() {
String currentPassword = "password";
- String newPassword = "secure_password";
+ String newPassword = "Secure_P@ssw0rd!";
String accessToken = TestUtils.clientCredentialsTokenGetter().port(iamPort).getAccessToken();
doPost(accessToken, currentPassword, newPassword).statusCode(HttpStatus.FORBIDDEN.value());
@@ -183,12 +179,11 @@ public void testUpdatePasswordNullPasswordAccess() {
String currentPassword = "password";
String newPassword = null;
String accessToken = passwordTokenGetter().port(iamPort)
- .username(testUser.getUserName())
- .password(currentPassword)
- .getAccessToken();
+ .username(testUser.getUserName())
+ .password(currentPassword)
+ .getAccessToken();
- doPost(accessToken, currentPassword, newPassword).statusCode(HttpStatus.BAD_REQUEST.value())
- .body(containsString("The password cannot be empty"));
+ doPost(accessToken, currentPassword, newPassword).statusCode(HttpStatus.BAD_REQUEST.value());
}
@Test
@@ -197,12 +192,11 @@ public void testUpdatePasswordEmptyPasswordAccess() {
String currentPassword = "password";
String newPassword = "";
String accessToken = passwordTokenGetter().port(iamPort)
- .username(testUser.getUserName())
- .password(currentPassword)
- .getAccessToken();
+ .username(testUser.getUserName())
+ .password(currentPassword)
+ .getAccessToken();
- doPost(accessToken, currentPassword, newPassword).statusCode(HttpStatus.BAD_REQUEST.value())
- .body(containsString("The password cannot be empty"));
+ doPost(accessToken, currentPassword, newPassword).statusCode(HttpStatus.BAD_REQUEST.value());
}
@Test
@@ -211,30 +205,68 @@ public void testUpdatePasswordTooShortPasswordAccess() {
String currentPassword = "password";
String newPassword = "pass";
String accessToken = passwordTokenGetter().port(iamPort)
- .username(testUser.getUserName())
- .password(currentPassword)
- .getAccessToken();
+ .username(testUser.getUserName())
+ .password(currentPassword)
+ .getAccessToken();
+
+ doPost(accessToken, currentPassword, newPassword).statusCode(HttpStatus.BAD_REQUEST.value());
+ }
+
+ @Test
+ public void testUpdatePasswordWithWeakPasswordAccess() {
+
+ String currentPassword = "password";
+ String newPassword = "newweakpassword";
+ String accessToken = passwordTokenGetter().port(iamPort)
+ .username(testUser.getUserName())
+ .password(currentPassword)
+ .getAccessToken();
+
+ doPost(accessToken, currentPassword, newPassword).statusCode(HttpStatus.BAD_REQUEST.value());
+ }
+
+ @Test
+ public void testUpdatePasswordWithWeakPasswordWithoutSpecialChars() {
+
+ String currentPassword = "password";
+ String newPassword = "Password1";
+ String accessToken = passwordTokenGetter().port(iamPort)
+ .username(testUser.getUserName())
+ .password(currentPassword)
+ .getAccessToken();
+
+ doPost(accessToken, currentPassword, newPassword).statusCode(HttpStatus.BAD_REQUEST.value());
+ }
+
+ @Test
+ public void testUpdatePasswordWithWeakPasswordWithoutNumbers() {
+
+ String currentPassword = "password";
+ String newPassword = "Sjfyt-hdddW!";
+ String accessToken = passwordTokenGetter().port(iamPort)
+ .username(testUser.getUserName())
+ .password(currentPassword)
+ .getAccessToken();
- doPost(accessToken, currentPassword, newPassword).statusCode(HttpStatus.BAD_REQUEST.value())
- .body(containsString("The password must be at least 5 characters"));
+ doPost(accessToken, currentPassword, newPassword).statusCode(HttpStatus.BAD_REQUEST.value());
}
@Test
public void testUpdatePasswordUserNotActive() throws Exception {
String currentPassword = "password";
- String newPassword = "newPassword";
+ String newPassword = "newP@ssw0rd";
String accessToken = passwordTokenGetter().port(iamPort)
- .username(testUser.getUserName())
- .password(currentPassword)
- .getAccessToken();
+ .username(testUser.getUserName())
+ .password(currentPassword)
+ .getAccessToken();
IamAccount account = accountRepository.findByUsername(testUser.getUserName())
- .orElseThrow(() -> new Exception("Test user not found"));
+ .orElseThrow(() -> new Exception("Test user not found"));
account.setActive(false);
accountRepository.save(account);
doPost(accessToken, currentPassword, newPassword).statusCode(HttpStatus.CONFLICT.value())
- .body(containsString("Account is not active or email is not verified"));
+ .body(containsString("Account is not active or email is not verified"));
}
}
diff --git a/iam-login-service/src/test/java/it/infn/mw/iam/test/api/aup/AupIntegrationTests.java b/iam-login-service/src/test/java/it/infn/mw/iam/test/api/aup/AupIntegrationTests.java
index 2033f7b87..b6d891540 100644
--- a/iam-login-service/src/test/java/it/infn/mw/iam/test/api/aup/AupIntegrationTests.java
+++ b/iam-login-service/src/test/java/it/infn/mw/iam/test/api/aup/AupIntegrationTests.java
@@ -15,8 +15,8 @@
*/
package it.infn.mw.iam.test.api.aup;
-import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.equalTo;
import static org.springframework.http.MediaType.APPLICATION_JSON;
import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete;
@@ -125,7 +125,9 @@ public void aupIsReturnedIfDefined() throws Exception {
@Test
public void aupCreationRequiresAuthenticatedUser() throws JsonProcessingException, Exception {
Date now = new Date();
- AupDTO aup = new AupDTO(DEFAULT_AUP_URL, DEFAULT_AUP_TEXT, DEFAULT_AUP_DESC, -1L, now, now);
+ String reminders = "1,15,30";
+ AupDTO aup =
+ new AupDTO(DEFAULT_AUP_URL, DEFAULT_AUP_TEXT, DEFAULT_AUP_DESC, -1L, now, now, reminders);
mvc
.perform(
@@ -138,7 +140,9 @@ public void aupCreationRequiresAuthenticatedUser() throws JsonProcessingExceptio
@WithMockUser(username = "test", roles = {"USER"})
public void aupCreationRequiresAdminPrivileges() throws JsonProcessingException, Exception {
Date now = new Date();
- AupDTO aup = new AupDTO(DEFAULT_AUP_URL, DEFAULT_AUP_TEXT, DEFAULT_AUP_DESC, -1L, now, now);
+ String reminders = "1,15,30";
+ AupDTO aup =
+ new AupDTO(DEFAULT_AUP_URL, DEFAULT_AUP_TEXT, DEFAULT_AUP_DESC, -1L, now, now, reminders);
mvc
.perform(
@@ -224,11 +228,11 @@ public void aupDescriptionNoLongerThan128Chars() throws JsonProcessingException,
}
-
@Test
@WithMockUser(username = "admin", roles = {"ADMIN", "USER"})
public void aupCreationRequiresSignatureValidityDays() throws JsonProcessingException, Exception {
- AupDTO aup = new AupDTO(DEFAULT_AUP_URL, DEFAULT_AUP_TEXT, null, null, null, null);
+ String reminders = "1,15,30";
+ AupDTO aup = new AupDTO(DEFAULT_AUP_URL, DEFAULT_AUP_TEXT, null, null, null, null, reminders);
Date now = new Date();
mockTimeProvider.setTime(now.getTime());
@@ -244,7 +248,8 @@ public void aupCreationRequiresSignatureValidityDays() throws JsonProcessingExce
@WithMockUser(username = "admin", roles = {"ADMIN", "USER"})
public void aupCreationRequiresPositiveSignatureValidityDays()
throws JsonProcessingException, Exception {
- AupDTO aup = new AupDTO(DEFAULT_AUP_URL, DEFAULT_AUP_TEXT, null, -1L, null, null);
+ String reminders = "1,15,30";
+ AupDTO aup = new AupDTO(DEFAULT_AUP_URL, DEFAULT_AUP_TEXT, null, -1L, null, null, reminders);
Date now = new Date();
mockTimeProvider.setTime(now.getTime());
@@ -255,6 +260,109 @@ public void aupCreationRequiresPositiveSignatureValidityDays()
.andExpect(jsonPath("$.error").value("Invalid AUP: signatureValidityInDays must be >= 0"));
}
+ @Test
+ @WithMockUser(username = "admin", roles = {"ADMIN", "USER"})
+ public void aupCreationRequiresAupRemindersInDays() throws Exception {
+ AupDTO aup = new AupDTO(DEFAULT_AUP_URL, DEFAULT_AUP_TEXT, null, 3L, null, null, null);
+ Date now = new Date();
+ mockTimeProvider.setTime(now.getTime());
+
+ mvc
+ .perform(
+ post("/iam/aup").contentType(APPLICATION_JSON).content(mapper.writeValueAsString(aup)))
+ .andExpect(status().isBadRequest())
+ .andExpect(jsonPath("$.error").value("Invalid AUP: aupRemindersInDays cannot be empty or null"));
+ }
+
+ @Test
+ @WithMockUser(username = "admin", roles = {"ADMIN", "USER"})
+ public void aupCreationRequiresAupRemindersInDaysNotEmpty() throws Exception {
+ AupDTO aup = new AupDTO(DEFAULT_AUP_URL, DEFAULT_AUP_TEXT, null, 3L, null, null, "");
+ Date now = new Date();
+ mockTimeProvider.setTime(now.getTime());
+
+ mvc
+ .perform(
+ post("/iam/aup").contentType(APPLICATION_JSON).content(mapper.writeValueAsString(aup)))
+ .andExpect(status().isBadRequest())
+ .andExpect(jsonPath("$.error").value("Invalid AUP: aupRemindersInDays cannot be empty or null"));
+ }
+
+ @Test
+ @WithMockUser(username = "admin", roles = {"ADMIN", "USER"})
+ public void aupCreationRequiresNoZeroInAupRemindersInDays() throws Exception {
+ AupDTO aup = new AupDTO(DEFAULT_AUP_URL, DEFAULT_AUP_TEXT, null, 3L, null, null, "0");
+ Date now = new Date();
+ mockTimeProvider.setTime(now.getTime());
+
+ mvc
+ .perform(
+ post("/iam/aup").contentType(APPLICATION_JSON).content(mapper.writeValueAsString(aup)))
+ .andExpect(status().isBadRequest())
+ .andExpect(jsonPath("$.error").value(
+ "Invalid AUP: zero or negative values for reminders are not allowed"));
+ }
+
+ @Test
+ @WithMockUser(username = "admin", roles = {"ADMIN", "USER"})
+ public void aupCreationRequiresPositiveAupRemindersInDays() throws Exception {
+ AupDTO aup = new AupDTO(DEFAULT_AUP_URL, DEFAULT_AUP_TEXT, null, 3L, null, null, "-22");
+ Date now = new Date();
+ mockTimeProvider.setTime(now.getTime());
+
+ mvc
+ .perform(
+ post("/iam/aup").contentType(APPLICATION_JSON).content(mapper.writeValueAsString(aup)))
+ .andExpect(status().isBadRequest())
+ .andExpect(jsonPath("$.error").value(
+ "Invalid AUP: zero or negative values for reminders are not allowed"));
+ }
+
+ @Test
+ @WithMockUser(username = "admin", roles = {"ADMIN", "USER"})
+ public void aupCreationRequiresNoLettersInAupRemindersInDays() throws Exception {
+ AupDTO aup = new AupDTO(DEFAULT_AUP_URL, DEFAULT_AUP_TEXT, null, 3L, null, null, "ciao");
+ Date now = new Date();
+ mockTimeProvider.setTime(now.getTime());
+
+ mvc
+ .perform(
+ post("/iam/aup").contentType(APPLICATION_JSON).content(mapper.writeValueAsString(aup)))
+ .andExpect(status().isBadRequest())
+ .andExpect(jsonPath("$.error").value(
+ "Invalid AUP: non-integer value found"));
+ }
+
+ @Test
+ @WithMockUser(username = "admin", roles = {"ADMIN", "USER"})
+ public void aupCreationRequiresNoDuplicationInAupRemindersInDays() throws Exception {
+ AupDTO aup = new AupDTO(DEFAULT_AUP_URL, DEFAULT_AUP_TEXT, null, 31L, null, null, "30,15,15");
+ Date now = new Date();
+ mockTimeProvider.setTime(now.getTime());
+
+ mvc
+ .perform(
+ post("/iam/aup").contentType(APPLICATION_JSON).content(mapper.writeValueAsString(aup)))
+ .andExpect(status().isBadRequest())
+ .andExpect(jsonPath("$.error").value(
+ "Invalid AUP: duplicate values for reminders are not allowed"));
+ }
+
+ @Test
+ @WithMockUser(username = "admin", roles = {"ADMIN", "USER"})
+ public void aupCreationRequiresAupRemindersInDaysSmallerThanAupExpirationDays() throws Exception {
+ AupDTO aup = new AupDTO(DEFAULT_AUP_URL, DEFAULT_AUP_TEXT, null, 3L, null, null, "4");
+ Date now = new Date();
+ mockTimeProvider.setTime(now.getTime());
+
+ mvc
+ .perform(
+ post("/iam/aup").contentType(APPLICATION_JSON).content(mapper.writeValueAsString(aup)))
+ .andExpect(status().isBadRequest())
+ .andExpect(jsonPath("$.error").value(
+ "Invalid AUP: aupRemindersInDays must be smaller than signatureValidityInDays"));
+ }
+
@Test
@WithMockUser(username = "admin", roles = {"ADMIN", "USER"})
public void aupCreationWorks() throws JsonProcessingException, Exception {
@@ -285,6 +393,36 @@ public void aupCreationWorks() throws JsonProcessingException, Exception {
assertThat(createdAup.getLastUpdateTime(), creationAndLastUpdateTimeMatcher);
}
+ @Test
+ @WithMockUser(username = "admin", roles = {"ADMIN", "USER"})
+ public void whiteSpacesAllowedAmongAupRemindersDays() throws Exception {
+ AupDTO aup = new AupDTO(DEFAULT_AUP_URL, DEFAULT_AUP_TEXT, null, 31L, null, null, " 30, 15, 7 ");
+
+ Date now = new Date();
+ mockTimeProvider.setTime(now.getTime());
+
+ mvc
+ .perform(
+ post("/iam/aup").contentType(APPLICATION_JSON).content(mapper.writeValueAsString(aup)))
+ .andExpect(status().isCreated());
+
+
+ String aupJson = mvc.perform(get("/iam/aup"))
+ .andExpect(status().isOk())
+ .andReturn()
+ .getResponse()
+ .getContentAsString();
+
+ AupDTO createdAup = mapper.readValue(aupJson, AupDTO.class);
+
+ DateEqualModulo1Second creationAndLastUpdateTimeMatcher = new DateEqualModulo1Second(now);
+ assertThat(createdAup.getUrl(), equalTo(aup.getUrl()));
+ assertThat(createdAup.getDescription(), equalTo(aup.getDescription()));
+ assertThat(createdAup.getSignatureValidityInDays(), equalTo(aup.getSignatureValidityInDays()));
+ assertThat(createdAup.getCreationTime(), creationAndLastUpdateTimeMatcher);
+ assertThat(createdAup.getLastUpdateTime(), creationAndLastUpdateTimeMatcher);
+ }
+
@Test
@WithMockUser(username = "admin", roles = {"ADMIN", "USER"})
public void aupCreationFailsIfAupAlreadyDefined() throws JsonProcessingException, Exception {
@@ -396,7 +534,7 @@ public void aupUpdateWorks() throws JsonProcessingException, Exception {
aup.setUrl(UPDATED_AUP_URL);
aup.setDescription(UPDATED_AUP_DESC);
- aup.setSignatureValidityInDays(18L);
+ aup.setSignatureValidityInDays(31L);
// Time travel 1 minute in the future
Date then = new Date(now.getTime() + TimeUnit.MINUTES.toMillis(1));
@@ -416,7 +554,7 @@ public void aupUpdateWorks() throws JsonProcessingException, Exception {
assertThat(updatedAup.getDescription(), equalTo(UPDATED_AUP_DESC));
assertThat(updatedAup.getCreationTime(), new DateEqualModulo1Second(now));
assertThat(updatedAup.getLastUpdateTime(), new DateEqualModulo1Second(now));
- assertThat(updatedAup.getSignatureValidityInDays(), equalTo(18L));
+ assertThat(updatedAup.getSignatureValidityInDays(), equalTo(31L));
}
}
diff --git a/iam-login-service/src/test/java/it/infn/mw/iam/test/api/aup/AupReminderTaskTests.java b/iam-login-service/src/test/java/it/infn/mw/iam/test/api/aup/AupReminderTaskTests.java
new file mode 100644
index 000000000..367caa594
--- /dev/null
+++ b/iam-login-service/src/test/java/it/infn/mw/iam/test/api/aup/AupReminderTaskTests.java
@@ -0,0 +1,227 @@
+/**
+ * Copyright (c) Istituto Nazionale di Fisica Nucleare (INFN). 2016-2021
+ *
+ * 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 it.infn.mw.iam.test.api.aup;
+
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+
+import java.time.LocalDate;
+import java.time.ZoneId;
+import java.util.Date;
+import java.util.concurrent.TimeUnit;
+
+import org.junit.After;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
+import org.springframework.security.test.context.support.WithMockUser;
+import org.springframework.test.context.TestPropertySource;
+import org.springframework.test.context.junit4.SpringRunner;
+
+import it.infn.mw.iam.IamLoginService;
+import it.infn.mw.iam.core.web.aup.AupReminderTask;
+import it.infn.mw.iam.persistence.model.IamAccount;
+import it.infn.mw.iam.persistence.model.IamAup;
+import it.infn.mw.iam.persistence.repository.IamAccountRepository;
+import it.infn.mw.iam.persistence.repository.IamAupRepository;
+import it.infn.mw.iam.persistence.repository.IamAupSignatureRepository;
+import it.infn.mw.iam.persistence.repository.IamEmailNotificationRepository;
+import it.infn.mw.iam.service.aup.DefaultAupSignatureCheckService;
+import it.infn.mw.iam.test.core.CoreControllerTestSupport;
+import it.infn.mw.iam.test.notification.NotificationTestConfig;
+import it.infn.mw.iam.test.util.MockTimeProvider;
+import it.infn.mw.iam.test.util.WithAnonymousUser;
+import it.infn.mw.iam.test.util.annotation.IamMockMvcIntegrationTest;
+import it.infn.mw.iam.test.util.notification.MockNotificationDelivery;
+
+@RunWith(SpringRunner.class)
+@IamMockMvcIntegrationTest
+@SpringBootTest(classes = {IamLoginService.class, CoreControllerTestSupport.class,
+ NotificationTestConfig.class}, webEnvironment = WebEnvironment.MOCK)
+@WithAnonymousUser
+@TestPropertySource(properties = {"notification.disable=false"})
+public class AupReminderTaskTests extends AupTestSupport {
+
+ @Autowired
+ private DefaultAupSignatureCheckService service;
+
+ @Autowired
+ private IamAccountRepository accountRepo;
+
+ @Autowired
+ private IamAupSignatureRepository signatureRepo;
+
+ @Autowired
+ private IamEmailNotificationRepository notificationRepo;
+
+ @Autowired
+ private AupReminderTask aupReminderTask;
+
+ @Autowired
+ private MockNotificationDelivery notificationDelivery;
+
+ @Autowired
+ private IamAupRepository aupRepo;
+
+ @Autowired
+ private MockTimeProvider mockTimeProvider;
+
+ @After
+ public void tearDown() {
+ notificationDelivery.clearDeliveredNotifications();
+ aupRepo.deleteAll();
+ }
+
+ @Test
+ @WithMockUser(username = "admin", roles = {"ADMIN", "USER"})
+ public void aupReminderEmailWorks() {
+ IamAup aup = buildDefaultAup();
+ aup.setSignatureValidityInDays(30L);
+ aupRepo.save(aup);
+
+ Date now = new Date();
+ mockTimeProvider.setTime(now.getTime());
+ LocalDate today = LocalDate.now();
+ LocalDate tomorrow = today.plusDays(1);
+ Date tomorrowDate = Date.from(tomorrow.atStartOfDay(ZoneId.systemDefault()).toInstant());
+
+ IamAccount testAccount = accountRepo.findByUsername("test")
+ .orElseThrow(() -> new AssertionError("Expected test account not found"));
+
+ mockTimeProvider.setTime(now.getTime() + TimeUnit.MINUTES.toMillis(5));
+
+ assertThat(service.needsAupSignature(testAccount), is(true));
+
+ signatureRepo.createSignatureForAccount(aup, testAccount,
+ new Date(mockTimeProvider.currentTimeMillis()));
+
+ assertThat(service.needsAupSignature(testAccount), is(false));
+
+ mockTimeProvider.setTime(now.getTime() + TimeUnit.MINUTES.toMillis(10));
+
+ assertThat(notificationRepo.countAupRemindersPerAccount(testAccount.getUserInfo().getEmail(),
+ tomorrowDate), equalTo(0));
+
+ aupReminderTask.sendAupReminders();
+ notificationDelivery.sendPendingNotifications();
+ assertThat(notificationRepo.countAupRemindersPerAccount(testAccount.getUserInfo().getEmail(),
+ tomorrowDate), equalTo(1));
+
+ }
+
+ @Test
+ @WithMockUser(username = "admin", roles = {"ADMIN", "USER"})
+ public void aupExpirationEmailWorks() {
+ IamAup aup = buildDefaultAup();
+ aup.setSignatureValidityInDays(2L);
+
+ LocalDate today = LocalDate.now();
+ LocalDate twoDaysAgo = today.minusDays(2);
+
+ Date date = Date.from(twoDaysAgo.atStartOfDay(ZoneId.systemDefault()).toInstant());
+ aup.setCreationTime(date);
+ aup.setLastUpdateTime(date);
+
+ aupRepo.save(aup);
+
+ IamAccount testAccount = accountRepo.findByUsername("test")
+ .orElseThrow(() -> new AssertionError("Expected test account not found"));
+
+ signatureRepo.createSignatureForAccount(aup, testAccount, date);
+
+ assertThat(
+ notificationRepo.countAupExpirationMessPerAccount(testAccount.getUserInfo().getEmail()),
+ equalTo(0));
+
+ aupReminderTask.sendAupReminders();
+ notificationDelivery.sendPendingNotifications();
+ assertThat(
+ notificationRepo.countAupExpirationMessPerAccount(testAccount.getUserInfo().getEmail()),
+ equalTo(1));
+
+ aupReminderTask.sendAupReminders();
+ notificationDelivery.sendPendingNotifications();
+ assertThat(
+ notificationRepo.countAupExpirationMessPerAccount(testAccount.getUserInfo().getEmail()),
+ equalTo(1));
+
+ }
+
+ @Test
+ @WithMockUser(username = "admin", roles = {"ADMIN", "USER"})
+ public void aupExpirationEmailNotSentIfUserIsDisabled() {
+ IamAup aup = buildDefaultAup();
+ aup.setSignatureValidityInDays(2L);
+
+ LocalDate today = LocalDate.now();
+ LocalDate twoDaysAgo = today.minusDays(2);
+
+ Date date = Date.from(twoDaysAgo.atStartOfDay(ZoneId.systemDefault()).toInstant());
+ aup.setCreationTime(date);
+ aup.setLastUpdateTime(date);
+
+ aupRepo.save(aup);
+
+ IamAccount testAccount = accountRepo.findByUsername("test")
+ .orElseThrow(() -> new AssertionError("Expected test account not found"));
+
+ signatureRepo.createSignatureForAccount(aup, testAccount, date);
+
+ assertThat(
+ notificationRepo.countAupExpirationMessPerAccount(testAccount.getUserInfo().getEmail()),
+ equalTo(0));
+
+ testAccount.setActive(false);
+ accountRepo.save(testAccount);
+
+ aupReminderTask.sendAupReminders();
+ notificationDelivery.sendPendingNotifications();
+ assertThat(
+ notificationRepo.countAupExpirationMessPerAccount(testAccount.getUserInfo().getEmail()),
+ equalTo(0));
+
+ }
+
+ @Test
+ @WithMockUser(username = "admin", roles = {"ADMIN", "USER"})
+ public void aupExpirationEmailNotSentIfAupSignatureValidityIsZero() {
+ IamAup aup = buildDefaultAup();
+ aup.setSignatureValidityInDays(0L);
+
+ LocalDate today = LocalDate.now();
+ Date date = Date.from(today.atStartOfDay(ZoneId.systemDefault()).toInstant());
+
+ aup.setCreationTime(date);
+ aup.setLastUpdateTime(date);
+
+ aupRepo.save(aup);
+
+ IamAccount testAccount = accountRepo.findByUsername("test")
+ .orElseThrow(() -> new AssertionError("Expected test account not found"));
+
+ signatureRepo.createSignatureForAccount(aup, testAccount, date);
+
+ aupReminderTask.sendAupReminders();
+ notificationDelivery.sendPendingNotifications();
+ assertThat(
+ notificationRepo.countAupExpirationMessPerAccount(testAccount.getUserInfo().getEmail()),
+ equalTo(0));
+
+ }
+}
diff --git a/iam-login-service/src/test/java/it/infn/mw/iam/test/api/aup/AupSignatureIntegrationTests.java b/iam-login-service/src/test/java/it/infn/mw/iam/test/api/aup/AupSignatureIntegrationTests.java
index 95af83605..67167bc02 100644
--- a/iam-login-service/src/test/java/it/infn/mw/iam/test/api/aup/AupSignatureIntegrationTests.java
+++ b/iam-login-service/src/test/java/it/infn/mw/iam/test/api/aup/AupSignatureIntegrationTests.java
@@ -55,6 +55,7 @@
import it.infn.mw.iam.test.util.DateEqualModulo1Second;
import it.infn.mw.iam.test.util.MockTimeProvider;
import it.infn.mw.iam.test.util.WithAnonymousUser;
+import it.infn.mw.iam.test.util.WithMockOAuthUser;
import it.infn.mw.iam.test.util.annotation.IamMockMvcIntegrationTest;
import it.infn.mw.iam.test.util.oauth.MockOAuth2Filter;
@@ -214,6 +215,38 @@ public void signatureOnBehalfWorks() throws Exception, NoSuchElementException {
}
+ @Test
+ @WithMockOAuthUser(scopes = "iam:admin.write", clientId = "client-cred")
+ public void signatureOnBehalfWithClientCredentialsWorks() throws Exception, NoSuchElementException {
+
+ IamAup aup = buildDefaultAup();
+ aupRepo.save(aup);
+ IamAccount testAccount = accountRepo.findByUsername("test").orElseThrow();
+ Date now = new Date();
+ mockTimeProvider.setTime(now.getTime());
+
+ Optional
signature =
+ aupSignatureRepo.findSignatureForAccount(aup, testAccount);
+ assertThat(signature.isEmpty(), equalTo(true));
+
+ AupSignatureDTO dto = new AupSignatureDTO();
+ dto.setAup(aupConverter.dtoFromEntity(aup));
+ AccountDTO accountDto = new AccountDTO();
+ accountDto.setName(testAccount.getUserInfo().getName());
+ accountDto.setUsername(testAccount.getUsername());
+ accountDto.setUuid(testAccount.getUuid());
+ dto.setAccount(accountDto);
+ dto.setSignatureTime(new Date());
+
+ mvc.perform(patch("/iam/aup/signature/{accountId}", testAccount.getUuid())
+ .content(mapper.writeValueAsString(dto))
+ .contentType(APPLICATION_JSON)).andExpect(status().isCreated());
+
+ assertThat(aupSignatureRepo.findSignatureForAccount(aup, testAccount).isEmpty(),
+ equalTo(false));
+
+ }
+
@Test
@WithMockUser(username = "admin", roles = {"ADMIN", "USER"})
public void aupRemovalForSingleUser() throws Exception {
@@ -240,6 +273,36 @@ public void aupRemovalForSingleUser() throws Exception {
}
+ @Test
+ @WithMockOAuthUser(scopes = "iam:admin.write", clientId = "client-cred")
+ public void aupRemovalForSingleUserWithClientCredentialsWorks() throws Exception {
+ IamAup aup = buildDefaultAup();
+ aupRepo.save(aup);
+ IamAccount testAccount = accountRepo.findByUsername("test").orElseThrow();
+ Date now = new Date();
+ mockTimeProvider.setTime(now.getTime());
+
+ Optional signature =
+ aupSignatureRepo.findSignatureForAccount(aup, testAccount);
+ assertThat(signature.isEmpty(), equalTo(true));
+
+ AupSignatureDTO dto = new AupSignatureDTO();
+ dto.setAup(aupConverter.dtoFromEntity(aup));
+ AccountDTO accountDto = new AccountDTO();
+ accountDto.setName(testAccount.getUserInfo().getName());
+ accountDto.setUsername(testAccount.getUsername());
+ accountDto.setUuid(testAccount.getUuid());
+ dto.setAccount(accountDto);
+ dto.setSignatureTime(new Date());
+
+ mvc.perform(patch("/iam/aup/signature/{accountId}", testAccount.getUuid())
+ .content(mapper.writeValueAsString(dto))
+ .contentType(APPLICATION_JSON)).andExpect(status().isCreated());
+ mvc.perform(delete("/iam/aup/signature/" + testAccount.getUuid()))
+ .andExpect(status().isNoContent());
+
+ }
+
@Test
@WithMockUser(username = "admin", roles = {"ADMIN", "USER"})
public void aupRemovalRemovesSignatureRecords() throws Exception {
diff --git a/iam-login-service/src/test/java/it/infn/mw/iam/test/api/aup/AupTestSupport.java b/iam-login-service/src/test/java/it/infn/mw/iam/test/api/aup/AupTestSupport.java
index 336433a4c..80b7bd72d 100644
--- a/iam-login-service/src/test/java/it/infn/mw/iam/test/api/aup/AupTestSupport.java
+++ b/iam-login-service/src/test/java/it/infn/mw/iam/test/api/aup/AupTestSupport.java
@@ -37,6 +37,7 @@ public IamAup buildDefaultAup() {
aup.setCreationTime(now);
aup.setLastUpdateTime(now);
aup.setSignatureValidityInDays(365L);
+ aup.setAupRemindersInDays("30,15,1");
return aup;
}
diff --git a/iam-login-service/src/test/java/it/infn/mw/iam/test/core/IamStatisticalEndpointTests.java b/iam-login-service/src/test/java/it/infn/mw/iam/test/core/IamStatisticalEndpointTests.java
new file mode 100644
index 000000000..53fa80cb6
--- /dev/null
+++ b/iam-login-service/src/test/java/it/infn/mw/iam/test/core/IamStatisticalEndpointTests.java
@@ -0,0 +1,56 @@
+/**
+ * Copyright (c) Istituto Nazionale di Fisica Nucleare (INFN). 2016-2021
+ *
+ * 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 it.infn.mw.iam.test.core;
+
+import static org.hamcrest.Matchers.equalTo;
+import static org.junit.Assert.assertEquals;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
+import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.test.context.junit4.SpringRunner;
+import org.springframework.test.web.servlet.MockMvc;
+
+import it.infn.mw.iam.core.StatsEndpointResponse;
+import it.infn.mw.iam.test.util.annotation.IamMockMvcIntegrationTest;
+
+@RunWith(SpringRunner.class)
+@IamMockMvcIntegrationTest
+public class IamStatisticalEndpointTests {
+
+ @Autowired
+ protected MockMvc mvc;
+
+ @Test
+ public void anonymousisAcceptedAtStatEndpoint() throws Exception {
+ mvc.perform(get("/stats"))
+ .andDo(print())
+ .andExpect(status().isOk())
+ .andExpect(jsonPath("$.numberOfUsers", equalTo(255)));
+ }
+
+ @Test
+ public void testSetNumberOfUsers() {
+ StatsEndpointResponse userCount = new StatsEndpointResponse(0);
+ userCount.setNumberOfUsers(255);
+ assertEquals(255, userCount.getNumberOfUsers());
+ }
+
+}
diff --git a/iam-login-service/src/test/java/it/infn/mw/iam/test/ext_authn/oidc/validator/OidcValidatorIntegrationTests.java b/iam-login-service/src/test/java/it/infn/mw/iam/test/ext_authn/oidc/validator/OidcValidatorIntegrationTests.java
index 2f21cc15c..2ae7569ad 100644
--- a/iam-login-service/src/test/java/it/infn/mw/iam/test/ext_authn/oidc/validator/OidcValidatorIntegrationTests.java
+++ b/iam-login-service/src/test/java/it/infn/mw/iam/test/ext_authn/oidc/validator/OidcValidatorIntegrationTests.java
@@ -105,8 +105,8 @@ public void testValidatorError() throws JOSEException,
UriComponentsBuilder.fromUri(response.getHeaders().getLocation()).build();
assertThat(locationUri.getPath(), equalTo("/login"));
- assertThat(locationUri.getQueryParams().keySet(), hasItem("externalAuthenticationError"));
- assertThat(locationUri.getQueryParams().getFirst("externalAuthenticationError"), is("always%20fails"));
+ assertThat(locationUri.getQueryParams().keySet(), hasItem("error"));
+ assertThat(locationUri.getQueryParams().getFirst("error"), is("true"));
}
diff --git a/iam-login-service/src/test/java/it/infn/mw/iam/test/login/LoginTests.java b/iam-login-service/src/test/java/it/infn/mw/iam/test/login/LoginTests.java
index 74efa1586..7fbb1f033 100644
--- a/iam-login-service/src/test/java/it/infn/mw/iam/test/login/LoginTests.java
+++ b/iam-login-service/src/test/java/it/infn/mw/iam/test/login/LoginTests.java
@@ -115,6 +115,7 @@ public void loginRedirectsToSignAupPageWhenNeeded() throws Exception {
aup.setUrl("http://default-aup.org/");
aup.setDescription("AUP description");
aup.setSignatureValidityInDays(0L);
+ aup.setAupRemindersInDays("30,15,1");
aupRepo.save(aup);
diff --git a/iam-login-service/src/test/java/it/infn/mw/iam/test/notification/RegistrationFlowNotificationTests.java b/iam-login-service/src/test/java/it/infn/mw/iam/test/notification/RegistrationFlowNotificationTests.java
index 2ef619a99..02bda33ea 100644
--- a/iam-login-service/src/test/java/it/infn/mw/iam/test/notification/RegistrationFlowNotificationTests.java
+++ b/iam-login-service/src/test/java/it/infn/mw/iam/test/notification/RegistrationFlowNotificationTests.java
@@ -110,6 +110,10 @@ public void tearDown() throws InterruptedException {
notificationDelivery.clearDeliveredNotifications();
}
+ public String formatSubject(String key) {
+ return String.format("[%s IAM] %s", organisationName, properties.getSubject().get(key));
+ }
+
@Test
public void testSendWithEmptyQueue() {
@@ -145,7 +149,7 @@ public void testApproveFlowNotifications() throws Exception {
IamEmailNotification message = notificationDelivery.getDeliveredNotifications().get(0);
- assertThat(message.getSubject(), equalTo(properties.getSubject().get("confirmation")));
+ assertThat(message.getSubject(), equalTo(formatSubject("confirmation")));
notificationDelivery.clearDeliveredNotifications();
@@ -161,7 +165,7 @@ public void testApproveFlowNotifications() throws Exception {
message = notificationDelivery.getDeliveredNotifications().get(0);
- assertThat(message.getSubject(), equalTo(properties.getSubject().get("adminHandleRequest")));
+ assertThat(message.getSubject(), equalTo(formatSubject("adminHandleRequest")));
assertThat(message.getReceivers(), hasSize(1));
assertThat(message.getReceivers().get(0).getEmailAddress(),
@@ -180,7 +184,7 @@ public void testApproveFlowNotifications() throws Exception {
message = notificationDelivery.getDeliveredNotifications().get(0);
- assertThat(message.getSubject(), equalTo(properties.getSubject().get("activated")));
+ assertThat(message.getSubject(), equalTo(formatSubject("activated")));
assertThat(message.getBody(), containsString(request.getUsername()));
}
@@ -212,7 +216,7 @@ public void testRejectFlowNoMotivationNotifications() throws Exception {
IamEmailNotification message = notificationDelivery.getDeliveredNotifications().get(0);
- assertThat(message.getSubject(), equalTo(properties.getSubject().get("confirmation")));
+ assertThat(message.getSubject(), equalTo(formatSubject("confirmation")));
notificationDelivery.clearDeliveredNotifications();
@@ -228,7 +232,7 @@ public void testRejectFlowNoMotivationNotifications() throws Exception {
message = notificationDelivery.getDeliveredNotifications().get(0);
- assertThat(message.getSubject(), equalTo(properties.getSubject().get("adminHandleRequest")));
+ assertThat(message.getSubject(), equalTo(formatSubject("adminHandleRequest")));
assertThat(message.getReceivers(), hasSize(1));
assertThat(message.getReceivers().get(0).getEmailAddress(),
@@ -247,7 +251,7 @@ public void testRejectFlowNoMotivationNotifications() throws Exception {
message = notificationDelivery.getDeliveredNotifications().get(0);
- assertThat(message.getSubject(), equalTo(properties.getSubject().get("rejected")));
+ assertThat(message.getSubject(), equalTo(formatSubject("rejected")));
assertThat(message.getBody(),
not(containsString("The administrator has provided the following motivation")));
@@ -280,7 +284,7 @@ public void testRejectFlowMotivationNotifications() throws Exception {
IamEmailNotification message = notificationDelivery.getDeliveredNotifications().get(0);
- assertThat(message.getSubject(), equalTo(properties.getSubject().get("confirmation")));
+ assertThat(message.getSubject(), equalTo(formatSubject("confirmation")));
notificationDelivery.clearDeliveredNotifications();
@@ -296,7 +300,7 @@ public void testRejectFlowMotivationNotifications() throws Exception {
message = notificationDelivery.getDeliveredNotifications().get(0);
- assertThat(message.getSubject(), equalTo(properties.getSubject().get("adminHandleRequest")));
+ assertThat(message.getSubject(), equalTo(formatSubject("adminHandleRequest")));
assertThat(message.getReceivers(), hasSize(1));
assertThat(message.getReceivers().get(0).getEmailAddress(),
@@ -317,11 +321,10 @@ public void testRejectFlowMotivationNotifications() throws Exception {
message = notificationDelivery.getDeliveredNotifications().get(0);
- assertThat(message.getSubject(), equalTo(properties.getSubject().get("rejected")));
+ assertThat(message.getSubject(), equalTo(formatSubject("rejected")));
assertThat(message.getBody(),
containsString("The administrator has provided the following motivation"));
- assertThat(message.getBody(),
- containsString("We hate you"));
+ assertThat(message.getBody(), containsString("We hate you"));
}
diff --git a/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/EndpointsTestUtils.java b/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/EndpointsTestUtils.java
index 7f28b6620..81aaf7146 100644
--- a/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/EndpointsTestUtils.java
+++ b/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/EndpointsTestUtils.java
@@ -22,6 +22,7 @@
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken;
import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder;
import com.fasterxml.jackson.databind.ObjectMapper;
@@ -103,7 +104,14 @@ public AccessTokenGetter audience(String audience) {
return this;
}
- public String performTokenRequest() throws Exception {
+ public String performSuccessfulTokenRequest() throws Exception {
+
+ return performTokenRequest(200)
+ .getResponse()
+ .getContentAsString();
+ }
+
+ public MvcResult performTokenRequest(int statusCode) throws Exception {
MockHttpServletRequestBuilder req = post("/token").param("grant_type", grantType)
.param("client_id", clientId)
.param("client_secret", clientSecret);
@@ -120,18 +128,14 @@ public String performTokenRequest() throws Exception {
req.param("aud", audience);
}
- String response = mvc.perform(req)
- .andExpect(status().isOk())
- .andReturn()
- .getResponse()
- .getContentAsString();
-
- return response;
+ return mvc.perform(req)
+ .andExpect(status().is(statusCode))
+ .andReturn();
}
public DefaultOAuth2AccessToken getTokenResponseObject() throws Exception {
- String response = performTokenRequest();
+ String response = performSuccessfulTokenRequest();
// This is incorrectly named in spring security OAuth, what they call OAuth2AccessToken
// is a TokenResponse object
diff --git a/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/RefreshTokenGranterTests.java b/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/RefreshTokenGranterTests.java
index c675a2203..057a46d7c 100644
--- a/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/RefreshTokenGranterTests.java
+++ b/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/RefreshTokenGranterTests.java
@@ -24,6 +24,7 @@
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mitre.oauth2.model.ClientDetailsEntity;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
@@ -34,6 +35,8 @@
import com.fasterxml.jackson.databind.ObjectMapper;
import it.infn.mw.iam.IamLoginService;
+import it.infn.mw.iam.api.client.service.ClientService;
+import it.infn.mw.iam.api.client.util.ClientSuppliers;
import it.infn.mw.iam.persistence.model.IamAup;
import it.infn.mw.iam.persistence.repository.IamAccountRepository;
import it.infn.mw.iam.persistence.repository.IamAupRepository;
@@ -58,6 +61,9 @@ public class RefreshTokenGranterTests {
@Autowired
private IamAccountRepository accountRepo;
+ @Autowired
+ private ClientService clientService;
+
@Autowired
private MockMvc mvc;
@@ -93,6 +99,7 @@ public void testTokenRefreshFailsIfAupIsNotSigned() throws Exception {
aup.setUrl("http://default-aup.org/");
aup.setDescription("AUP description");
aup.setSignatureValidityInDays(0L);
+ aup.setAupRemindersInDays("30,15,1");
aupRepo.save(aup);
@@ -157,4 +164,48 @@ public void testRefreshFlowNotAllowedIfUserIsSuspended() throws Exception {
accountRepo.findByUsername("test").get().setActive(true);
}
+ @Test
+ public void testRefreshFlowNotAllowedIfClientIsSuspended() throws Exception {
+
+ String clientId = "password-grant";
+ String clientSecret = "secret";
+
+ // @formatter:off
+ String response = mvc.perform(post("/token")
+ .with(httpBasic(clientId, clientSecret))
+ .param("grant_type", "password")
+ .param("username", USERNAME)
+ .param("password", PASSWORD)
+ .param("scope", SCOPE))
+ .andExpect(status().isOk())
+ .andReturn()
+ .getResponse()
+ .getContentAsString();
+ // @formatter:on
+
+ DefaultOAuth2AccessToken tokenResponse =
+ mapper.readValue(response, DefaultOAuth2AccessToken.class);
+
+ String refreshToken = tokenResponse.getRefreshToken().toString();
+
+ ClientDetailsEntity client = clientService.findClientByClientId(clientId)
+ .orElseThrow(ClientSuppliers.clientNotFound(clientId));
+
+ client.setActive(false);
+ clientService.updateClient(client);
+
+ // @formatter:off
+ mvc.perform(post("/token")
+ .with(httpBasic(clientId, clientSecret))
+ .param("grant_type", "refresh_token")
+ .param("refresh_token", refreshToken))
+ .andExpect(status().isUnauthorized())
+ .andExpect(jsonPath("$.error").value("invalid_client"))
+ .andExpect(jsonPath("$.error_description").value("Client is suspended: " + clientId));
+ // @formatter:on
+
+ client.setActive(true);
+ clientService.updateClient(client);
+ }
+
}
diff --git a/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/ResourceOwnerPasswordCredentialsTests.java b/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/ResourceOwnerPasswordCredentialsTests.java
index 7775a224a..7f89daa3a 100644
--- a/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/ResourceOwnerPasswordCredentialsTests.java
+++ b/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/ResourceOwnerPasswordCredentialsTests.java
@@ -123,6 +123,7 @@ public void testResourceOwnerPasswordCredentialsFailsIfAupIsNotSigned() throws E
aup.setUrl("http://default-aup.org/");
aup.setDescription("AUP description");
aup.setSignatureValidityInDays(0L);
+ aup.setAupRemindersInDays("30,15,1");
aupRepo.save(aup);
diff --git a/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/TokenExchangeTests.java b/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/TokenExchangeTests.java
index 8ea60bc2c..c4f367ecd 100644
--- a/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/TokenExchangeTests.java
+++ b/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/TokenExchangeTests.java
@@ -178,6 +178,7 @@ public void testImpersonationFlowFailsIfAUPNotSigned() throws Exception {
aup.setUrl("http://default-aup.org/");
aup.setDescription("AUP description");
aup.setSignatureValidityInDays(0L);
+ aup.setAupRemindersInDays("30,15,1");
aupRepo.save(aup);
diff --git a/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/authzcode/AuthorizationCodeTests.java b/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/authzcode/AuthorizationCodeTests.java
index 7b90fa9c9..3d1fa7718 100644
--- a/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/authzcode/AuthorizationCodeTests.java
+++ b/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/authzcode/AuthorizationCodeTests.java
@@ -137,6 +137,7 @@ public void testOidcAuthorizationCodeFlowWithAUPSignature() throws Exception {
aup.setUrl("http://default-aup.org/");
aup.setDescription("AUP description");
aup.setSignatureValidityInDays(0L);
+ aup.setAupRemindersInDays("30,15,1");
aupRepo.save(aup);
diff --git a/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/scope/matchers/ScopeMatcherCacheTests.java b/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/scope/matchers/ScopeMatcherCacheTests.java
index 2ba38ac82..0d78278b0 100644
--- a/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/scope/matchers/ScopeMatcherCacheTests.java
+++ b/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/scope/matchers/ScopeMatcherCacheTests.java
@@ -28,6 +28,7 @@
import org.mitre.oauth2.model.ClientDetailsEntity;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.CacheManager;
+import org.springframework.cache.concurrent.ConcurrentMapCacheManager;
import org.springframework.test.context.TestPropertySource;
import org.springframework.test.context.junit4.SpringRunner;
@@ -36,8 +37,8 @@
import com.nimbusds.jwt.JWTParser;
import it.infn.mw.iam.api.client.service.ClientService;
-import it.infn.mw.iam.config.CacheConfig;
-import it.infn.mw.iam.config.RedisCacheProperties;
+import it.infn.mw.iam.config.CacheProperties;
+import it.infn.mw.iam.persistence.repository.client.IamClientRepository;
import it.infn.mw.iam.test.oauth.EndpointsTestUtils;
import it.infn.mw.iam.test.util.annotation.IamMockMvcIntegrationTest;
@@ -53,10 +54,13 @@ public class ScopeMatcherCacheTests extends EndpointsTestUtils {
private ClientService clientService;
@Autowired
- private CacheConfig cacheConfig;
-
+ private IamClientRepository clientRepository;
+
+ @Autowired
+ private CacheManager localCacheManager;
+
@Autowired
- private RedisCacheProperties redisCacheProperties;
+ private CacheProperties cacheProperties;
private String getAccessTokenForClient(String scopes) throws Exception {
@@ -67,10 +71,19 @@ private String getAccessTokenForClient(String scopes) throws Exception {
.getAccessTokenValue();
}
+ private void getAccessTokenForClientFailWithStatusCode(String scopes, int statusCode) throws Exception {
+
+ new AccessTokenGetter().grantType("client_credentials")
+ .clientId(CLIENT_ID)
+ .clientSecret(CLIENT_SECRET)
+ .scope(scopes)
+ .performTokenRequest(statusCode);
+ }
+
@Test
- public void ensureRedisCashIsDisabled() {
- assertFalse(redisCacheProperties.isEnabled());
- assertThat(cacheConfig.localCacheManager(), instanceOf(CacheManager.class));
+ public void ensureRedisCacheIsDisabled() {
+ assertFalse(cacheProperties.getRedis().isEnabled());
+ assertThat(localCacheManager, instanceOf(ConcurrentMapCacheManager.class));
}
@Test
@@ -87,6 +100,8 @@ public void updatingClientScopesInvalidatesCache() throws ParseException, Except
assertThat("scim:read",
not(in(token.getJWTClaimsSet().getClaim("scope").toString().split(" "))));
client.setScope(Sets.newHashSet("openid", "profile", "email", "scim:read"));
+ clientRepository.save(client);
+ getAccessTokenForClientFailWithStatusCode("openid profile email scim:read", 400);
clientService.updateClient(client);
token = JWTParser.parse(getAccessTokenForClient("openid profile email scim:read"));
assertThat("scim:read", in(token.getJWTClaimsSet().getClaim("scope").toString().split(" ")));
@@ -94,4 +109,5 @@ public void updatingClientScopesInvalidatesCache() throws ParseException, Except
clientService.deleteClient(client);
}
}
+
}
diff --git a/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/scope/matchers/ScopeMatcherExternalCacheTests.java b/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/scope/matchers/ScopeMatcherExternalCacheTests.java
index efcedaa85..56af564ff 100644
--- a/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/scope/matchers/ScopeMatcherExternalCacheTests.java
+++ b/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/scope/matchers/ScopeMatcherExternalCacheTests.java
@@ -27,11 +27,11 @@
import org.junit.jupiter.api.Test;
import org.mitre.oauth2.model.ClientDetailsEntity;
import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.boot.autoconfigure.cache.RedisCacheManagerBuilderCustomizer;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.boot.web.server.LocalServerPort;
-import org.springframework.data.redis.cache.RedisCacheConfiguration;
+import org.springframework.cache.CacheManager;
+import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.test.context.DynamicPropertyRegistry;
import org.springframework.test.context.DynamicPropertySource;
import org.testcontainers.junit.jupiter.Container;
@@ -44,8 +44,7 @@
import io.restassured.RestAssured;
import it.infn.mw.iam.api.client.service.ClientService;
-import it.infn.mw.iam.config.CacheConfig;
-import it.infn.mw.iam.config.RedisCacheProperties;
+import it.infn.mw.iam.config.CacheProperties;
import it.infn.mw.iam.test.TestUtils;
import it.infn.mw.iam.test.oauth.EndpointsTestUtils;
import it.infn.mw.iam.test.util.annotation.IamMockMvcIntegrationTest;
@@ -54,7 +53,7 @@
@Testcontainers
@IamMockMvcIntegrationTest
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT,
- properties = {"iam.access_token.include_scope=true", "redis-cache.enabled=true"})
+ properties = {"iam.access_token.include_scope=true", "cache.redis.enabled=true"})
public class ScopeMatcherExternalCacheTests extends EndpointsTestUtils {
private static final String CLIENT_ID = "cache-client";
@@ -68,10 +67,7 @@ public class ScopeMatcherExternalCacheTests extends EndpointsTestUtils {
private ClientService clientService;
@Autowired
- private CacheConfig cacheConfig;
-
- @Autowired
- private RedisCacheProperties redisCacheProperties;
+ private CacheProperties redisCacheProperties;
@LocalServerPort
private Integer iamPort;
@@ -79,6 +75,9 @@ public class ScopeMatcherExternalCacheTests extends EndpointsTestUtils {
@Autowired
ObjectMapper mapper;
+ @Autowired
+ CacheManager cacheManager;
+
@Container
private static final RedisContainer REDIS = new RedisContainer();
@@ -93,10 +92,7 @@ public void setup() {
TestUtils.initRestAssured();
RestAssured.port = iamPort;
assertTrue(redisCacheProperties.isEnabled());
- assertThat(cacheConfig.redisCacheConfiguration(), instanceOf(RedisCacheConfiguration.class));
- assertThat(cacheConfig.redisCacheManagerBuilderCustomizer(),
- instanceOf(RedisCacheManagerBuilderCustomizer.class));
-
+ assertThat(cacheManager, instanceOf(RedisCacheManager.class));
}
private String getAccessTokenForClient(String scopes) throws Exception {
@@ -106,7 +102,6 @@ private String getAccessTokenForClient(String scopes) throws Exception {
.clientSecret(CLIENT_SECRET)
.scope(scopes)
.getAccessTokenValue();
-
}
@Test
diff --git a/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/scope/matchers/ScopeMatcherNoCacheTests.java b/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/scope/matchers/ScopeMatcherNoCacheTests.java
new file mode 100644
index 000000000..85ad1a3a4
--- /dev/null
+++ b/iam-login-service/src/test/java/it/infn/mw/iam/test/oauth/scope/matchers/ScopeMatcherNoCacheTests.java
@@ -0,0 +1,93 @@
+/**
+ * Copyright (c) Istituto Nazionale di Fisica Nucleare (INFN). 2016-2021
+ *
+ * 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 it.infn.mw.iam.test.oauth.scope.matchers;
+
+import static org.hamcrest.CoreMatchers.instanceOf;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.in;
+import static org.hamcrest.Matchers.not;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mitre.oauth2.model.ClientDetailsEntity;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.cache.CacheManager;
+import org.springframework.cache.concurrent.ConcurrentMapCacheManager;
+import org.springframework.cache.support.NoOpCacheManager;
+import org.springframework.data.redis.cache.RedisCacheManager;
+import org.springframework.test.context.TestPropertySource;
+import org.springframework.test.context.junit4.SpringRunner;
+
+import com.google.common.collect.Sets;
+import com.nimbusds.jwt.JWT;
+import com.nimbusds.jwt.JWTParser;
+
+import it.infn.mw.iam.persistence.repository.client.IamClientRepository;
+import it.infn.mw.iam.test.oauth.EndpointsTestUtils;
+import it.infn.mw.iam.test.util.annotation.IamMockMvcIntegrationTest;
+
+@RunWith(SpringRunner.class)
+@IamMockMvcIntegrationTest
+@TestPropertySource(properties = {"cache.enabled=false", "iam.access_token.include_scope=true"})
+public class ScopeMatcherNoCacheTests extends EndpointsTestUtils {
+
+ private static final String CLIENT_ID = "cache-client";
+ private static final String CLIENT_SECRET = "secret";
+
+ @Autowired
+ private IamClientRepository clientRepo;
+
+ @Autowired
+ private CacheManager cacheManager;
+
+ private String getAccessTokenForClient(String scopes) throws Exception {
+
+ return new AccessTokenGetter().grantType("client_credentials")
+ .clientId(CLIENT_ID)
+ .clientSecret(CLIENT_SECRET)
+ .scope(scopes)
+ .getAccessTokenValue();
+ }
+
+ @Test
+ public void ensureRedisCacheIsDisabled() {
+ assertThat(cacheManager, instanceOf(NoOpCacheManager.class));
+ assertThat(cacheManager, not(instanceOf(ConcurrentMapCacheManager.class)));
+ assertThat(cacheManager, not(instanceOf(RedisCacheManager.class)));
+ }
+
+ @Test
+ public void updatingClientScopesWithNoCache() throws Exception {
+
+ ClientDetailsEntity client = new ClientDetailsEntity();
+ client.setClientId(CLIENT_ID);
+ client.setClientSecret(CLIENT_SECRET);
+ client.setScope(Sets.newHashSet("openid", "profile", "email"));
+ clientRepo.save(client);
+
+ try {
+ JWT token = JWTParser.parse(getAccessTokenForClient("openid profile email"));
+ assertThat("scim:read",
+ not(in(token.getJWTClaimsSet().getClaim("scope").toString().split(" "))));
+ client.setScope(Sets.newHashSet("openid", "profile", "email", "scim:read"));
+ clientRepo.save(client);
+ token = JWTParser.parse(getAccessTokenForClient("openid profile email scim:read"));
+ assertThat("scim:read", in(token.getJWTClaimsSet().getClaim("scope").toString().split(" ")));
+ } finally {
+ clientRepo.delete(client);
+ }
+ }
+}
diff --git a/iam-login-service/src/test/java/it/infn/mw/iam/test/registration/RegistrationUsernameTests.java b/iam-login-service/src/test/java/it/infn/mw/iam/test/registration/RegistrationUsernameTests.java
index af9c23eb8..dc61d61c1 100644
--- a/iam-login-service/src/test/java/it/infn/mw/iam/test/registration/RegistrationUsernameTests.java
+++ b/iam-login-service/src/test/java/it/infn/mw/iam/test/registration/RegistrationUsernameTests.java
@@ -67,7 +67,7 @@ public void teardown() {
private RegistrationRequestDto createRegistrationRequest(String username) {
- String email = username + "@example.org";
+ String email = username.split("@")[0] + "@example.org";
RegistrationRequestDto request = new RegistrationRequestDto();
request.setGivenname("Test");
request.setFamilyname("User");
@@ -81,8 +81,8 @@ private RegistrationRequestDto createRegistrationRequest(String username) {
@Test
public void validUsernames() throws Exception {
- final String[] validUsernames = {"bob", "b", "test$", "root", "test1234", "test_", "_test",
- "username@example.com", "username@domain"};
+ final String[] validUsernames = {"bob","test$", "root", "test1234", "test_", "_test",
+ "username1@example.com", "username2@domain"};
for (String u : validUsernames) {
RegistrationRequestDto r = createRegistrationRequest(u);
@@ -102,7 +102,7 @@ public void validUsernames() throws Exception {
@Test
public void invalidUsernames() throws Exception {
- final String[] invalidUsernames = {"£$%^&*(", ".,", "-test", "1test", "test$$", "@domain",
+ final String[] invalidUsernames = {"a","£$%^&*(", ".,", "-test", "1test", "test$$", "@domain",
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"};
for (String u : invalidUsernames) {
diff --git a/iam-login-service/src/test/java/it/infn/mw/iam/test/registration/cern/CernRegistrationValidationServiceTests.java b/iam-login-service/src/test/java/it/infn/mw/iam/test/registration/cern/CernRegistrationValidationServiceTests.java
index f54c34d02..b1b2d6bda 100644
--- a/iam-login-service/src/test/java/it/infn/mw/iam/test/registration/cern/CernRegistrationValidationServiceTests.java
+++ b/iam-login-service/src/test/java/it/infn/mw/iam/test/registration/cern/CernRegistrationValidationServiceTests.java
@@ -119,7 +119,6 @@ private RegistrationRequestDto createDto(String username) {
request.setEmail(email);
request.setUsername(username);
request.setNotes("Some short notes...");
- request.setPassword("password");
return request;
}
diff --git a/iam-login-service/src/test/java/it/infn/mw/iam/test/repository/IamAupRepositoryTests.java b/iam-login-service/src/test/java/it/infn/mw/iam/test/repository/IamAupRepositoryTests.java
index 2c606f6cb..dd0e46a47 100644
--- a/iam-login-service/src/test/java/it/infn/mw/iam/test/repository/IamAupRepositoryTests.java
+++ b/iam-login-service/src/test/java/it/infn/mw/iam/test/repository/IamAupRepositoryTests.java
@@ -64,6 +64,7 @@ public void aupCreationWorks() {
assertThat(aup.getCreationTime(), new DateEqualModulo1Second(creationTime));
assertThat(aup.getLastUpdateTime(), new DateEqualModulo1Second(creationTime));
assertThat(aup.getSignatureValidityInDays(), equalTo(365L));
+ assertThat(aup.getAupRemindersInDays(), equalTo("30,15,1"));
}
diff --git a/iam-login-service/src/test/java/it/infn/mw/iam/test/scim/user/ScimUserCreationTests.java b/iam-login-service/src/test/java/it/infn/mw/iam/test/scim/user/ScimUserCreationTests.java
index dce25dabd..d7fee48be 100644
--- a/iam-login-service/src/test/java/it/infn/mw/iam/test/scim/user/ScimUserCreationTests.java
+++ b/iam-login-service/src/test/java/it/infn/mw/iam/test/scim/user/ScimUserCreationTests.java
@@ -21,6 +21,8 @@
import static it.infn.mw.iam.test.scim.ScimUtils.SCIM_WRITE_SCOPE;
import static it.infn.mw.iam.test.scim.ScimUtils.buildUser;
import static it.infn.mw.iam.test.scim.ScimUtils.buildUserWithPassword;
+import static java.lang.Boolean.FALSE;
+import static java.lang.Boolean.TRUE;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.equalTo;
@@ -36,7 +38,9 @@
import java.util.Date;
import java.util.List;
import java.util.Optional;
+import java.util.stream.Collectors;
+import org.hamcrest.collection.IsIterableContainingInAnyOrder;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
@@ -387,19 +391,28 @@ public void testUserCreationWithMultipleX509Certificate() throws Exception {
.active(true)
.build();
- List userCertList = user.getIndigoUser().getCertificates();
-
ScimUser createdUser = scimUtils.postUser(user);
List createdUserCertList = createdUser.getIndigoUser().getCertificates();
assertNotNull(createdUserCertList);
- assertThat(createdUserCertList, hasSize(equalTo(2)));
- assertThat(createdUserCertList.get(0).getDisplay(), equalTo(userCertList.get(0).getDisplay()));
- assertThat(createdUserCertList.get(0).getPemEncodedCertificate(),
- equalTo(userCertList.get(0).getPemEncodedCertificate()));
- assertThat(createdUserCertList.get(0).getPrimary(), equalTo(userCertList.get(0).getPrimary()));
- assertThat(createdUserCertList.get(1).getDisplay(), equalTo(userCertList.get(1).getDisplay()));
- assertThat(createdUserCertList.get(1).getPrimary(), equalTo(userCertList.get(1).getPrimary()));
+ assertThat(createdUserCertList.stream().map(c -> c.getDisplay()).collect(Collectors.toList()),
+ IsIterableContainingInAnyOrder.containsInAnyOrder("Personal1", "Personal2"));
+ ScimX509Certificate createdCert1 = createdUserCertList.stream()
+ .filter(cert -> "Personal1".equals(cert.getDisplay()))
+ .findFirst()
+ .get();
+ ScimX509Certificate createdCert2 = createdUserCertList.stream()
+ .filter(cert -> "Personal2".equals(cert.getDisplay()))
+ .findFirst()
+ .get();
+
+ assertThat(createdCert1.getPrimary(), equalTo(FALSE));
+ assertThat(createdCert2.getPrimary(), equalTo(TRUE));
+ assertThat(createdCert1.getPemEncodedCertificate(),
+ equalTo(X509Utils.x509Certs.get(0).certificate));
+ assertThat(createdCert2.getPemEncodedCertificate(),
+ equalTo(X509Utils.x509Certs.get(1).certificate));
+
}
@Test
@@ -426,19 +439,28 @@ public void testUserCreationWithMultipleX509CertificateAndNoPrimary() throws Exc
.active(true)
.build();
- List userCertList = user.getIndigoUser().getCertificates();
-
ScimUser createdUser = scimUtils.postUser(user);
List createdUserCertList = createdUser.getIndigoUser().getCertificates();
assertNotNull(createdUserCertList);
- assertThat(createdUserCertList, hasSize(equalTo(2)));
- assertThat(createdUserCertList.get(0).getDisplay(), equalTo(userCertList.get(0).getDisplay()));
- assertThat(createdUserCertList.get(0).getPemEncodedCertificate(),
- equalTo(userCertList.get(0).getPemEncodedCertificate()));
- assertThat(createdUserCertList.get(0).getPrimary(), equalTo(true));
- assertThat(createdUserCertList.get(1).getDisplay(), equalTo(userCertList.get(1).getDisplay()));
- assertThat(createdUserCertList.get(1).getPrimary(), equalTo(false));
+ assertThat(createdUserCertList.stream().map(c -> c.getDisplay()).collect(Collectors.toList()),
+ IsIterableContainingInAnyOrder.containsInAnyOrder("Personal1", "Personal2"));
+ ScimX509Certificate createdCert1 = createdUserCertList.stream()
+ .filter(cert -> "Personal1".equals(cert.getDisplay()))
+ .findFirst()
+ .get();
+ ScimX509Certificate createdCert2 = createdUserCertList.stream()
+ .filter(cert -> "Personal2".equals(cert.getDisplay()))
+ .findFirst()
+ .get();
+
+ assertThat(createdCert1.getPrimary(), equalTo(TRUE));
+ assertThat(createdCert2.getPrimary(), equalTo(FALSE));
+ assertThat(createdCert1.getPemEncodedCertificate(),
+ equalTo(X509Utils.x509Certs.get(0).certificate));
+ assertThat(createdCert2.getPemEncodedCertificate(),
+ equalTo(X509Utils.x509Certs.get(1).certificate));
+
}
@Test
@@ -472,7 +494,7 @@ public void testUserCreationWithAupSignatureIsIgnored() throws Exception {
final String AUP_DESCRIPTION = "Test AUP";
final Date currentDate = new Date();
- AupDTO aup = new AupDTO(AUP_URL, "", AUP_DESCRIPTION, 0L, currentDate, currentDate);
+ AupDTO aup = new AupDTO(AUP_URL, "", AUP_DESCRIPTION, 0L, currentDate, currentDate, "30,15,1");
aupService.saveAup(aup);
Calendar cal = Calendar.getInstance();
@@ -480,9 +502,10 @@ public void testUserCreationWithAupSignatureIsIgnored() throws Exception {
cal.add(Calendar.HOUR_OF_DAY, 1);
Date signatureTime = cal.getTime();
- ScimUser user = buildUser("user_with_aup_signature", "userwithaupsignature@email.test", "User", "Test")
- .aupSignatureTime(signatureTime)
- .build();
+ ScimUser user =
+ buildUser("user_with_aup_signature", "userwithaupsignature@email.test", "User", "Test")
+ .aupSignatureTime(signatureTime)
+ .build();
ScimUser createdUser = scimUtils.postUser(user);
assertThat(user.getUserName(), equalTo(createdUser.getUserName()));
diff --git a/iam-login-service/src/test/java/it/infn/mw/iam/test/service/IamAccountServiceTests.java b/iam-login-service/src/test/java/it/infn/mw/iam/test/service/IamAccountServiceTests.java
index fded5a4ad..67ccf07df 100644
--- a/iam-login-service/src/test/java/it/infn/mw/iam/test/service/IamAccountServiceTests.java
+++ b/iam-login-service/src/test/java/it/infn/mw/iam/test/service/IamAccountServiceTests.java
@@ -39,6 +39,7 @@
import java.util.Arrays;
import java.util.Calendar;
import java.util.Date;
+import java.util.List;
import java.util.Optional;
import org.junit.Before;
@@ -59,12 +60,18 @@
import com.google.common.collect.Sets;
import it.infn.mw.iam.audit.events.account.AccountEndTimeUpdatedEvent;
+import it.infn.mw.iam.config.IamProperties;
+import it.infn.mw.iam.config.IamProperties.DefaultGroup;
+import it.infn.mw.iam.core.group.DefaultIamGroupService;
import it.infn.mw.iam.core.time.TimeProvider;
import it.infn.mw.iam.core.user.DefaultIamAccountService;
import it.infn.mw.iam.core.user.exception.CredentialAlreadyBoundException;
import it.infn.mw.iam.core.user.exception.InvalidCredentialException;
import it.infn.mw.iam.core.user.exception.UserAlreadyExistsException;
+import it.infn.mw.iam.notification.NotificationFactory;
import it.infn.mw.iam.persistence.model.IamAccount;
+import it.infn.mw.iam.persistence.model.IamAccountGroupMembership;
+import it.infn.mw.iam.persistence.model.IamGroup;
import it.infn.mw.iam.persistence.model.IamOidcId;
import it.infn.mw.iam.persistence.model.IamSamlId;
import it.infn.mw.iam.persistence.model.IamSshKey;
@@ -77,6 +84,8 @@
@RunWith(MockitoJUnitRunner.class)
public class IamAccountServiceTests extends IamAccountServiceTestSupport {
+ private static final String TEST_GROUP_1 = "Test-group-1";
+
public static final Instant NOW = Instant.parse("2021-01-01T00:00:00.00Z");
@Mock
@@ -103,10 +112,21 @@ public class IamAccountServiceTests extends IamAccountServiceTestSupport {
@Mock
private OAuth2TokenEntityService tokenService;
+ @Mock
+ private NotificationFactory notificationFactory;
+
private Clock clock = Clock.fixed(NOW, ZoneId.systemDefault());
private DefaultIamAccountService accountService;
+ @Mock
+ private DefaultIamGroupService iamGroupService;
+
+ @Mock
+ private IamProperties iamProperties;
+
+ private IamProperties.RegistrationProperties registrationProperties = new IamProperties.RegistrationProperties();
+
@Captor
private ArgumentCaptor eventCaptor;
@@ -126,9 +146,10 @@ public void setup() {
when(authoritiesRepo.findByAuthority(anyString())).thenReturn(Optional.empty());
when(authoritiesRepo.findByAuthority("ROLE_USER")).thenReturn(Optional.of(ROLE_USER_AUTHORITY));
when(passwordEncoder.encode(any())).thenReturn(PASSWORD);
+ when(iamProperties.getRegistration()).thenReturn(registrationProperties);
accountService = new DefaultIamAccountService(clock, accountRepo, groupRepo, authoritiesRepo,
- passwordEncoder, eventPublisher, tokenService, accountClientRepo);
+ passwordEncoder, eventPublisher, tokenService, accountClientRepo, notificationFactory, iamProperties, iamGroupService);
}
@Test(expected = NullPointerException.class)
@@ -843,4 +864,41 @@ public void testSetEndTimeWorksForNonNullDate() {
assertThat(e.getAccount().getEndTime(), is(newEndTime));
}
+ @Test
+ public void testNewAccountAddedToDefaultGroups() {
+ IamAccount account = cloneAccount(CICCIO_ACCOUNT);
+
+ IamGroup testGroup = new IamGroup();
+ testGroup.setName(TEST_GROUP_1);
+ DefaultGroup defaultGroup = new DefaultGroup();
+ defaultGroup.setName(TEST_GROUP_1);
+ defaultGroup.setEnrollment("INSERT");
+ List defaultGroups = Arrays.asList(defaultGroup);
+
+ registrationProperties.setDefaultGroups(defaultGroups);
+ when(iamGroupService.findByName(TEST_GROUP_1)).thenReturn(Optional.of(testGroup));
+
+ account = accountService.createAccount(account);
+
+ assertTrue(getGroup(account).equals(testGroup));
+ }
+
+ private IamGroup getGroup(IamAccount account) {
+ Optional groupMembershipOptional = account.getGroups().stream().findFirst();
+ if (groupMembershipOptional.isPresent()) {
+ return groupMembershipOptional.get().getGroup();
+ }
+ return null;
+ }
+
+ @Test
+ public void testNoDefaultGroupsAddedWhenDefaultGroupsNotGiven() {
+ IamAccount account = cloneAccount(CICCIO_ACCOUNT);
+
+ account = accountService.createAccount(account);
+
+ Optional groupMembershipOptional = account.getGroups().stream().findFirst();
+ assertFalse(groupMembershipOptional.isPresent());
+ }
+
}
diff --git a/iam-login-service/src/test/java/it/infn/mw/iam/test/service/client/ClientManagementServiceTests.java b/iam-login-service/src/test/java/it/infn/mw/iam/test/service/client/ClientManagementServiceTests.java
index 1711e80da..b9f8df669 100644
--- a/iam-login-service/src/test/java/it/infn/mw/iam/test/service/client/ClientManagementServiceTests.java
+++ b/iam-login-service/src/test/java/it/infn/mw/iam/test/service/client/ClientManagementServiceTests.java
@@ -105,7 +105,7 @@ public void testPagedClientLookup() {
Sort sort = Sort.by(Direction.ASC, "clientId");
Pageable pageable = PagingUtils.buildPageRequest(10, 1, 100, sort);
-
+
ListResponseDTO clients = managementService.retrieveAllClients(pageable);
assertThat(clients.getTotalResults(), is(19L));
@@ -249,8 +249,7 @@ public void testBasicClientValidation() {
}
@Test
- public void testDynamicallyRegisteredClientCanBeUpdated()
- throws ParseException {
+ public void testDynamicallyRegisteredClientCanBeUpdated() throws ParseException {
userAuth = Mockito.mock(UsernamePasswordAuthenticationToken.class);
when(userAuth.getName()).thenReturn("test");
@@ -261,7 +260,7 @@ public void testDynamicallyRegisteredClientCanBeUpdated()
request.setGrantTypes(Sets.newHashSet(AuthorizationGrantType.CLIENT_CREDENTIALS));
RegisteredClientDTO response = registrationService.registerClient(request, userAuth);
-
+
String clientId = response.getClientId();
ClientDetailsEntity entity = clientService.findClientByClientId(clientId).orElseThrow();
assertThat(entity.isDynamicallyRegistered(), is(true));
@@ -296,8 +295,7 @@ public void testSecretRotation() throws ParseException {
RegisteredClientDTO updatedClient =
managementService.retrieveClientByClientId(client.getClientId()).orElseThrow();
- assertThat(updatedClient.getClientSecret(),
- not(equalTo(savedClient.getClientSecret())));
+ assertThat(updatedClient.getClientSecret(), not(equalTo(savedClient.getClientSecret())));
}
@Test
@@ -333,9 +331,8 @@ public void testClientOwnerAssignRemove() throws ParseException {
RegisteredClientDTO savedClient = managementService.saveNewClient(client);
assertThat(savedClient.getClientId(), is(client.getClientId()));
assertThat(savedClient.getClientSecret(), notNullValue());
-
- ListResponseDTO owners =
- managementService.getClientOwners(savedClient.getClientId(),
+
+ ListResponseDTO owners = managementService.getClientOwners(savedClient.getClientId(),
PagingUtils.buildUnpagedPageRequest());
assertThat(owners.getTotalResults(), is(0L));
@@ -417,4 +414,24 @@ public void testClientStatusChange() {
assertTrue(client.getStatusChangedOn().equals(Date.from(clock.instant())));
assertEquals("userUUID", client.getStatusChangedBy());
}
+
+ @Test
+ public void testClientStatusChangeWithContacts() {
+ managementService.updateClientStatus("device-code-client", false, "userUUID");
+ RegisteredClientDTO client = managementService.retrieveClientByClientId("device-code-client").get();
+
+ assertFalse(client.isActive());
+ assertTrue(client.getStatusChangedOn().equals(Date.from(clock.instant())));
+ assertEquals("userUUID", client.getStatusChangedBy());
+ }
+
+ @Test
+ public void testClientStatusChangeWithoutOwners() {
+ managementService.updateClientStatus("client-cred", false, "userUUID");
+ RegisteredClientDTO client = managementService.retrieveClientByClientId("client-cred").get();
+
+ assertFalse(client.isActive());
+ assertTrue(client.getStatusChangedOn().equals(Date.from(clock.instant())));
+ assertEquals("userUUID", client.getStatusChangedBy());
+ }
}
diff --git a/iam-persistence/pom.xml b/iam-persistence/pom.xml
index 4d13a98e9..ae547b3de 100644
--- a/iam-persistence/pom.xml
+++ b/iam-persistence/pom.xml
@@ -22,7 +22,7 @@
it.infn.mw.iam-parent
iam-parent
- 1.9.0
+ 1.10.0
it.infn.mw.iam-persistence
diff --git a/iam-persistence/src/main/java/it/infn/mw/iam/core/IamNotificationType.java b/iam-persistence/src/main/java/it/infn/mw/iam/core/IamNotificationType.java
index 971ae61be..ba3c26c74 100644
--- a/iam-persistence/src/main/java/it/infn/mw/iam/core/IamNotificationType.java
+++ b/iam-persistence/src/main/java/it/infn/mw/iam/core/IamNotificationType.java
@@ -16,5 +16,5 @@
package it.infn.mw.iam.core;
public enum IamNotificationType {
- CONFIRMATION, RESETPASSWD, ACTIVATED, REJECTED, GROUP_MEMBERSHIP
+ CONFIRMATION, RESETPASSWD, ACTIVATED, REJECTED, GROUP_MEMBERSHIP, AUP_REMINDER, AUP_EXPIRATION, AUP_SIGNATURE_REQUEST, ACCOUNT_SUSPENDED, ACCOUNT_RESTORED, CLIENT_STATUS
}
diff --git a/iam-persistence/src/main/java/it/infn/mw/iam/persistence/model/IamAup.java b/iam-persistence/src/main/java/it/infn/mw/iam/persistence/model/IamAup.java
index c39a926e2..0039e0746 100644
--- a/iam-persistence/src/main/java/it/infn/mw/iam/persistence/model/IamAup.java
+++ b/iam-persistence/src/main/java/it/infn/mw/iam/persistence/model/IamAup.java
@@ -50,6 +50,9 @@ public class IamAup implements Serializable {
@Column(name = "sig_validity_days", nullable = false)
Long signatureValidityInDays;
+ @Column(name = "aup_reminders_days", nullable = false)
+ String aupRemindersInDays;
+
@Temporal(TemporalType.TIMESTAMP)
@Column(name = "creation_time", nullable = false)
Date creationTime;
@@ -137,6 +140,14 @@ public void setSignatureValidityInDays(Long signatureValidityInDays) {
this.signatureValidityInDays = signatureValidityInDays;
}
+ public String getAupRemindersInDays() {
+ return aupRemindersInDays;
+ }
+
+ public void setAupRemindersInDays(String aupRemindersInDays) {
+ this.aupRemindersInDays = aupRemindersInDays;
+ }
+
public Date getCreationTime() {
return creationTime;
diff --git a/iam-persistence/src/main/java/it/infn/mw/iam/persistence/repository/IamAccountRepository.java b/iam-persistence/src/main/java/it/infn/mw/iam/persistence/repository/IamAccountRepository.java
index 225a9637e..38c196d60 100644
--- a/iam-persistence/src/main/java/it/infn/mw/iam/persistence/repository/IamAccountRepository.java
+++ b/iam-persistence/src/main/java/it/infn/mw/iam/persistence/repository/IamAccountRepository.java
@@ -141,7 +141,6 @@ Page findByLabelNameAndValue(@Param("name") String name, @Param("val
@Query("select a from IamAccount a where a.active = TRUE")
Page findActiveAccounts(Pageable op);
-
@Modifying
@Query("delete from IamAccountGroupMembership")
void deleteAllAccountGroupMemberships();
diff --git a/iam-persistence/src/main/java/it/infn/mw/iam/persistence/repository/IamAupSignatureRepository.java b/iam-persistence/src/main/java/it/infn/mw/iam/persistence/repository/IamAupSignatureRepository.java
index cdbbeec4d..a2b775911 100644
--- a/iam-persistence/src/main/java/it/infn/mw/iam/persistence/repository/IamAupSignatureRepository.java
+++ b/iam-persistence/src/main/java/it/infn/mw/iam/persistence/repository/IamAupSignatureRepository.java
@@ -15,11 +15,13 @@
*/
package it.infn.mw.iam.persistence.repository;
-
-
+import java.util.Date;
+import java.util.List;
import java.util.Optional;
+import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.PagingAndSortingRepository;
+import org.springframework.data.repository.query.Param;
import it.infn.mw.iam.persistence.model.IamAccount;
import it.infn.mw.iam.persistence.model.IamAup;
@@ -28,8 +30,12 @@
public interface IamAupSignatureRepository
extends PagingAndSortingRepository, IamAupSignatureRepositoryCustom {
+ @Query("select ias from IamAupSignature ias join ias.account a where a.active = TRUE and ias.aup = :aup and :signatureTime <= ias.signatureTime and ias.signatureTime < :plusOne")
+ List findByAupAndSignatureTime(@Param("aup") IamAup aup,
+ @Param("signatureTime") Date signatureTime, @Param("plusOne") Date plusOne);
+
Optional findByAupAndAccount(IamAup aup, IamAccount account);
-
+
Long deleteByAup(IamAup aup);
-
+
}
diff --git a/iam-persistence/src/main/java/it/infn/mw/iam/persistence/repository/IamEmailNotificationRepository.java b/iam-persistence/src/main/java/it/infn/mw/iam/persistence/repository/IamEmailNotificationRepository.java
index fdb932798..16f970bdc 100644
--- a/iam-persistence/src/main/java/it/infn/mw/iam/persistence/repository/IamEmailNotificationRepository.java
+++ b/iam-persistence/src/main/java/it/infn/mw/iam/persistence/repository/IamEmailNotificationRepository.java
@@ -46,4 +46,17 @@ List findByStatusWithUpdateTime(
Integer countByDeliveryStatus(IamDeliveryStatus deliveryStatus);
List findByNotificationType(IamNotificationType notificationType);
+
+ @Query("select count(n) from IamEmailNotification n join n.receivers r where n.notificationType = it.infn.mw.iam.core.IamNotificationType.AUP_REMINDER"
+ + " and CURRENT_DATE <= n.lastUpdate and n.lastUpdate < :tomorrow"
+ + " and n.deliveryStatus <> it.infn.mw.iam.core.IamDeliveryStatus.DELIVERY_ERROR"
+ + " and r.emailAddress = :email_address")
+ Integer countAupRemindersPerAccount(@Param("email_address") String emailAddress,
+ @Param("tomorrow") Date tomorrow);
+
+ @Query("select count(n) from IamEmailNotification n join n.receivers r where n.notificationType = it.infn.mw.iam.core.IamNotificationType.AUP_EXPIRATION"
+ + " and n.deliveryStatus <> it.infn.mw.iam.core.IamDeliveryStatus.DELIVERY_ERROR"
+ + " and r.emailAddress = :email_address")
+ Integer countAupExpirationMessPerAccount(@Param("email_address") String emailAddress);
+
}
diff --git a/iam-persistence/src/main/resources/db/migration/h2/V105__add_aup_reminders.sql b/iam-persistence/src/main/resources/db/migration/h2/V105__add_aup_reminders.sql
new file mode 100644
index 000000000..101cb09f8
--- /dev/null
+++ b/iam-persistence/src/main/resources/db/migration/h2/V105__add_aup_reminders.sql
@@ -0,0 +1 @@
+ALTER TABLE iam_aup ADD COLUMN aup_reminders_days VARCHAR(128) NOT NULL DEFAULT '30,15,1';
\ No newline at end of file
diff --git a/iam-persistence/src/main/resources/db/migration/h2/V19__aup_tables.sql b/iam-persistence/src/main/resources/db/migration/h2/V19__aup_tables.sql
index 18f449f46..8ad7b522c 100644
--- a/iam-persistence/src/main/resources/db/migration/h2/V19__aup_tables.sql
+++ b/iam-persistence/src/main/resources/db/migration/h2/V19__aup_tables.sql
@@ -3,7 +3,7 @@ CREATE TABLE iam_aup (ID BIGINT IDENTITY NOT NULL,
description VARCHAR(128),
last_update_time TIMESTAMP NOT NULL,
name VARCHAR(36) NOT NULL UNIQUE,
- sig_validity_days BIGINT NOT NULL,
+ sig_validity_days BIGINT NOT NULL,
text LONGVARCHAR NOT NULL,
PRIMARY KEY (ID));
diff --git a/iam-persistence/src/main/resources/db/migration/mysql/V105__add_aup_reminders.sql b/iam-persistence/src/main/resources/db/migration/mysql/V105__add_aup_reminders.sql
new file mode 100644
index 000000000..101cb09f8
--- /dev/null
+++ b/iam-persistence/src/main/resources/db/migration/mysql/V105__add_aup_reminders.sql
@@ -0,0 +1 @@
+ALTER TABLE iam_aup ADD COLUMN aup_reminders_days VARCHAR(128) NOT NULL DEFAULT '30,15,1';
\ No newline at end of file
diff --git a/iam-persistence/src/main/resources/db/migration/test/V100000___test_data.sql b/iam-persistence/src/main/resources/db/migration/test/V100000___test_data.sql
index 468928784..0d2b46151 100644
--- a/iam-persistence/src/main/resources/db/migration/test/V100000___test_data.sql
+++ b/iam-persistence/src/main/resources/db/migration/test/V100000___test_data.sql
@@ -186,6 +186,9 @@ INSERT INTO client_grant_type (owner_id, grant_type) VALUES
(18, 'urn:ietf:params:oauth:grant-type:device_code'),
(18, 'authorization_code'),
(19, 'client_credentials');
+
+INSERT INTO client_contact (owner_id, contact) VALUES
+ (12, 'test@example.com');
INSERT INTO iam_user_info(ID, GIVENNAME, FAMILYNAME, EMAIL, EMAILVERIFIED, BIRTHDATE, GENDER, NICKNAME) VALUES
(2, 'Test', 'User', 'test@iam.test', true, '1950-01-01','M','test'),
diff --git a/iam-test-client/pom.xml b/iam-test-client/pom.xml
index db660af74..aa2493565 100644
--- a/iam-test-client/pom.xml
+++ b/iam-test-client/pom.xml
@@ -5,7 +5,7 @@
it.infn.mw.iam-parent
iam-parent
- 1.9.0
+ 1.10.0
it.infn.mw.iam-test-client
diff --git a/iam-test-client/src/main/resources/templates/index.html b/iam-test-client/src/main/resources/templates/index.html
index bef2168a7..b1c437fdd 100644
--- a/iam-test-client/src/main/resources/templates/index.html
+++ b/iam-test-client/src/main/resources/templates/index.html
@@ -106,7 +106,7 @@ INDIGO IAM Test Client Application