diff --git a/api/src/main/java/run/halo/app/core/user/service/UserPostCreatingHandler.java b/api/src/main/java/run/halo/app/core/user/service/UserPostCreatingHandler.java new file mode 100644 index 0000000000..e7ad214fc8 --- /dev/null +++ b/api/src/main/java/run/halo/app/core/user/service/UserPostCreatingHandler.java @@ -0,0 +1,23 @@ +package run.halo.app.core.user.service; + +import org.pf4j.ExtensionPoint; +import reactor.core.publisher.Mono; +import run.halo.app.core.extension.User; + +/** + * User post-creating handler. + * + * @author johnniang + * @since 2.20.8 + */ +public interface UserPostCreatingHandler extends ExtensionPoint { + + /** + * Do something after creating user. + * + * @param user create user. + * @return {@code Mono.empty()} if handling successfully. + */ + Mono postCreating(User user); + +} diff --git a/api/src/main/java/run/halo/app/core/user/service/UserPreCreatingHandler.java b/api/src/main/java/run/halo/app/core/user/service/UserPreCreatingHandler.java new file mode 100644 index 0000000000..775324eda4 --- /dev/null +++ b/api/src/main/java/run/halo/app/core/user/service/UserPreCreatingHandler.java @@ -0,0 +1,23 @@ +package run.halo.app.core.user.service; + +import org.pf4j.ExtensionPoint; +import reactor.core.publisher.Mono; +import run.halo.app.core.extension.User; + +/** + * User pre-creating handler. + * + * @author johnniang + * @since 2.20.8 + */ +public interface UserPreCreatingHandler extends ExtensionPoint { + + /** + * Do something before user creating. + * + * @param user modifiable user detail + * @return {@code Mono.empty()} if handling successfully. + */ + Mono preCreating(User user); + +} diff --git a/application/src/main/java/run/halo/app/core/user/service/UserServiceImpl.java b/application/src/main/java/run/halo/app/core/user/service/impl/UserServiceImpl.java similarity index 90% rename from application/src/main/java/run/halo/app/core/user/service/UserServiceImpl.java rename to application/src/main/java/run/halo/app/core/user/service/impl/UserServiceImpl.java index fb1b451244..86a4414247 100644 --- a/application/src/main/java/run/halo/app/core/user/service/UserServiceImpl.java +++ b/application/src/main/java/run/halo/app/core/user/service/impl/UserServiceImpl.java @@ -1,4 +1,4 @@ -package run.halo.app.core.user.service; +package run.halo.app.core.user.service.impl; import static run.halo.app.extension.ExtensionUtil.defaultSort; import static run.halo.app.extension.index.query.QueryFactory.equal; @@ -25,6 +25,12 @@ import run.halo.app.core.extension.Role; import run.halo.app.core.extension.RoleBinding; import run.halo.app.core.extension.User; +import run.halo.app.core.user.service.EmailVerificationService; +import run.halo.app.core.user.service.RoleService; +import run.halo.app.core.user.service.SignUpData; +import run.halo.app.core.user.service.UserPostCreatingHandler; +import run.halo.app.core.user.service.UserPreCreatingHandler; +import run.halo.app.core.user.service.UserService; import run.halo.app.event.user.PasswordChangedEvent; import run.halo.app.extension.ListOptions; import run.halo.app.extension.Metadata; @@ -38,6 +44,7 @@ import run.halo.app.infra.exception.EmailVerificationFailed; import run.halo.app.infra.exception.UnsatisfiedAttributeValueException; import run.halo.app.infra.exception.UserNotFoundException; +import run.halo.app.plugin.extensionpoint.ExtensionGetter; @Service @RequiredArgsConstructor @@ -57,6 +64,8 @@ public class UserServiceImpl implements UserService { private final EmailVerificationService emailVerificationService; + private final ExtensionGetter extensionGetter; + private Clock clock = Clock.systemUTC(); void setClock(Clock clock) { @@ -222,13 +231,20 @@ public Mono createUser(User user, Set roleNames) { ) .then(); }) - .then(Mono.defer(() -> client.create(user) - .flatMap(newUser -> grantRoles(user.getMetadata().getName(), roleNames) - .retryWhen( - Retry.backoff(5, Duration.ofMillis(100)) + .then(extensionGetter.getExtensions(UserPreCreatingHandler.class) + .concatMap(handler -> handler.preCreating(user)) + .then(Mono.defer(() -> client.create(user) + .flatMap(newUser -> grantRoles(user.getMetadata().getName(), roleNames) + .retryWhen(Retry.backoff(5, Duration.ofMillis(100)) .filter(OptimisticLockingFailureException.class::isInstance) + ) ) )) + .flatMap(createdUser -> extensionGetter.getExtensions(UserPostCreatingHandler.class) + .concatMap(handler -> handler.postCreating(createdUser)) + .then() + .thenReturn(createdUser) + ) ); } diff --git a/application/src/test/java/run/halo/app/core/user/service/UserServiceImplTest.java b/application/src/test/java/run/halo/app/core/user/service/impl/UserServiceImplTest.java similarity index 92% rename from application/src/test/java/run/halo/app/core/user/service/UserServiceImplTest.java rename to application/src/test/java/run/halo/app/core/user/service/impl/UserServiceImplTest.java index d01bed46d5..9b1a5f5571 100644 --- a/application/src/test/java/run/halo/app/core/user/service/UserServiceImplTest.java +++ b/application/src/test/java/run/halo/app/core/user/service/impl/UserServiceImplTest.java @@ -1,4 +1,4 @@ -package run.halo.app.core.user.service; +package run.halo.app.core.user.service.impl; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -8,6 +8,7 @@ import static org.mockito.ArgumentMatchers.anySet; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.argThat; +import static org.mockito.ArgumentMatchers.assertArg; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.isA; import static org.mockito.Mockito.doReturn; @@ -19,6 +20,7 @@ import static org.mockito.Mockito.when; import static run.halo.app.extension.GroupVersionKind.fromExtension; +import java.util.HashMap; import java.util.Set; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; @@ -37,6 +39,10 @@ import run.halo.app.core.extension.RoleBinding; import run.halo.app.core.extension.RoleBinding.Subject; import run.halo.app.core.extension.User; +import run.halo.app.core.user.service.RoleService; +import run.halo.app.core.user.service.SignUpData; +import run.halo.app.core.user.service.UserPostCreatingHandler; +import run.halo.app.core.user.service.UserPreCreatingHandler; import run.halo.app.event.user.PasswordChangedEvent; import run.halo.app.extension.Metadata; import run.halo.app.extension.ReactiveExtensionClient; @@ -46,6 +52,7 @@ import run.halo.app.infra.exception.DuplicateNameException; import run.halo.app.infra.exception.UnsatisfiedAttributeValueException; import run.halo.app.infra.exception.UserNotFoundException; +import run.halo.app.plugin.extensionpoint.ExtensionGetter; @ExtendWith(MockitoExtension.class) class UserServiceImplTest { @@ -65,6 +72,9 @@ class UserServiceImplTest { @Mock RoleService roleService; + @Mock + ExtensionGetter extensionGetter; + @InjectMocks UserServiceImpl userService; @@ -305,6 +315,7 @@ void shouldUpdateRoleBindingIfExists() { @Nested class SignUpTest { + @Test void signUpWhenRegistrationNotAllowed() { SystemSetting.User userSetting = new SystemSetting.User(); @@ -354,6 +365,8 @@ void signUpWhenRegistrationUsernameExists() { when(passwordEncoder.encode(eq("fake-password"))).thenReturn("fake-password"); when(client.fetch(eq(User.class), eq("fake-user"))) .thenReturn(Mono.just(createFakeUser("test", "test"))); + when(extensionGetter.getExtensions(UserPreCreatingHandler.class)) + .thenReturn(Flux.empty()); var signUpData = createSignUpData("fake-user", "fake-password"); userService.signUp(signUpData) @@ -382,6 +395,20 @@ void signUpWhenRegistrationSuccessfully() { UserServiceImpl spyUserService = spy(userService); doReturn(Mono.just(fakeUser)).when(spyUserService).grantRoles(eq("fake-user"), anySet()); + when(extensionGetter.getExtensions(UserPreCreatingHandler.class)) + .thenReturn(Flux.just(user -> { + if (user.getMetadata().getAnnotations() == null) { + user.getMetadata().setAnnotations(new HashMap<>()); + } + user.getMetadata().getAnnotations() + .put("pre.creating.handler.handled", "true"); + return Mono.empty(); + })); + when(extensionGetter.getExtensions(UserPostCreatingHandler.class)) + .thenReturn(Flux.just(user -> { + assertEquals(fakeUser, user); + return Mono.empty(); + })); spyUserService.signUp(signUpData) .as(StepVerifier::create) @@ -391,7 +418,10 @@ void signUpWhenRegistrationSuccessfully() { }) .verifyComplete(); - verify(client).create(any(User.class)); + verify(client).create(assertArg(u -> { + var handled = u.getMetadata().getAnnotations().get("pre.creating.handler.handled"); + assertEquals("true", handled); + })); verify(spyUserService).grantRoles(eq("fake-user"), anySet()); }