From 4ebc9d573c5c221095436257c4aff5f951db6f1f Mon Sep 17 00:00:00 2001 From: jultty Date: Wed, 24 Jan 2024 19:05:15 -0300 Subject: [PATCH] Fix password encoding persistence --- README.md | 11 ++++++++- build.gradle.kts | 3 ++- docs/v0.1.2/relatorio.md | 10 ++++++++ src/{test/hurl => hurl/v0.1.1}/account.hurl | 0 src/{test/hurl => hurl/v0.1.1}/ewa-demo.hurl | 0 .../hurl => hurl/v0.1.1}/exercise-set.hurl | 0 src/{test/hurl => hurl/v0.1.1}/exercise.hurl | 0 src/{test/hurl => hurl/v0.1.1}/oop-demo.hurl | 0 src/{test/hurl => hurl/v0.1.1}/option.hurl | 0 src/hurl/v0.1.2/account_single.hurl | 10 ++++++++ .../java/mirante/api/account/Account.java | 23 +++++++++++++++---- .../api/account/AccountController.java | 10 +++++++- .../mirante/api/account/AccountRequest.java | 17 ++++++++++++++ .../java/mirante/api/account/SecUtils.java | 8 ------- .../java/mirante/api/account/AccountTest.java | 19 +++++++++++++++ src/web/account.html | 12 +++++----- 16 files changed, 102 insertions(+), 21 deletions(-) rename src/{test/hurl => hurl/v0.1.1}/account.hurl (100%) rename src/{test/hurl => hurl/v0.1.1}/ewa-demo.hurl (100%) rename src/{test/hurl => hurl/v0.1.1}/exercise-set.hurl (100%) rename src/{test/hurl => hurl/v0.1.1}/exercise.hurl (100%) rename src/{test/hurl => hurl/v0.1.1}/oop-demo.hurl (100%) rename src/{test/hurl => hurl/v0.1.1}/option.hurl (100%) create mode 100644 src/hurl/v0.1.2/account_single.hurl create mode 100644 src/main/java/mirante/api/account/AccountRequest.java delete mode 100644 src/main/java/mirante/api/account/SecUtils.java create mode 100644 src/test/java/mirante/api/account/AccountTest.java diff --git a/README.md b/README.md index db283c3..1b6691e 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # mirante-spring -Mirante is a data-oriented educational system that aims to minimize the loss of relevant data that could bring us more insight into how we learn. +Mirante is a data-oriented educational system that aims to minimize the loss of relevant data that could bring more insight into how we learn. This repository contains an implementation using Java and the Spring Boot Framework. @@ -56,5 +56,14 @@ To see all available options: gradle tasks ``` +Once the server is running, for development environments an [H2 database console](https://www.h2database.com/html/tutorial.html) is available on `localhost:/h2-console`. + +The following options allow access to the H2 console: + +- **Driver class:** `org.h2.Driver` +- **JDBC URL:** `jdbc:h2:mem:mirante` +- **User Name:** `dev` +- **Password:** Empty + If you have [Nix](https://nixos.org/manual/nix/stable/introduction) available on your system and flake support enabled, you can use the flake file to setup a development environment with JDK 21 and Gradle using `nix develop`. diff --git a/build.gradle.kts b/build.gradle.kts index 49d42c3..3b721c6 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -19,8 +19,9 @@ repositories { dependencies { implementation("org.springframework.boot:spring-boot-starter-data-jpa") implementation("org.springframework.boot:spring-boot-starter-data-rest") - implementation("org.springframework.security:spring-security-crypto:6.2.1") testImplementation("org.springframework.boot:spring-boot-starter-test") + implementation("org.springframework.security:spring-security-crypto:6.2.1") + implementation("org.bouncycastle:bcprov-jdk15on:1.64") runtimeOnly("com.h2database:h2") } diff --git a/docs/v0.1.2/relatorio.md b/docs/v0.1.2/relatorio.md index ea781d8..9d1084e 100644 --- a/docs/v0.1.2/relatorio.md +++ b/docs/v0.1.2/relatorio.md @@ -17,6 +17,16 @@ A classe utilizada cuida da geração de um _salt_ para tornar a senha armazenad Durante a implementação, um erro inicial enfrentado após o _commit_ `0b41f38` foi que as senhas eram gravadas no banco como valores nulos. +Ao investigar a raiz do problema, foram indetificadas algumas novas informações: + +- O uso da classe `Argon2PaswordEncoder` requer a inserção manual da dependência `org.bouncycastle:bcprov-jdk15on:1.64` +- A [especificação do JPA][#4] exige que exista um construtor padrão (sem argumentos) em classes que definem uma entidade + +A segunda informação levou à compreensão de que o ORM não estava chamando o construtor onde a senha era codificada, mas sim utilizando o construtor padrão. + +Como solução, o controlador do _endpoint_ `account` passou a serializar os dados da requisição em um objeto de uma nova classe intermediária, criada com acesso restrito somente ao pacote `account`, e então instanciar um objeto da classe `Account` utilizando o construtor correto, que codifica a senha recebida, e então a passa para o repositório JPA para ser persistida no banco. + [#1]: https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html [#2]: https://docs.spring.io/spring-security/reference/features/authentication/password-storage.html#authentication-password-storage-argon2 [#3]: https://docs.spring.io/spring-security/site/docs/6.2.1/api/org/springframework/security/crypto/argon2/Argon2PasswordEncoder.html#encode(java.lang.CharSequence) +[#4]: https://openjpa.apache.org/builds/1.2.3/apache-openjpa/docs/jpa_overview_pc.html#:~:text=The%20JPA%20specification%20requires%20that,include%20a%20no%2Darg%20constructor. diff --git a/src/test/hurl/account.hurl b/src/hurl/v0.1.1/account.hurl similarity index 100% rename from src/test/hurl/account.hurl rename to src/hurl/v0.1.1/account.hurl diff --git a/src/test/hurl/ewa-demo.hurl b/src/hurl/v0.1.1/ewa-demo.hurl similarity index 100% rename from src/test/hurl/ewa-demo.hurl rename to src/hurl/v0.1.1/ewa-demo.hurl diff --git a/src/test/hurl/exercise-set.hurl b/src/hurl/v0.1.1/exercise-set.hurl similarity index 100% rename from src/test/hurl/exercise-set.hurl rename to src/hurl/v0.1.1/exercise-set.hurl diff --git a/src/test/hurl/exercise.hurl b/src/hurl/v0.1.1/exercise.hurl similarity index 100% rename from src/test/hurl/exercise.hurl rename to src/hurl/v0.1.1/exercise.hurl diff --git a/src/test/hurl/oop-demo.hurl b/src/hurl/v0.1.1/oop-demo.hurl similarity index 100% rename from src/test/hurl/oop-demo.hurl rename to src/hurl/v0.1.1/oop-demo.hurl diff --git a/src/test/hurl/option.hurl b/src/hurl/v0.1.1/option.hurl similarity index 100% rename from src/test/hurl/option.hurl rename to src/hurl/v0.1.1/option.hurl diff --git a/src/hurl/v0.1.2/account_single.hurl b/src/hurl/v0.1.2/account_single.hurl new file mode 100644 index 0000000..7148f8e --- /dev/null +++ b/src/hurl/v0.1.2/account_single.hurl @@ -0,0 +1,10 @@ +# client can create an account +POST http://localhost:8888/account +{ + "registration": "jc000001", + "name": " Tania Wolfgramm", + "email": "tania@mirante.dev", + "password": "xyz6060" +} + +HTTP 201 diff --git a/src/main/java/mirante/api/account/Account.java b/src/main/java/mirante/api/account/Account.java index 06d99fe..f5db96d 100644 --- a/src/main/java/mirante/api/account/Account.java +++ b/src/main/java/mirante/api/account/Account.java @@ -2,6 +2,9 @@ import jakarta.persistence.Entity; import jakarta.persistence.Id; +import jakarta.persistence.Transient; + +import org.springframework.security.crypto.argon2.Argon2PasswordEncoder; @Entity public class Account { @@ -12,18 +15,30 @@ public class Account { private String email; private String password; - public Account() {} + @Transient + private Argon2PasswordEncoder encoder = + Argon2PasswordEncoder.defaultsForSpringSecurity_v5_8(); public Account(String registration, String name, String email, String password) { this.registration = registration; this.name = name; this.email = email; - this.password = SecUtils.encoder.encode(password); + this.password = encoder.encode(password); + } + + Account() {} + + public Boolean checkPassword(String password) { + if (encoder.matches(password, this.password)) { + return true; + } else { + return false; + } } public void changePassword(String old_password, String new_password) { - if (SecUtils.encoder.matches(old_password, this.password)) { - this.password = SecUtils.encoder.encode(new_password); + if (encoder.matches(old_password, this.password)) { + this.password = encoder.encode(new_password); } } diff --git a/src/main/java/mirante/api/account/AccountController.java b/src/main/java/mirante/api/account/AccountController.java index e4feebc..a9cfadc 100644 --- a/src/main/java/mirante/api/account/AccountController.java +++ b/src/main/java/mirante/api/account/AccountController.java @@ -22,7 +22,15 @@ List all() { @PostMapping("/account") @ResponseStatus(HttpStatus.CREATED) - Account newAccount(@RequestBody Account newAccount) { + Account newAccount(@RequestBody AccountRequest request) { + + Account newAccount = new Account( + request.registration, + request.name, + request.email, + request.password + ); + return repository.save(newAccount); } diff --git a/src/main/java/mirante/api/account/AccountRequest.java b/src/main/java/mirante/api/account/AccountRequest.java new file mode 100644 index 0000000..358fdcd --- /dev/null +++ b/src/main/java/mirante/api/account/AccountRequest.java @@ -0,0 +1,17 @@ +package mirante.api.account; + +class AccountRequest { + + String registration; + String name; + String email; + String password; + + AccountRequest(String registration, String name, String email, String password) { + this.registration = registration; + this.name = name; + this.email = email; + this.password = password; + } +} + diff --git a/src/main/java/mirante/api/account/SecUtils.java b/src/main/java/mirante/api/account/SecUtils.java deleted file mode 100644 index acd4b4e..0000000 --- a/src/main/java/mirante/api/account/SecUtils.java +++ /dev/null @@ -1,8 +0,0 @@ -package mirante.api.account; - -import org.springframework.security.crypto.argon2.Argon2PasswordEncoder; - -class SecUtils { - public static Argon2PasswordEncoder encoder = Argon2PasswordEncoder.defaultsForSpringSecurity_v5_8(); -} - diff --git a/src/test/java/mirante/api/account/AccountTest.java b/src/test/java/mirante/api/account/AccountTest.java new file mode 100644 index 0000000..0061772 --- /dev/null +++ b/src/test/java/mirante/api/account/AccountTest.java @@ -0,0 +1,19 @@ +package mirante.api.account; + +import mirante.api.account.Account; + +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertTrue; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +class AccountTest { + + @Test + void encodedPasswordCanBeDecoded() { + String password = "xyz0000"; + Account account = new Account("JC000000", "Jane Doe", "jane@doe.com", password); + assertTrue(account.checkPassword(password)); + } + +} diff --git a/src/web/account.html b/src/web/account.html index 54804d1..814b527 100644 --- a/src/web/account.html +++ b/src/web/account.html @@ -8,16 +8,16 @@
-
-
+
+
-
-
+
+


-
+

@@ -47,8 +47,8 @@ xhr.setRequestHeader('Content-Type', 'application/json'); const data = { + registration: form.elements.registration.value, name: form.elements.name.value, - username: form.elements.username.value, email: form.elements.email.value, password: form.elements.password.value, };