Skip to content

Commit

Permalink
Support hooking user creation (#6945)
Browse files Browse the repository at this point in the history
#### 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
支持在插件中定义用户创建的前置和后置处理器
```
  • Loading branch information
JohnNiang authored Oct 25, 2024
1 parent 180b6b2 commit a0b352a
Show file tree
Hide file tree
Showing 4 changed files with 99 additions and 7 deletions.
Original file line number Diff line number Diff line change
@@ -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);

}
Original file line number Diff line number Diff line change
@@ -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);

}
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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;
Expand All @@ -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
Expand All @@ -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) {
Expand Down Expand Up @@ -222,13 +231,20 @@ public Mono<User> createUser(User user, Set<String> 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)
)
);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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;
Expand All @@ -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;
Expand All @@ -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;
Expand All @@ -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 {
Expand All @@ -65,6 +72,9 @@ class UserServiceImplTest {
@Mock
RoleService roleService;

@Mock
ExtensionGetter extensionGetter;

@InjectMocks
UserServiceImpl userService;

Expand Down Expand Up @@ -305,6 +315,7 @@ void shouldUpdateRoleBindingIfExists() {

@Nested
class SignUpTest {

@Test
void signUpWhenRegistrationNotAllowed() {
SystemSetting.User userSetting = new SystemSetting.User();
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand All @@ -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());
}

Expand Down

0 comments on commit a0b352a

Please sign in to comment.