mirror of https://github.com/halo-dev/halo
Provide an endpoint to update user profile (#3067)
#### What type of PR is this? /kind feature /area core /milestone 2.1.x #### What this PR does / why we need it: Provide an endpoint to update current user profile instead of whole user data. ##### Request example ```bash curl -X 'PUT' \ 'http://localhost:8090/apis/api.console.halo.run/v1alpha1/users/-' \ -H 'accept: */*' \ -H 'Content-Type: */*' \ -d '{ "spec": { "displayName": "JohnNiang", "email": "johnniang@halo.run", "password": "xxx", "registeredAt": "2022-12-19T03:46:54.809770900Z", "twoFactorAuthEnabled": false, "disabled": false }, "status": { "permalink": "http://localhost:8090/authors/admin" }, "apiVersion": "v1alpha1", "kind": "User", "metadata": { "finalizers": [ "user-protection" ], "name": "admin", "annotations": { "rbac.authorization.halo.run/role-names": "[\"super-role\"]" }, "version": 3, "creationTimestamp": "2022-12-19T03:46:54.911951800Z" } }' ``` ##### Response example ```json { "spec": { "displayName": "JohnNiang", "email": "johnniang@halo.run", "password": "{bcrypt}$2a$10$IBV8/q7Q6Fj78Ls5AG1eBO0bCQ.rM6vli5pAVexf/gqu.hNfjJxaq", "registeredAt": "2022-12-19T03:46:54.809770900Z", "twoFactorAuthEnabled": false, "disabled": false }, "status": { "permalink": "http://localhost:8090/authors/admin" }, "apiVersion": "v1alpha1", "kind": "User", "metadata": { "finalizers": [ "user-protection" ], "name": "admin", "annotations": { "rbac.authorization.halo.run/role-names": "[\"super-role\"]" }, "version": 5, "creationTimestamp": "2022-12-19T03:46:54.911951800Z" } } ``` #### Which issue(s) this PR fixes: Fixes https://github.com/halo-dev/halo/issues/3035 #### Does this PR introduce a user-facing change? ```release-note 提供更新当前登录用户信息功能 ```pull/3068/head^2
parent
da55532777
commit
313605d52c
|
@ -16,7 +16,9 @@ import java.util.stream.Collectors;
|
||||||
import org.springdoc.webflux.core.fn.SpringdocRouteBuilder;
|
import org.springdoc.webflux.core.fn.SpringdocRouteBuilder;
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
import org.springframework.lang.NonNull;
|
import org.springframework.lang.NonNull;
|
||||||
|
import org.springframework.security.core.Authentication;
|
||||||
import org.springframework.security.core.context.ReactiveSecurityContextHolder;
|
import org.springframework.security.core.context.ReactiveSecurityContextHolder;
|
||||||
|
import org.springframework.security.core.context.SecurityContext;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
import org.springframework.web.reactive.function.server.RouterFunction;
|
import org.springframework.web.reactive.function.server.RouterFunction;
|
||||||
import org.springframework.web.reactive.function.server.ServerRequest;
|
import org.springframework.web.reactive.function.server.ServerRequest;
|
||||||
|
@ -52,6 +54,12 @@ public class UserEndpoint implements CustomEndpoint {
|
||||||
.description("Get current user detail")
|
.description("Get current user detail")
|
||||||
.tag(tag)
|
.tag(tag)
|
||||||
.response(responseBuilder().implementation(User.class)))
|
.response(responseBuilder().implementation(User.class)))
|
||||||
|
.PUT("/users/-", this::updateProfile,
|
||||||
|
builder -> builder.operationId("UpdateCurrentUser")
|
||||||
|
.description("Update current user profile, but password.")
|
||||||
|
.tag(tag)
|
||||||
|
.requestBody(requestBodyBuilder().required(true).implementation(User.class))
|
||||||
|
.response(responseBuilder().implementation(User.class)))
|
||||||
.POST("/users/{name}/permissions", this::grantPermission,
|
.POST("/users/{name}/permissions", this::grantPermission,
|
||||||
builder -> builder.operationId("GrantPermission")
|
builder -> builder.operationId("GrantPermission")
|
||||||
.description("Grant permissions to user")
|
.description("Grant permissions to user")
|
||||||
|
@ -89,6 +97,32 @@ public class UserEndpoint implements CustomEndpoint {
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Mono<ServerResponse> updateProfile(ServerRequest request) {
|
||||||
|
return ReactiveSecurityContextHolder.getContext()
|
||||||
|
.map(SecurityContext::getAuthentication)
|
||||||
|
.map(Authentication::getName)
|
||||||
|
.flatMap(currentUserName -> client.get(User.class, currentUserName))
|
||||||
|
.flatMap(currentUser -> request.bodyToMono(User.class)
|
||||||
|
.filter(user ->
|
||||||
|
Objects.equals(user.getMetadata().getName(),
|
||||||
|
currentUser.getMetadata().getName()))
|
||||||
|
.switchIfEmpty(
|
||||||
|
Mono.error(() -> new ServerWebInputException("Username didn't match.")))
|
||||||
|
.map(user -> {
|
||||||
|
var spec = currentUser.getSpec();
|
||||||
|
var newSpec = user.getSpec();
|
||||||
|
spec.setAvatar(newSpec.getAvatar());
|
||||||
|
spec.setBio(newSpec.getBio());
|
||||||
|
spec.setDisplayName(newSpec.getDisplayName());
|
||||||
|
spec.setTwoFactorAuthEnabled(newSpec.getTwoFactorAuthEnabled());
|
||||||
|
spec.setEmail(newSpec.getEmail());
|
||||||
|
spec.setPhone(newSpec.getPhone());
|
||||||
|
return currentUser;
|
||||||
|
}))
|
||||||
|
.flatMap(client::update)
|
||||||
|
.flatMap(updatedUser -> ServerResponse.ok().bodyValue(updatedUser));
|
||||||
|
}
|
||||||
|
|
||||||
Mono<ServerResponse> changePassword(ServerRequest request) {
|
Mono<ServerResponse> changePassword(ServerRequest request) {
|
||||||
final var nameInPath = request.pathVariable("name");
|
final var nameInPath = request.pathVariable("name");
|
||||||
return ReactiveSecurityContextHolder.getContext()
|
return ReactiveSecurityContextHolder.getContext()
|
||||||
|
|
|
@ -31,7 +31,7 @@ rules:
|
||||||
- apiGroups: [ "api.console.halo.run" ]
|
- apiGroups: [ "api.console.halo.run" ]
|
||||||
resources: [ "users" ]
|
resources: [ "users" ]
|
||||||
resourceNames: [ "-" ]
|
resourceNames: [ "-" ]
|
||||||
verbs: [ "list", "get" ]
|
verbs: [ "get", "update" ]
|
||||||
---
|
---
|
||||||
apiVersion: v1alpha1
|
apiVersion: v1alpha1
|
||||||
kind: "Role"
|
kind: "Role"
|
||||||
|
|
|
@ -98,6 +98,65 @@ class UserEndpointTest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nested
|
||||||
|
@DisplayName("UpdateProfile")
|
||||||
|
class UpdateProfileTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldUpdateProfileCorrectly() {
|
||||||
|
var currentUser = createUser("fake-user");
|
||||||
|
var updatedUser = createUser("fake-user");
|
||||||
|
var requestUser = createUser("fake-user");
|
||||||
|
|
||||||
|
when(client.get(User.class, "fake-user")).thenReturn(Mono.just(currentUser));
|
||||||
|
when(client.update(currentUser)).thenReturn(Mono.just(updatedUser));
|
||||||
|
|
||||||
|
webClient.put().uri("/apis/api.console.halo.run/v1alpha1/users/-")
|
||||||
|
.bodyValue(requestUser)
|
||||||
|
.exchange()
|
||||||
|
.expectStatus().isOk()
|
||||||
|
.expectBody(User.class)
|
||||||
|
.isEqualTo(updatedUser);
|
||||||
|
|
||||||
|
verify(client).get(User.class, "fake-user");
|
||||||
|
verify(client).update(currentUser);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldGetErrorIfUsernameMismatch() {
|
||||||
|
var currentUser = createUser("fake-user");
|
||||||
|
var updatedUser = createUser("fake-user");
|
||||||
|
var requestUser = createUser("another-fake-user");
|
||||||
|
|
||||||
|
when(client.get(User.class, "fake-user")).thenReturn(Mono.just(currentUser));
|
||||||
|
when(client.update(currentUser)).thenReturn(Mono.just(updatedUser));
|
||||||
|
|
||||||
|
webClient.put().uri("/apis/api.console.halo.run/v1alpha1/users/-")
|
||||||
|
.bodyValue(requestUser)
|
||||||
|
.exchange()
|
||||||
|
.expectStatus().isBadRequest();
|
||||||
|
|
||||||
|
verify(client).get(User.class, "fake-user");
|
||||||
|
verify(client, never()).update(currentUser);
|
||||||
|
}
|
||||||
|
|
||||||
|
User createUser(String name) {
|
||||||
|
var spec = new User.UserSpec();
|
||||||
|
spec.setEmail("hi@halo.run");
|
||||||
|
spec.setBio("Fake bio");
|
||||||
|
spec.setDisplayName("Faker");
|
||||||
|
spec.setPassword("fake-password");
|
||||||
|
|
||||||
|
var metadata = new Metadata();
|
||||||
|
metadata.setName(name);
|
||||||
|
|
||||||
|
var user = new User();
|
||||||
|
user.setSpec(spec);
|
||||||
|
user.setMetadata(metadata);
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Nested
|
@Nested
|
||||||
@DisplayName("ChangePassword")
|
@DisplayName("ChangePassword")
|
||||||
class ChangePasswordTest {
|
class ChangePasswordTest {
|
||||||
|
|
Loading…
Reference in New Issue