Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support hooking user creation #6945

Merged
merged 1 commit into from
Oct 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading