From a6c259b0273a3e34fc3573de273ef19ddf0554aa Mon Sep 17 00:00:00 2001 From: Marthym Date: Sat, 13 Jan 2024 16:02:25 +0100 Subject: [PATCH] feat(sandside): #198 implement password generator and checker for anonymous --- .../security/api/PasswordService.java | 19 +++++++++++++---- .../security/domain/PasswordServiceImpl.java | 21 +++++++++++++++++-- .../domain/ports/PasswordStrengthChecker.java | 2 ++ .../infra/adapters/PasswordCheckerNbvcxz.java | 6 ++++++ .../security/infra/adapters/UserMapper.java | 3 +++ .../infra/controllers/PasswordController.java | 16 ++++++++++++++ .../graphql/security/password.graphqls | 4 +++- 7 files changed, 64 insertions(+), 7 deletions(-) diff --git a/sandside/src/main/java/fr/ght1pc9kc/baywatch/security/api/PasswordService.java b/sandside/src/main/java/fr/ght1pc9kc/baywatch/security/api/PasswordService.java index 66abf6fe..482d205c 100644 --- a/sandside/src/main/java/fr/ght1pc9kc/baywatch/security/api/PasswordService.java +++ b/sandside/src/main/java/fr/ght1pc9kc/baywatch/security/api/PasswordService.java @@ -1,17 +1,28 @@ package fr.ght1pc9kc.baywatch.security.api; import fr.ght1pc9kc.baywatch.security.api.model.PasswordEvaluation; +import fr.ght1pc9kc.baywatch.security.api.model.User; +import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; public interface PasswordService { /** - * Check the password strength and return an evaluation + *

Check the password strength and return an evaluation

+ *

The current authenticated user is used to populate the password evaluation context

* * @param password The new password - * @return {@code Void} when the password is changed - * @throws IllegalArgumentException When password strength is not enough + * @return A password evaluation + * @throws fr.ght1pc9kc.baywatch.common.api.exceptions.UnauthorizedException When no user was authenticated */ Mono checkPasswordStrength(String password); - Mono generateSecurePassword(); + /** + * Check the password strength and return a {@link PasswordEvaluation} + * + * @param user The user owner of the password as the context + * @return A password evaluation + */ + Mono checkPasswordStrength(User user); + + Flux generateSecurePassword(int number); } diff --git a/sandside/src/main/java/fr/ght1pc9kc/baywatch/security/domain/PasswordServiceImpl.java b/sandside/src/main/java/fr/ght1pc9kc/baywatch/security/domain/PasswordServiceImpl.java index 7c9df9eb..ee896635 100644 --- a/sandside/src/main/java/fr/ght1pc9kc/baywatch/security/domain/PasswordServiceImpl.java +++ b/sandside/src/main/java/fr/ght1pc9kc/baywatch/security/domain/PasswordServiceImpl.java @@ -5,13 +5,18 @@ import fr.ght1pc9kc.baywatch.security.api.AuthenticationFacade; import fr.ght1pc9kc.baywatch.security.api.PasswordService; import fr.ght1pc9kc.baywatch.security.api.model.PasswordEvaluation; +import fr.ght1pc9kc.baywatch.security.api.model.User; import fr.ght1pc9kc.baywatch.security.domain.ports.PasswordStrengthChecker; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import reactor.util.function.Tuples; import java.util.List; +import java.util.stream.LongStream; +@Slf4j @RequiredArgsConstructor public class PasswordServiceImpl implements PasswordService { @@ -33,7 +38,19 @@ public Mono checkPasswordStrength(String password) { } @Override - public Mono generateSecurePassword() { - return null; + public Mono checkPasswordStrength(User user) { + return localeFacade.getLocale() + .map(locale -> passwordChecker.estimate(user.password, locale, List.of(user.name, user.login, user.mail))) + } + + @Override + public Flux generateSecurePassword(int number) { + return Flux.create(sink -> + sink.onRequest(n -> + LongStream.range(1, n) + .mapToObj(ignore -> passwordChecker.generate()) + .forEach(sink::next))) + .take(Integer.valueOf(number).longValue()) + .doOnComplete(() -> log.atDebug().log("Password generation complete")); } } diff --git a/sandside/src/main/java/fr/ght1pc9kc/baywatch/security/domain/ports/PasswordStrengthChecker.java b/sandside/src/main/java/fr/ght1pc9kc/baywatch/security/domain/ports/PasswordStrengthChecker.java index 33e1f061..f3a525fe 100644 --- a/sandside/src/main/java/fr/ght1pc9kc/baywatch/security/domain/ports/PasswordStrengthChecker.java +++ b/sandside/src/main/java/fr/ght1pc9kc/baywatch/security/domain/ports/PasswordStrengthChecker.java @@ -12,4 +12,6 @@ public interface PasswordStrengthChecker { default PasswordEvaluation estimate(String password, Locale locale) { return estimate(password, locale, Collections.emptyList()); } + + String generate(); } diff --git a/sandside/src/main/java/fr/ght1pc9kc/baywatch/security/infra/adapters/PasswordCheckerNbvcxz.java b/sandside/src/main/java/fr/ght1pc9kc/baywatch/security/infra/adapters/PasswordCheckerNbvcxz.java index 9ee0e3bd..c28b3ec7 100644 --- a/sandside/src/main/java/fr/ght1pc9kc/baywatch/security/infra/adapters/PasswordCheckerNbvcxz.java +++ b/sandside/src/main/java/fr/ght1pc9kc/baywatch/security/infra/adapters/PasswordCheckerNbvcxz.java @@ -7,6 +7,7 @@ import me.gosimple.nbvcxz.resources.ConfigurationBuilder; import me.gosimple.nbvcxz.resources.Dictionary; import me.gosimple.nbvcxz.resources.DictionaryBuilder; +import me.gosimple.nbvcxz.resources.Generator; import me.gosimple.nbvcxz.scoring.Result; import me.gosimple.nbvcxz.scoring.TimeEstimate; import org.springframework.stereotype.Service; @@ -55,4 +56,9 @@ public PasswordEvaluation estimate(String password, Locale locale, Collection userForm); + User getUser(UserForm userForm); + @SuppressWarnings({"OptionalAssignedToNull", "java:S2789", "java:S3655"}) default UsersRecord updatableUserToRecord(UpdatableUser user) { var r = new UsersRecord(); diff --git a/sandside/src/main/java/fr/ght1pc9kc/baywatch/security/infra/controllers/PasswordController.java b/sandside/src/main/java/fr/ght1pc9kc/baywatch/security/infra/controllers/PasswordController.java index aebadbbb..5f571b44 100644 --- a/sandside/src/main/java/fr/ght1pc9kc/baywatch/security/infra/controllers/PasswordController.java +++ b/sandside/src/main/java/fr/ght1pc9kc/baywatch/security/infra/controllers/PasswordController.java @@ -2,11 +2,14 @@ import fr.ght1pc9kc.baywatch.security.api.PasswordService; import fr.ght1pc9kc.baywatch.security.api.model.PasswordEvaluation; +import fr.ght1pc9kc.baywatch.security.infra.adapters.UserMapper; +import fr.ght1pc9kc.baywatch.security.infra.model.UserForm; import lombok.RequiredArgsConstructor; import org.springframework.graphql.data.method.annotation.Argument; import org.springframework.graphql.data.method.annotation.QueryMapping; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.stereotype.Controller; +import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; @Controller @@ -14,9 +17,22 @@ @PreAuthorize("isAuthenticated()") public class PasswordController { private final PasswordService passwordService; + private final UserMapper userMapper; @QueryMapping public Mono checkPasswordStrength(@Argument("password") String password) { return passwordService.checkPasswordStrength(password); } + + @QueryMapping + @PreAuthorize("isAnonymous()") + public Mono checkAnonymousPassword(@Argument("user") UserForm user) { + return passwordService.checkPasswordStrength(userMapper.getUser(user)); + } + + @QueryMapping + @PreAuthorize("isAnonymous()") + public Flux generatePasswords(@Argument("number") int number) { + return passwordService.generateSecurePassword(number); + } } diff --git a/sandside/src/main/resources/graphql/security/password.graphqls b/sandside/src/main/resources/graphql/security/password.graphqls index aa6baf3a..a35b8c2e 100644 --- a/sandside/src/main/resources/graphql/security/password.graphqls +++ b/sandside/src/main/resources/graphql/security/password.graphqls @@ -6,4 +6,6 @@ type PasswordEvaluation { extend type Query { checkPasswordStrength(password: String): PasswordEvaluation -} \ No newline at end of file + checkAnonymousPassword(user: UserForm): PasswordEvaluation + generatePasswords(number: Int): [String] +}