From 7546e5daccd0c09591468284748d0a3eeb8764f2 Mon Sep 17 00:00:00 2001 From: Romuald Lemesle Date: Thu, 29 Feb 2024 10:52:21 +0100 Subject: [PATCH] [backend] Ensure uniqueness of email field when creating a user account --- .../test/java/io/openex/rest/UserApiTest.java | 40 +++++++++++++++++-- .../java/io/openex/database/model/User.java | 18 +++++++-- 2 files changed, 51 insertions(+), 7 deletions(-) diff --git a/openex-api/src/test/java/io/openex/rest/UserApiTest.java b/openex-api/src/test/java/io/openex/rest/UserApiTest.java index b48bfaf6de..2c4d6479fa 100644 --- a/openex-api/src/test/java/io/openex/rest/UserApiTest.java +++ b/openex-api/src/test/java/io/openex/rest/UserApiTest.java @@ -4,6 +4,7 @@ import io.openex.database.model.User; import io.openex.database.repository.UserRepository; import io.openex.rest.user.form.login.LoginUserInput; +import io.openex.rest.user.form.user.CreateUserInput; import io.openex.rest.utils.fixtures.UserFixture; import org.junit.jupiter.api.*; import org.springframework.beans.factory.annotation.Autowired; @@ -12,6 +13,7 @@ import org.springframework.test.web.servlet.MockMvc; import static io.openex.rest.utils.JsonUtils.asJsonString; +import static io.openex.rest.utils.fixtures.UserFixture.EMAIL; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; @@ -32,7 +34,7 @@ class UserApiTest extends IntegrationTest { public void setup() { // Create user User user = new User(); - user.setEmail(UserFixture.EMAIL); + user.setEmail(EMAIL); user.setPassword(UserFixture.ENCODED_PASSWORD); savedUser = this.userRepository.save(user); @@ -60,7 +62,7 @@ void given_known_login_user_input_should_return_user() throws Exception { .contentType(MediaType.APPLICATION_JSON) .content(asJsonString(loginUserInput))) .andExpect(status().is2xxSuccessful()) - .andExpect(jsonPath("user_email").value(UserFixture.EMAIL)); + .andExpect(jsonPath("user_email").value(EMAIL)); } @@ -87,7 +89,7 @@ void given_known_login_user_in_uppercase_input_should_return_user() throws Excep .contentType(MediaType.APPLICATION_JSON) .content(asJsonString(loginUserInput))) .andExpect(status().is2xxSuccessful()) - .andExpect(jsonPath("user_email").value(UserFixture.EMAIL)); + .andExpect(jsonPath("user_email").value(EMAIL)); } @@ -101,8 +103,38 @@ void given_known_login_user_in_alternatingcase_input_should_return_user() throws .contentType(MediaType.APPLICATION_JSON) .content(asJsonString(loginUserInput))) .andExpect(status().is2xxSuccessful()) - .andExpect(jsonPath("user_email").value(UserFixture.EMAIL)); + .andExpect(jsonPath("user_email").value(EMAIL)); } } } + + @Nested + @DisplayName("Create user") + class Creating { + @DisplayName("Create existing user by email in lowercase gives a conflict") + @Test + @WithMockUser(roles = {"ADMIN"}) + void given_known_create_user_in_lowercase_input_should_return_conflict() throws Exception { + CreateUserInput input = new CreateUserInput(); + input.setEmail(EMAIL); + + mvc.perform(post("/api/users") + .contentType(MediaType.APPLICATION_JSON) + .content(asJsonString(input))) + .andExpect(status().isConflict()); + } + + @DisplayName("Create existing user by email in uppercase gives a conflict") + @Test + @WithMockUser(roles = {"ADMIN"}) + void given_known_create_user_in_uppercase_input_should_return_conflict() throws Exception { + CreateUserInput input = new CreateUserInput(); + input.setEmail(EMAIL.toUpperCase()); + + mvc.perform(post("/api/users") + .contentType(MediaType.APPLICATION_JSON) + .content(asJsonString(input))) + .andExpect(status().isConflict()); + } + } } diff --git a/openex-model/src/main/java/io/openex/database/model/User.java b/openex-model/src/main/java/io/openex/database/model/User.java index 8aa430d76c..41535ce38a 100644 --- a/openex-model/src/main/java/io/openex/database/model/User.java +++ b/openex-model/src/main/java/io/openex/database/model/User.java @@ -22,7 +22,9 @@ import java.util.stream.Collectors; import static java.time.Instant.now; +import static java.util.Optional.ofNullable; import static java.util.stream.StreamSupport.stream; +import static lombok.AccessLevel.NONE; @Getter @Entity @@ -57,17 +59,19 @@ public class User implements Base { @JsonProperty("user_lastname") private String lastname; + @Getter(NONE) @Setter @Column(name = "user_lang") @JsonProperty("user_lang") private String lang = LANG_AUTO; + @Getter(NONE) @Setter @Column(name = "user_theme") @JsonProperty("user_theme") private String theme = THEME_DEFAULT; - @Setter + @Getter(NONE) @Column(name = "user_email") @JsonProperty("user_email") @NotBlank @@ -179,11 +183,19 @@ public class User implements Base { private List comcheckStatuses = new ArrayList<>(); public String getLang() { - return Optional.ofNullable(this.lang).orElse(LANG_AUTO); + return ofNullable(this.lang).orElse(LANG_AUTO); } public String getTheme() { - return Optional.ofNullable(this.theme).orElse(THEME_DEFAULT); + return ofNullable(this.theme).orElse(THEME_DEFAULT); + } + + public String getEmail() { + return ofNullable(this.email).map(String::toLowerCase).orElse(null); + } + + public void setEmail(final String email) { + this.email = ofNullable(email).map(String::toLowerCase).orElseThrow(() -> new IllegalArgumentException("Email can't be null")); } private transient List injects = new ArrayList<>();