mirror of https://github.com/halo-dev/halo
Support hooking user creation (#6945)
#### What type of PR is this? /kind feature /area core /milestone 2.20.x #### What this PR does / why we need it: This PR adds support for hooking user creating. Plugin developers can define extension points of `UserPreCreatingHandler` and `UserPostCreatingHandler` to do something else. #### Does this PR introduce a user-facing change? ```release-note 支持在插件中定义用户创建的前置和后置处理器 ```pull/6952/head
parent
180b6b2b87
commit
a0b352ac2d
|
@ -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<Void> postCreating(User user);
|
||||||
|
|
||||||
|
}
|
|
@ -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<Void> preCreating(User user);
|
||||||
|
|
||||||
|
}
|
|
@ -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.ExtensionUtil.defaultSort;
|
||||||
import static run.halo.app.extension.index.query.QueryFactory.equal;
|
import static run.halo.app.extension.index.query.QueryFactory.equal;
|
||||||
|
@ -25,6 +25,12 @@ import reactor.util.retry.Retry;
|
||||||
import run.halo.app.core.extension.Role;
|
import run.halo.app.core.extension.Role;
|
||||||
import run.halo.app.core.extension.RoleBinding;
|
import run.halo.app.core.extension.RoleBinding;
|
||||||
import run.halo.app.core.extension.User;
|
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.event.user.PasswordChangedEvent;
|
||||||
import run.halo.app.extension.ListOptions;
|
import run.halo.app.extension.ListOptions;
|
||||||
import run.halo.app.extension.Metadata;
|
import run.halo.app.extension.Metadata;
|
||||||
|
@ -38,6 +44,7 @@ import run.halo.app.infra.exception.DuplicateNameException;
|
||||||
import run.halo.app.infra.exception.EmailVerificationFailed;
|
import run.halo.app.infra.exception.EmailVerificationFailed;
|
||||||
import run.halo.app.infra.exception.UnsatisfiedAttributeValueException;
|
import run.halo.app.infra.exception.UnsatisfiedAttributeValueException;
|
||||||
import run.halo.app.infra.exception.UserNotFoundException;
|
import run.halo.app.infra.exception.UserNotFoundException;
|
||||||
|
import run.halo.app.plugin.extensionpoint.ExtensionGetter;
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
|
@ -57,6 +64,8 @@ public class UserServiceImpl implements UserService {
|
||||||
|
|
||||||
private final EmailVerificationService emailVerificationService;
|
private final EmailVerificationService emailVerificationService;
|
||||||
|
|
||||||
|
private final ExtensionGetter extensionGetter;
|
||||||
|
|
||||||
private Clock clock = Clock.systemUTC();
|
private Clock clock = Clock.systemUTC();
|
||||||
|
|
||||||
void setClock(Clock clock) {
|
void setClock(Clock clock) {
|
||||||
|
@ -222,13 +231,20 @@ public class UserServiceImpl implements UserService {
|
||||||
)
|
)
|
||||||
.then();
|
.then();
|
||||||
})
|
})
|
||||||
.then(Mono.defer(() -> client.create(user)
|
.then(extensionGetter.getExtensions(UserPreCreatingHandler.class)
|
||||||
.flatMap(newUser -> grantRoles(user.getMetadata().getName(), roleNames)
|
.concatMap(handler -> handler.preCreating(user))
|
||||||
.retryWhen(
|
.then(Mono.defer(() -> client.create(user)
|
||||||
Retry.backoff(5, Duration.ofMillis(100))
|
.flatMap(newUser -> grantRoles(user.getMetadata().getName(), roleNames)
|
||||||
|
.retryWhen(Retry.backoff(5, Duration.ofMillis(100))
|
||||||
.filter(OptimisticLockingFailureException.class::isInstance)
|
.filter(OptimisticLockingFailureException.class::isInstance)
|
||||||
|
)
|
||||||
)
|
)
|
||||||
))
|
))
|
||||||
|
.flatMap(createdUser -> extensionGetter.getExtensions(UserPostCreatingHandler.class)
|
||||||
|
.concatMap(handler -> handler.postCreating(createdUser))
|
||||||
|
.then()
|
||||||
|
.thenReturn(createdUser)
|
||||||
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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.assertj.core.api.Assertions.assertThat;
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
@ -8,6 +8,7 @@ import static org.mockito.ArgumentMatchers.any;
|
||||||
import static org.mockito.ArgumentMatchers.anySet;
|
import static org.mockito.ArgumentMatchers.anySet;
|
||||||
import static org.mockito.ArgumentMatchers.anyString;
|
import static org.mockito.ArgumentMatchers.anyString;
|
||||||
import static org.mockito.ArgumentMatchers.argThat;
|
import static org.mockito.ArgumentMatchers.argThat;
|
||||||
|
import static org.mockito.ArgumentMatchers.assertArg;
|
||||||
import static org.mockito.ArgumentMatchers.eq;
|
import static org.mockito.ArgumentMatchers.eq;
|
||||||
import static org.mockito.ArgumentMatchers.isA;
|
import static org.mockito.ArgumentMatchers.isA;
|
||||||
import static org.mockito.Mockito.doReturn;
|
import static org.mockito.Mockito.doReturn;
|
||||||
|
@ -19,6 +20,7 @@ import static org.mockito.Mockito.verify;
|
||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
import static run.halo.app.extension.GroupVersionKind.fromExtension;
|
import static run.halo.app.extension.GroupVersionKind.fromExtension;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import org.junit.jupiter.api.DisplayName;
|
import org.junit.jupiter.api.DisplayName;
|
||||||
import org.junit.jupiter.api.Nested;
|
import org.junit.jupiter.api.Nested;
|
||||||
|
@ -37,6 +39,10 @@ import run.halo.app.core.extension.Role;
|
||||||
import run.halo.app.core.extension.RoleBinding;
|
import run.halo.app.core.extension.RoleBinding;
|
||||||
import run.halo.app.core.extension.RoleBinding.Subject;
|
import run.halo.app.core.extension.RoleBinding.Subject;
|
||||||
import run.halo.app.core.extension.User;
|
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.event.user.PasswordChangedEvent;
|
||||||
import run.halo.app.extension.Metadata;
|
import run.halo.app.extension.Metadata;
|
||||||
import run.halo.app.extension.ReactiveExtensionClient;
|
import run.halo.app.extension.ReactiveExtensionClient;
|
||||||
|
@ -46,6 +52,7 @@ import run.halo.app.infra.SystemSetting;
|
||||||
import run.halo.app.infra.exception.DuplicateNameException;
|
import run.halo.app.infra.exception.DuplicateNameException;
|
||||||
import run.halo.app.infra.exception.UnsatisfiedAttributeValueException;
|
import run.halo.app.infra.exception.UnsatisfiedAttributeValueException;
|
||||||
import run.halo.app.infra.exception.UserNotFoundException;
|
import run.halo.app.infra.exception.UserNotFoundException;
|
||||||
|
import run.halo.app.plugin.extensionpoint.ExtensionGetter;
|
||||||
|
|
||||||
@ExtendWith(MockitoExtension.class)
|
@ExtendWith(MockitoExtension.class)
|
||||||
class UserServiceImplTest {
|
class UserServiceImplTest {
|
||||||
|
@ -65,6 +72,9 @@ class UserServiceImplTest {
|
||||||
@Mock
|
@Mock
|
||||||
RoleService roleService;
|
RoleService roleService;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
ExtensionGetter extensionGetter;
|
||||||
|
|
||||||
@InjectMocks
|
@InjectMocks
|
||||||
UserServiceImpl userService;
|
UserServiceImpl userService;
|
||||||
|
|
||||||
|
@ -305,6 +315,7 @@ class UserServiceImplTest {
|
||||||
|
|
||||||
@Nested
|
@Nested
|
||||||
class SignUpTest {
|
class SignUpTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void signUpWhenRegistrationNotAllowed() {
|
void signUpWhenRegistrationNotAllowed() {
|
||||||
SystemSetting.User userSetting = new SystemSetting.User();
|
SystemSetting.User userSetting = new SystemSetting.User();
|
||||||
|
@ -354,6 +365,8 @@ class UserServiceImplTest {
|
||||||
when(passwordEncoder.encode(eq("fake-password"))).thenReturn("fake-password");
|
when(passwordEncoder.encode(eq("fake-password"))).thenReturn("fake-password");
|
||||||
when(client.fetch(eq(User.class), eq("fake-user")))
|
when(client.fetch(eq(User.class), eq("fake-user")))
|
||||||
.thenReturn(Mono.just(createFakeUser("test", "test")));
|
.thenReturn(Mono.just(createFakeUser("test", "test")));
|
||||||
|
when(extensionGetter.getExtensions(UserPreCreatingHandler.class))
|
||||||
|
.thenReturn(Flux.empty());
|
||||||
|
|
||||||
var signUpData = createSignUpData("fake-user", "fake-password");
|
var signUpData = createSignUpData("fake-user", "fake-password");
|
||||||
userService.signUp(signUpData)
|
userService.signUp(signUpData)
|
||||||
|
@ -382,6 +395,20 @@ class UserServiceImplTest {
|
||||||
UserServiceImpl spyUserService = spy(userService);
|
UserServiceImpl spyUserService = spy(userService);
|
||||||
doReturn(Mono.just(fakeUser)).when(spyUserService).grantRoles(eq("fake-user"),
|
doReturn(Mono.just(fakeUser)).when(spyUserService).grantRoles(eq("fake-user"),
|
||||||
anySet());
|
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)
|
spyUserService.signUp(signUpData)
|
||||||
.as(StepVerifier::create)
|
.as(StepVerifier::create)
|
||||||
|
@ -391,7 +418,10 @@ class UserServiceImplTest {
|
||||||
})
|
})
|
||||||
.verifyComplete();
|
.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());
|
verify(spyUserService).grantRoles(eq("fake-user"), anySet());
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue