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