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.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.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.DuplicateNameException;
|
|||
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 class UserServiceImpl implements UserService {
|
|||
)
|
||||
.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)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
|
@ -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.any;
|
|||
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.verify;
|
|||
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.Role;
|
|||
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.SystemSetting;
|
|||
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 @@ class UserServiceImplTest {
|
|||
|
||||
@Nested
|
||||
class SignUpTest {
|
||||
|
||||
@Test
|
||||
void signUpWhenRegistrationNotAllowed() {
|
||||
SystemSetting.User userSetting = new SystemSetting.User();
|
||||
|
@ -354,6 +365,8 @@ class UserServiceImplTest {
|
|||
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 @@ class UserServiceImplTest {
|
|||
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 @@ class UserServiceImplTest {
|
|||
})
|
||||
.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());
|
||||
}
|
||||
|
Loading…
Reference in New Issue