diff --git a/sandside/src/main/java/fr/ght1pc9kc/baywatch/security/domain/UserServiceImpl.java b/sandside/src/main/java/fr/ght1pc9kc/baywatch/security/domain/UserServiceImpl.java index ac17577f..8d2d9c54 100644 --- a/sandside/src/main/java/fr/ght1pc9kc/baywatch/security/domain/UserServiceImpl.java +++ b/sandside/src/main/java/fr/ght1pc9kc/baywatch/security/domain/UserServiceImpl.java @@ -13,6 +13,7 @@ import fr.ght1pc9kc.baywatch.security.domain.exceptions.UnauthenticatedUser; import fr.ght1pc9kc.baywatch.security.domain.exceptions.UnauthorizedOperation; import fr.ght1pc9kc.baywatch.security.domain.ports.AuthorizationPersistencePort; +import fr.ght1pc9kc.baywatch.security.domain.ports.NotificationPort; import fr.ght1pc9kc.baywatch.security.domain.ports.UserPersistencePort; import fr.ght1pc9kc.baywatch.techwatch.domain.model.QueryContext; import fr.ght1pc9kc.juery.api.Criteria; @@ -33,7 +34,10 @@ import java.util.Objects; import static fr.ght1pc9kc.baywatch.common.api.model.EntitiesProperties.ID; +import static fr.ght1pc9kc.baywatch.common.api.model.EntitiesProperties.ROLES; +import static fr.ght1pc9kc.baywatch.notify.api.model.EventType.USER_NOTIFICATION; import static fr.ght1pc9kc.baywatch.security.api.model.RoleUtils.hasRole; +import static java.util.function.Predicate.not; @Slf4j @AllArgsConstructor @@ -44,6 +48,7 @@ public final class UserServiceImpl implements UserService, AuthorizationService private final UserPersistencePort userRepository; private final AuthorizationPersistencePort authorizationRepository; + private final NotificationPort notificationPort; private final AuthenticationFacade authFacade; private final PasswordEncoder passwordEncoder; private final Clock clock; @@ -77,7 +82,16 @@ public Mono> create(User user) { Entity entity = Entity.identify(userId, clock.instant(), withPassword); return authorizeAllData() .flatMap(u -> userRepository.persist(List.of(entity)).single()) - .then(userRepository.persist(userId, user.roles.stream().map(Permission::toString).distinct().toList())); + .then(userRepository.persist(userId, user.roles.stream().map(Permission::toString).distinct().toList())) + .flatMap(this::notifyAdmins); + } + + private Mono> notifyAdmins(Entity newUser) { + return this.list(PageRequest.all(Criteria.property(ROLES).eq(Role.ADMIN.toString()))) + .filter(not(admin -> admin.id.equals(newUser.createdBy))) + .map(admin -> notificationPort.send(admin.id, USER_NOTIFICATION, + String.format("New user %s created by %s.", newUser.self.login, newUser.createdBy))) + .then(Mono.just(newUser)); } @Override diff --git a/sandside/src/main/java/fr/ght1pc9kc/baywatch/security/domain/ports/NotificationPort.java b/sandside/src/main/java/fr/ght1pc9kc/baywatch/security/domain/ports/NotificationPort.java new file mode 100644 index 00000000..a48a63bb --- /dev/null +++ b/sandside/src/main/java/fr/ght1pc9kc/baywatch/security/domain/ports/NotificationPort.java @@ -0,0 +1,8 @@ +package fr.ght1pc9kc.baywatch.security.domain.ports; + +import fr.ght1pc9kc.baywatch.notify.api.model.BasicEvent; +import fr.ght1pc9kc.baywatch.notify.api.model.EventType; + +public interface NotificationPort { + BasicEvent send(String userId, EventType type, T data); +} diff --git a/sandside/src/main/java/fr/ght1pc9kc/baywatch/security/infra/adapters/NotificationServiceAdapter.java b/sandside/src/main/java/fr/ght1pc9kc/baywatch/security/infra/adapters/NotificationServiceAdapter.java new file mode 100644 index 00000000..1a905eb2 --- /dev/null +++ b/sandside/src/main/java/fr/ght1pc9kc/baywatch/security/infra/adapters/NotificationServiceAdapter.java @@ -0,0 +1,14 @@ +package fr.ght1pc9kc.baywatch.security.infra.adapters; + +import fr.ght1pc9kc.baywatch.notify.api.NotifyService; +import fr.ght1pc9kc.baywatch.security.domain.ports.NotificationPort; +import lombok.RequiredArgsConstructor; +import lombok.experimental.Delegate; +import org.springframework.stereotype.Component; + +@Component +@RequiredArgsConstructor +public class NotificationServiceAdapter implements NotificationPort { + @Delegate + private final NotifyService delegate; +} diff --git a/sandside/src/main/java/fr/ght1pc9kc/baywatch/security/infra/adapters/UserServiceAdapter.java b/sandside/src/main/java/fr/ght1pc9kc/baywatch/security/infra/adapters/UserServiceAdapter.java index 8078e295..c7c04663 100644 --- a/sandside/src/main/java/fr/ght1pc9kc/baywatch/security/infra/adapters/UserServiceAdapter.java +++ b/sandside/src/main/java/fr/ght1pc9kc/baywatch/security/infra/adapters/UserServiceAdapter.java @@ -6,6 +6,7 @@ import fr.ght1pc9kc.baywatch.security.api.UserService; import fr.ght1pc9kc.baywatch.security.domain.UserServiceImpl; import fr.ght1pc9kc.baywatch.security.domain.ports.AuthorizationPersistencePort; +import fr.ght1pc9kc.baywatch.security.domain.ports.NotificationPort; import fr.ght1pc9kc.baywatch.security.domain.ports.UserPersistencePort; import fr.ght1pc9kc.baywatch.security.infra.model.BaywatchUserDetails; import fr.ght1pc9kc.juery.api.Criteria; @@ -34,11 +35,12 @@ public class UserServiceAdapter implements AuthorizationService, UserService, Re @Autowired public UserServiceAdapter(UserPersistencePort userPersistencePort, AuthorizationPersistencePort authorizationRepository, + NotificationPort notificationPort, AuthenticationFacade authFacade, PasswordEncoder passwordEncoder) { this.delegate = new UserServiceImpl( - userPersistencePort, authorizationRepository, authFacade, passwordEncoder, Clock.systemUTC(), - UlidFactory.newInstance()); + userPersistencePort, authorizationRepository, notificationPort, authFacade, passwordEncoder, + Clock.systemUTC(), UlidFactory.newInstance()); this.delegateA = (UserServiceImpl) this.delegate; } diff --git a/sandside/src/test/java/fr/ght1pc9kc/baywatch/security/domain/UserServiceImplTest.java b/sandside/src/test/java/fr/ght1pc9kc/baywatch/security/domain/UserServiceImplTest.java index c521b045..293d045b 100644 --- a/sandside/src/test/java/fr/ght1pc9kc/baywatch/security/domain/UserServiceImplTest.java +++ b/sandside/src/test/java/fr/ght1pc9kc/baywatch/security/domain/UserServiceImplTest.java @@ -4,6 +4,8 @@ import com.github.f4b6a3.ulid.UlidFactory; import fr.ght1pc9kc.baywatch.common.api.model.EntitiesProperties; import fr.ght1pc9kc.baywatch.common.api.model.Entity; +import fr.ght1pc9kc.baywatch.notify.api.model.BasicEvent; +import fr.ght1pc9kc.baywatch.notify.api.model.EventType; import fr.ght1pc9kc.baywatch.security.api.AuthenticationFacade; import fr.ght1pc9kc.baywatch.security.api.model.Permission; import fr.ght1pc9kc.baywatch.security.api.model.Role; @@ -11,6 +13,7 @@ import fr.ght1pc9kc.baywatch.security.domain.exceptions.UnauthenticatedUser; import fr.ght1pc9kc.baywatch.security.domain.exceptions.UnauthorizedOperation; import fr.ght1pc9kc.baywatch.security.domain.ports.AuthorizationPersistencePort; +import fr.ght1pc9kc.baywatch.security.domain.ports.NotificationPort; import fr.ght1pc9kc.baywatch.security.domain.ports.UserPersistencePort; import fr.ght1pc9kc.baywatch.techwatch.domain.model.QueryContext; import fr.ght1pc9kc.baywatch.tests.samples.UserSamples; @@ -43,6 +46,7 @@ class UserServiceImplTest { private final AuthorizationPersistencePort mockAuthorizationRepository = mock(AuthorizationPersistencePort.class); private final AuthenticationFacade mockAuthFacade = mock(AuthenticationFacade.class); private final UlidFactory mockUlidFactory = mock(UlidFactory.class); + private final NotificationPort mockNotificationPort = mock(NotificationPort.class); private UserServiceImpl tested; @@ -57,8 +61,10 @@ void setUp() { when(mockUserRepository.delete(anyString(), anyCollection())).thenReturn(Mono.empty().then()); when(mockUserRepository.count(any())).thenReturn(Mono.just(3)); when(mockAuthorizationRepository.count(any())).thenReturn(Mono.just(0)); + when(mockNotificationPort.send(anyString(), any(EventType.class), any())) + .thenReturn(new BasicEvent<>(Ulid.fast().toString(), EventType.USER_NOTIFICATION, "New jedi in the force")); - tested = new UserServiceImpl(mockUserRepository, mockAuthorizationRepository, + tested = new UserServiceImpl(mockUserRepository, mockAuthorizationRepository, mockNotificationPort, mockAuthFacade, NoOpPasswordEncoder.getInstance(), Clock.fixed(CURRENT, ZoneOffset.UTC), mockUlidFactory); } @@ -117,7 +123,7 @@ void should_create_user_with_admin() { ArgumentCaptor>> users = ArgumentCaptor.forClass(List.class); verify(mockUserRepository).persist(users.capture()); - Entity actual = users.getValue().get(0); + Entity actual = users.getValue().getFirst(); Assertions.assertThat(actual).isEqualTo(Entity.identify(UserSamples.OBIWAN.id, CURRENT, UserSamples.OBIWAN.self)); }