Skip to content

Commit

Permalink
feat(sandside): #198 implement password check from user
Browse files Browse the repository at this point in the history
and password generator

close #198
  • Loading branch information
Marthym committed Jan 13, 2024
1 parent aab2a42 commit 507d89f
Show file tree
Hide file tree
Showing 4 changed files with 89 additions and 16 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
* @param self The Persisted Entity
* @param <T> The type of the persisted object
*/
@Builder
@Builder(toBuilder = true)
public record Entity<T>(
@NonNull String id,
@NonNull String createdBy,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
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;
Expand All @@ -28,13 +27,8 @@ public class PasswordServiceImpl implements PasswordService {
public Mono<PasswordEvaluation> checkPasswordStrength(String password) {
return authFacade.getConnectedUser()
.switchIfEmpty(Mono.error(UnauthorizedException::new))
.flatMap(user -> localeFacade.getLocale().map(l -> Tuples.of(user, l)))
.map(context ->
passwordChecker.estimate(password, context.getT2(), List.of(
context.getT1().self().name,
context.getT1().self().login,
context.getT1().self().mail))
);
.map(user -> user.self().withPassword(password))
.flatMap(this::checkPasswordStrength);
}

@Override
Expand All @@ -46,11 +40,9 @@ public Mono<PasswordEvaluation> checkPasswordStrength(User user) {
@Override
public Flux<String> generateSecurePassword(int number) {
return Flux.<String>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"));
sink.onRequest(n -> LongStream.range(0, number)
.mapToObj(ignore -> passwordChecker.generate())
.forEach(sink::next)))
.take(Integer.valueOf(number).longValue());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,14 @@
import me.gosimple.nbvcxz.scoring.TimeEstimate;
import org.springframework.stereotype.Service;

import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Locale;
import java.util.Optional;
import java.util.ResourceBundle;
import java.util.Set;
import java.util.stream.Collectors;

@Service
public record PasswordCheckerNbvcxz() implements PasswordStrengthChecker {
Expand All @@ -28,11 +31,14 @@ public PasswordEvaluation estimate(String password, Locale locale, Collection<St
return new PasswordEvaluation(false, 0, mainResource.getString("main.estimate.instant"));
}

Set<String> excludeWords = exclude.stream()
.flatMap(word -> Arrays.stream(word.split("[ @_-]")))
.collect(Collectors.toUnmodifiableSet());
List<Dictionary> dictionaryList = ConfigurationBuilder.getDefaultDictionaries();
dictionaryList.add(new DictionaryBuilder()
.setDictionaryName("exclude")
.setExclusion(true)
.addWords(exclude, 0)
.addWords(excludeWords, 0)
.createDictionary());

Configuration configuration = new ConfigurationBuilder()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package fr.ght1pc9kc.baywatch.security.domain;

import fr.ght1pc9kc.baywatch.common.api.LocaleFacade;
import fr.ght1pc9kc.baywatch.security.api.AuthenticationFacade;
import fr.ght1pc9kc.baywatch.security.api.PasswordService;
import fr.ght1pc9kc.baywatch.security.api.model.User;
import fr.ght1pc9kc.baywatch.security.infra.adapters.PasswordCheckerNbvcxz;
import fr.ght1pc9kc.baywatch.tests.samples.UserSamples;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;

import java.util.Locale;

import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

class PasswordServiceImplTest {

private PasswordService tested;

@BeforeEach
void setUp() {
AuthenticationFacade authenticationFacade = mock(AuthenticationFacade.class);
when(authenticationFacade.getConnectedUser()).thenReturn(Mono.just(UserSamples.OBIWAN));
LocaleFacade localeFacade = mock(LocaleFacade.class);
when(localeFacade.getLocale()).thenReturn(Mono.just(Locale.ENGLISH));

tested = new PasswordServiceImpl(authenticationFacade, new PasswordCheckerNbvcxz(), localeFacade);
}

@ParameterizedTest
@CsvSource({
"123, false",
"obiwan, false",
"154uytfoKenobi123!, false",
"J0Qs, false",
"Ohvahoizaetho1at, true",
})
void should_check_password_strength_when_authenticated(String password, boolean expectedSecure) {
StepVerifier.create(tested.checkPasswordStrength(password))
.assertNext(actual -> Assertions.assertThat(actual.isSecure()).isEqualTo(expectedSecure))
.verifyComplete();
}

@ParameterizedTest
@CsvSource({
"123, false",
"luke, false",
"154uytfluke123skywalker123!, false",
"J0Qs, false",
"Ohvahoizaetho1at, true",
})
void should_check_password_strength_for_anonymous(String password, boolean expectedSecure) {
User user = UserSamples.LUKE.self().withPassword(password);
StepVerifier.create(tested.checkPasswordStrength(user))
.assertNext(actual -> Assertions.assertThat(actual.isSecure()).isEqualTo(expectedSecure))
.verifyComplete();
}

@Test
void should_generate_password() {
StepVerifier.create(tested.generateSecurePassword(5))
.assertNext(actual -> Assertions.assertThat(actual).isNotBlank().hasSize(16))
.assertNext(actual -> Assertions.assertThat(actual).isNotBlank().hasSize(16))
.assertNext(actual -> Assertions.assertThat(actual).isNotBlank().hasSize(16))
.assertNext(actual -> Assertions.assertThat(actual).isNotBlank().hasSize(16))
.assertNext(actual -> Assertions.assertThat(actual).isNotBlank().hasSize(16))
.verifyComplete();
}
}

0 comments on commit 507d89f

Please sign in to comment.