mirror of https://github.com/halo-dev/halo
Fix the problem that roles could not be granted sometimes (#6471)
#### What type of PR is this? /kind improvement /area core #### What this PR does / why we need it: This PR refactors searching roles by using index mechanism to speed up every request and fix the problem of not being able to grant roles to users sometimes. #### Which issue(s) this PR fixes: Fixes #5807 Fixes https://github.com/halo-dev/halo/issues/4954 Fixes https://github.com/halo-dev/halo/issues/5057 #### Does this PR introduce a user-facing change? ```release-note 修复有时无法给用户赋权限的问题 ```pull/6486/head
parent
7ba5fc671f
commit
3a782be607
|
@ -22881,14 +22881,12 @@
|
|||
}
|
||||
},
|
||||
"roles": {
|
||||
"uniqueItems": true,
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/Role"
|
||||
}
|
||||
},
|
||||
"uiPermissions": {
|
||||
"uniqueItems": true,
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
|
|
|
@ -6236,14 +6236,12 @@
|
|||
}
|
||||
},
|
||||
"roles": {
|
||||
"uniqueItems": true,
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/Role"
|
||||
}
|
||||
},
|
||||
"uiPermissions": {
|
||||
"uniqueItems": true,
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
|
|
|
@ -13,6 +13,7 @@ import lombok.Data;
|
|||
import lombok.EqualsAndHashCode;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.ToString;
|
||||
import org.springframework.util.StringUtils;
|
||||
import run.halo.app.extension.AbstractExtension;
|
||||
import run.halo.app.extension.ExtensionOperator;
|
||||
import run.halo.app.extension.GVK;
|
||||
|
@ -118,6 +119,14 @@ public class RoleBinding extends AbstractExtension {
|
|||
&& User.GROUP.equals(subject.apiGroup)
|
||||
&& usernames.contains(subject.getName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
if (StringUtils.hasText(apiGroup)) {
|
||||
return apiGroup + "/" + kind + "/" + name;
|
||||
}
|
||||
return kind + "/" + name;
|
||||
}
|
||||
}
|
||||
|
||||
public static RoleBinding create(String username, String roleName) {
|
||||
|
|
|
@ -46,6 +46,8 @@ public class User extends AbstractExtension {
|
|||
|
||||
public static final String HIDDEN_USER_LABEL = "halo.run/hidden-user";
|
||||
|
||||
public static final String REQUEST_TO_UPDATE = "halo.run/request-to-update";
|
||||
|
||||
@Schema(requiredMode = REQUIRED)
|
||||
private UserSpec spec = new UserSpec();
|
||||
|
||||
|
|
|
@ -3,6 +3,9 @@ package run.halo.app.extension;
|
|||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import run.halo.app.extension.index.query.Query;
|
||||
import run.halo.app.extension.index.query.QueryFactory;
|
||||
|
||||
public enum ExtensionUtil {
|
||||
;
|
||||
|
@ -34,4 +37,25 @@ public enum ExtensionUtil {
|
|||
return removed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Query for not deleting.
|
||||
*
|
||||
* @return Query
|
||||
*/
|
||||
public static Query notDeleting() {
|
||||
return QueryFactory.isNull("metadata.deletionTimestamp");
|
||||
}
|
||||
|
||||
/**
|
||||
* Default sort by creation timestamp desc and name asc.
|
||||
*
|
||||
* @return Sort
|
||||
*/
|
||||
public static Sort defaultSort() {
|
||||
return Sort.by(
|
||||
Sort.Order.desc("metadata.creationTimestamp"),
|
||||
Sort.Order.asc("metadata.name")
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -10,9 +10,9 @@ import static org.springdoc.core.fn.builders.requestbody.Builder.requestBodyBuil
|
|||
import static org.springdoc.core.fn.builders.schema.Builder.schemaBuilder;
|
||||
import static org.springframework.web.reactive.function.server.RequestPredicates.contentType;
|
||||
import static run.halo.app.extension.ListResult.generateGenericClass;
|
||||
import static run.halo.app.extension.index.query.QueryFactory.and;
|
||||
import static run.halo.app.extension.index.query.QueryFactory.contains;
|
||||
import static run.halo.app.extension.index.query.QueryFactory.equal;
|
||||
import static run.halo.app.extension.index.query.QueryFactory.in;
|
||||
import static run.halo.app.extension.index.query.QueryFactory.or;
|
||||
import static run.halo.app.extension.router.QueryParamBuildUtil.sortParameter;
|
||||
import static run.halo.app.extension.router.selector.SelectorUtil.labelAndFieldSelectorToListOptions;
|
||||
|
@ -28,13 +28,15 @@ import io.swagger.v3.oas.annotations.media.ArraySchema;
|
|||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import java.security.Principal;
|
||||
import java.time.Duration;
|
||||
import java.util.Collections;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
@ -83,8 +85,6 @@ import run.halo.app.extension.MetadataUtil;
|
|||
import run.halo.app.extension.PageRequestImpl;
|
||||
import run.halo.app.extension.ReactiveExtensionClient;
|
||||
import run.halo.app.extension.router.IListRequest;
|
||||
import run.halo.app.extension.router.selector.FieldSelector;
|
||||
import run.halo.app.infra.AnonymousUserConst;
|
||||
import run.halo.app.infra.SystemConfigurableEnvironmentFetcher;
|
||||
import run.halo.app.infra.SystemSetting;
|
||||
import run.halo.app.infra.ValidationUtils;
|
||||
|
@ -457,11 +457,14 @@ public class UserEndpoint implements CustomEndpoint {
|
|||
private Mono<ServerResponse> getUserByName(ServerRequest request) {
|
||||
final var name = request.pathVariable("name");
|
||||
return userService.getUser(name)
|
||||
.flatMap(this::toDetailedUser)
|
||||
.flatMap(user -> ServerResponse.ok()
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.bodyValue(user)
|
||||
);
|
||||
.flatMap(user -> roleService.getRolesByUsername(name)
|
||||
.collectList()
|
||||
.flatMap(roleNames -> roleService.list(new HashSet<>(roleNames), true)
|
||||
.collectList()
|
||||
.map(roles -> new DetailedUser(user, roles))
|
||||
)
|
||||
)
|
||||
.flatMap(detailedUser -> ServerResponse.ok().bodyValue(detailedUser));
|
||||
}
|
||||
|
||||
record CreateUserRequest(@Schema(requiredMode = REQUIRED) String name,
|
||||
|
@ -600,33 +603,16 @@ public class UserEndpoint implements CustomEndpoint {
|
|||
Mono<ServerResponse> me(ServerRequest request) {
|
||||
return ReactiveSecurityContextHolder.getContext()
|
||||
.map(SecurityContext::getAuthentication)
|
||||
.filter(obj -> !(obj instanceof TwoFactorAuthentication))
|
||||
.map(Authentication::getName)
|
||||
.defaultIfEmpty(AnonymousUserConst.PRINCIPAL)
|
||||
.flatMap(userService::getUser)
|
||||
.flatMap(this::toDetailedUser)
|
||||
.flatMap(user -> ServerResponse.ok()
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.bodyValue(user));
|
||||
}
|
||||
|
||||
private Mono<DetailedUser> toDetailedUser(User user) {
|
||||
Set<String> roleNames = roleNames(user);
|
||||
return roleService.list(roleNames)
|
||||
.collectList()
|
||||
.map(roles -> new DetailedUser(user, roles))
|
||||
.defaultIfEmpty(new DetailedUser(user, List.of()));
|
||||
}
|
||||
|
||||
Set<String> roleNames(User user) {
|
||||
Assert.notNull(user, "User must not be null");
|
||||
Map<String, String> annotations = MetadataUtil.nullSafeAnnotations(user);
|
||||
String roleNamesJson = annotations.get(User.ROLE_NAMES_ANNO);
|
||||
if (StringUtils.isBlank(roleNamesJson)) {
|
||||
return Set.of();
|
||||
}
|
||||
return JsonUtils.jsonToObject(roleNamesJson, new TypeReference<>() {
|
||||
});
|
||||
.filter(auth -> !(auth instanceof TwoFactorAuthentication))
|
||||
.flatMap(auth -> userService.getUser(auth.getName())
|
||||
.flatMap(user -> {
|
||||
var roleNames = authoritiesToRoles(auth.getAuthorities());
|
||||
return roleService.list(roleNames, true)
|
||||
.collectList()
|
||||
.map(roles -> new DetailedUser(user, roles));
|
||||
})
|
||||
)
|
||||
.flatMap(detailedUser -> ServerResponse.ok().bodyValue(detailedUser));
|
||||
}
|
||||
|
||||
record DetailedUser(@Schema(requiredMode = REQUIRED) User user,
|
||||
|
@ -649,85 +635,55 @@ public class UserEndpoint implements CustomEndpoint {
|
|||
|
||||
@NonNull
|
||||
private Mono<ServerResponse> getUserPermission(ServerRequest request) {
|
||||
var name = request.pathVariable("name");
|
||||
Mono<UserPermission> userPermission;
|
||||
if (SELF_USER.equals(name)) {
|
||||
userPermission = ReactiveSecurityContextHolder.getContext()
|
||||
.map(SecurityContext::getAuthentication)
|
||||
.flatMap(auth -> {
|
||||
var roleNames = authoritiesToRoles(auth.getAuthorities());
|
||||
var up = new UserPermission();
|
||||
var roles = roleService.list(roleNames)
|
||||
.collect(Collectors.toSet())
|
||||
.doOnNext(up::setRoles)
|
||||
.then();
|
||||
var permissions = roleService.listPermissions(roleNames)
|
||||
.distinct()
|
||||
.collectList()
|
||||
.doOnNext(up::setPermissions)
|
||||
.doOnNext(perms -> {
|
||||
var uiPermissions = uiPermissions(new HashSet<>(perms));
|
||||
up.setUiPermissions(uiPermissions);
|
||||
})
|
||||
.then();
|
||||
return roles.and(permissions).thenReturn(up);
|
||||
var username = request.pathVariable("name");
|
||||
return Mono.defer(() -> {
|
||||
if (SELF_USER.equals(username)) {
|
||||
return ReactiveSecurityContextHolder.getContext()
|
||||
.map(SecurityContext::getAuthentication)
|
||||
.map(auth -> authoritiesToRoles(auth.getAuthorities()));
|
||||
}
|
||||
return roleService.getRolesByUsername(username)
|
||||
.collect(Collectors.toCollection(LinkedHashSet::new));
|
||||
}).flatMap(roleNames -> {
|
||||
var up = new UserPermission();
|
||||
var setRoles = roleService.list(roleNames, true)
|
||||
.distinct()
|
||||
.collectSortedList()
|
||||
.doOnNext(up::setRoles);
|
||||
var setPerms = roleService.listPermissions(roleNames)
|
||||
.distinct()
|
||||
.collectSortedList()
|
||||
.doOnNext(permissions -> {
|
||||
up.setPermissions(permissions);
|
||||
up.setUiPermissions(uiPermissions(permissions));
|
||||
});
|
||||
} else {
|
||||
// get roles from username
|
||||
userPermission = userService.listRoles(name)
|
||||
.collect(Collectors.toSet())
|
||||
.flatMap(roles -> {
|
||||
var up = new UserPermission();
|
||||
var setRoles = Mono.fromRunnable(() -> up.setRoles(roles)).then();
|
||||
var roleNames = roles.stream()
|
||||
.map(role -> role.getMetadata().getName())
|
||||
.collect(Collectors.toSet());
|
||||
var setPermissions = roleService.listPermissions(roleNames)
|
||||
.distinct()
|
||||
.collectList()
|
||||
.doOnNext(up::setPermissions)
|
||||
.doOnNext(perms -> {
|
||||
var uiPermissions = uiPermissions(new HashSet<>(perms));
|
||||
up.setUiPermissions(uiPermissions);
|
||||
})
|
||||
.then();
|
||||
return setRoles.and(setPermissions).thenReturn(up);
|
||||
});
|
||||
}
|
||||
|
||||
return ServerResponse.ok().body(userPermission, UserPermission.class);
|
||||
return Mono.when(setRoles, setPerms).thenReturn(up);
|
||||
}).flatMap(userPermission -> ServerResponse.ok().bodyValue(userPermission));
|
||||
}
|
||||
|
||||
private Set<String> uiPermissions(Set<Role> roles) {
|
||||
private List<String> uiPermissions(Collection<Role> roles) {
|
||||
if (CollectionUtils.isEmpty(roles)) {
|
||||
return Collections.emptySet();
|
||||
return List.of();
|
||||
}
|
||||
return roles.stream()
|
||||
.<Set<String>>map(role -> {
|
||||
var annotations = role.getMetadata().getAnnotations();
|
||||
if (annotations == null) {
|
||||
return Set.of();
|
||||
}
|
||||
var uiPermissionsJson = annotations.get(Role.UI_PERMISSIONS_ANNO);
|
||||
if (StringUtils.isBlank(uiPermissionsJson)) {
|
||||
return Set.of();
|
||||
}
|
||||
return JsonUtils.jsonToObject(uiPermissionsJson,
|
||||
new TypeReference<LinkedHashSet<String>>() {
|
||||
});
|
||||
})
|
||||
.flatMap(Set::stream)
|
||||
.collect(Collectors.toSet());
|
||||
var uiPerms = new LinkedList<String>();
|
||||
roles.forEach(role -> Optional.ofNullable(role.getMetadata().getAnnotations())
|
||||
.map(annotations -> annotations.get(Role.UI_PERMISSIONS_ANNO))
|
||||
.filter(StringUtils::isNotBlank)
|
||||
.map(json -> JsonUtils.jsonToObject(json, new TypeReference<Set<String>>() {
|
||||
}))
|
||||
.ifPresent(uiPerms::addAll)
|
||||
);
|
||||
return uiPerms.stream().distinct().sorted().toList();
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class UserPermission {
|
||||
@Schema(requiredMode = REQUIRED)
|
||||
private Set<Role> roles;
|
||||
private List<Role> roles;
|
||||
@Schema(requiredMode = REQUIRED)
|
||||
private List<Role> permissions;
|
||||
@Schema(requiredMode = REQUIRED)
|
||||
private Set<String> uiPermissions;
|
||||
private List<String> uiPermissions;
|
||||
|
||||
}
|
||||
|
||||
|
@ -767,29 +723,23 @@ public class UserEndpoint implements CustomEndpoint {
|
|||
* Converts query parameters to list options.
|
||||
*/
|
||||
public ListOptions toListOptions() {
|
||||
var listOptions =
|
||||
var defaultListOptions =
|
||||
labelAndFieldSelectorToListOptions(getLabelSelector(), getFieldSelector());
|
||||
|
||||
var fieldQuery = listOptions.getFieldSelector().query();
|
||||
if (StringUtils.isNotBlank(getKeyword())) {
|
||||
fieldQuery = and(
|
||||
fieldQuery,
|
||||
or(
|
||||
contains("spec.displayName", getKeyword()),
|
||||
equal("metadata.name", getKeyword())
|
||||
)
|
||||
);
|
||||
}
|
||||
var builder = ListOptions.builder(defaultListOptions);
|
||||
|
||||
if (StringUtils.isNotBlank(getRole())) {
|
||||
fieldQuery = and(
|
||||
fieldQuery,
|
||||
equal(User.USER_RELATED_ROLES_INDEX, getRole())
|
||||
);
|
||||
}
|
||||
Optional.ofNullable(getKeyword())
|
||||
.filter(StringUtils::isNotBlank)
|
||||
.ifPresent(keyword -> builder.andQuery(or(
|
||||
contains("spec.displayName", keyword),
|
||||
equal("metadata.name", keyword)
|
||||
)));
|
||||
|
||||
listOptions.setFieldSelector(FieldSelector.of(fieldQuery));
|
||||
return listOptions;
|
||||
Optional.ofNullable(getRole())
|
||||
.filter(StringUtils::isNotBlank)
|
||||
.ifPresent(role -> builder.andQuery(in(User.USER_RELATED_ROLES_INDEX, role)));
|
||||
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
public static void buildParameters(Builder builder) {
|
||||
|
@ -829,17 +779,28 @@ public class UserEndpoint implements CustomEndpoint {
|
|||
}
|
||||
|
||||
private Mono<ListResult<ListedUser>> toListedUser(ListResult<User> listResult) {
|
||||
return Flux.fromStream(listResult.get())
|
||||
.concatMap(user -> {
|
||||
Set<String> roleNames = roleNames(user);
|
||||
return roleService.list(roleNames)
|
||||
.collectList()
|
||||
.map(roles -> new ListedUser(user, roles))
|
||||
.defaultIfEmpty(new ListedUser(user, List.of()));
|
||||
})
|
||||
.collectList()
|
||||
.map(items -> convertFrom(listResult, items))
|
||||
.defaultIfEmpty(convertFrom(listResult, List.of()));
|
||||
var usernames = listResult.getItems().stream()
|
||||
.map(user -> user.getMetadata().getName())
|
||||
.collect(Collectors.toList());
|
||||
return roleService.getRolesByUsernames(usernames)
|
||||
.flatMap(usernameRolesMap -> {
|
||||
var allRoleNames = new HashSet<String>();
|
||||
usernameRolesMap.values().forEach(allRoleNames::addAll);
|
||||
return roleService.list(allRoleNames)
|
||||
.collectMap(role -> role.getMetadata().getName())
|
||||
.map(roleMap -> {
|
||||
var listedUsers = listResult.getItems().stream()
|
||||
.map(user -> {
|
||||
var username = user.getMetadata().getName();
|
||||
var roles = Optional.ofNullable(usernameRolesMap.get(username))
|
||||
.map(roleNames -> roleNames.stream().map(roleMap::get).toList())
|
||||
.orElseGet(List::of);
|
||||
return new ListedUser(user, roles);
|
||||
})
|
||||
.toList();
|
||||
return convertFrom(listResult, listedUsers);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
<T> ListResult<T> convertFrom(ListResult<?> listResult, List<T> items) {
|
||||
|
|
|
@ -1,82 +0,0 @@
|
|||
package run.halo.app.core.extension.reconciler;
|
||||
|
||||
import static run.halo.app.core.extension.RoleBinding.containsUser;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.data.util.Lazy;
|
||||
import org.springframework.stereotype.Component;
|
||||
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.extension.ExtensionClient;
|
||||
import run.halo.app.extension.controller.Controller;
|
||||
import run.halo.app.extension.controller.ControllerBuilder;
|
||||
import run.halo.app.extension.controller.Reconciler;
|
||||
import run.halo.app.extension.controller.Reconciler.Request;
|
||||
import run.halo.app.infra.utils.JsonUtils;
|
||||
|
||||
@Slf4j
|
||||
@Component
|
||||
public class RoleBindingReconciler implements Reconciler<Request> {
|
||||
|
||||
private final ExtensionClient client;
|
||||
|
||||
public RoleBindingReconciler(ExtensionClient client) {
|
||||
this.client = client;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result reconcile(Request request) {
|
||||
client.fetch(RoleBinding.class, request.name()).ifPresent(roleBinding -> {
|
||||
// get all usernames;
|
||||
var usernames = roleBinding.getSubjects().stream()
|
||||
.filter(subject -> User.KIND.equals(subject.getKind()))
|
||||
.map(Subject::getName)
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
// get all role-bindings lazily
|
||||
var bindings =
|
||||
Lazy.of(() -> client.list(RoleBinding.class, containsUser(usernames), null));
|
||||
|
||||
usernames.forEach(username -> {
|
||||
var roleNames = bindings.get().stream()
|
||||
.filter(containsUser(username))
|
||||
.map(RoleBinding::getRoleRef)
|
||||
.filter(roleRef -> Objects.equals(roleRef.getKind(), Role.KIND))
|
||||
.map(RoleBinding.RoleRef::getName)
|
||||
.sorted()
|
||||
// we have to use LinkedHashSet below to make sure the sorted above functional
|
||||
.collect(Collectors.toCollection(LinkedHashSet::new));
|
||||
// we should update the role names even if the role names are empty
|
||||
client.fetch(User.class, username).ifPresent(user -> {
|
||||
var annotations = user.getMetadata().getAnnotations();
|
||||
if (annotations == null) {
|
||||
annotations = new HashMap<>();
|
||||
}
|
||||
var oldAnnotations = Map.copyOf(annotations);
|
||||
annotations.put(User.ROLE_NAMES_ANNO, JsonUtils.objectToJson(roleNames));
|
||||
user.getMetadata().setAnnotations(annotations);
|
||||
if (!Objects.deepEquals(oldAnnotations, annotations)) {
|
||||
// update user
|
||||
client.update(user);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
return new Result(false, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Controller setupWith(ControllerBuilder builder) {
|
||||
return builder
|
||||
.extension(new RoleBinding())
|
||||
.build();
|
||||
}
|
||||
|
||||
}
|
|
@ -1,20 +1,24 @@
|
|||
package run.halo.app.core.extension.reconciler;
|
||||
|
||||
import static run.halo.app.core.extension.User.GROUP;
|
||||
import static run.halo.app.core.extension.User.KIND;
|
||||
import static run.halo.app.extension.ExtensionUtil.addFinalizers;
|
||||
import static run.halo.app.extension.ExtensionUtil.defaultSort;
|
||||
import static run.halo.app.extension.ExtensionUtil.isDeleted;
|
||||
import static run.halo.app.extension.ExtensionUtil.removeFinalizers;
|
||||
import static run.halo.app.extension.index.query.QueryFactory.equal;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.HashSet;
|
||||
import java.time.Duration;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.retry.support.RetryTemplate;
|
||||
import org.springframework.stereotype.Component;
|
||||
import run.halo.app.core.extension.Role;
|
||||
import run.halo.app.core.extension.RoleBinding;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
import run.halo.app.core.extension.User;
|
||||
import run.halo.app.core.extension.UserConnection;
|
||||
import run.halo.app.core.extension.attachment.Attachment;
|
||||
|
@ -22,8 +26,7 @@ import run.halo.app.core.extension.service.AttachmentService;
|
|||
import run.halo.app.core.extension.service.RoleService;
|
||||
import run.halo.app.core.extension.service.UserService;
|
||||
import run.halo.app.extension.ExtensionClient;
|
||||
import run.halo.app.extension.GroupKind;
|
||||
import run.halo.app.extension.MetadataUtil;
|
||||
import run.halo.app.extension.ListOptions;
|
||||
import run.halo.app.extension.controller.Controller;
|
||||
import run.halo.app.extension.controller.ControllerBuilder;
|
||||
import run.halo.app.extension.controller.Reconciler;
|
||||
|
@ -32,7 +35,6 @@ import run.halo.app.extension.controller.RequeueException;
|
|||
import run.halo.app.infra.AnonymousUserConst;
|
||||
import run.halo.app.infra.ExternalUrlSupplier;
|
||||
import run.halo.app.infra.utils.JsonUtils;
|
||||
import run.halo.app.infra.utils.PathUtils;
|
||||
|
||||
@Slf4j
|
||||
@Component
|
||||
|
@ -43,26 +45,21 @@ public class UserReconciler implements Reconciler<Request> {
|
|||
private final ExternalUrlSupplier externalUrlSupplier;
|
||||
private final RoleService roleService;
|
||||
private final AttachmentService attachmentService;
|
||||
private final RetryTemplate retryTemplate = RetryTemplate.builder()
|
||||
.maxAttempts(20)
|
||||
.fixedBackoff(300)
|
||||
.retryOn(IllegalStateException.class)
|
||||
.build();
|
||||
private final UserService userService;
|
||||
|
||||
@Override
|
||||
public Result reconcile(Request request) {
|
||||
client.fetch(User.class, request.name()).ifPresent(user -> {
|
||||
if (user.getMetadata().getDeletionTimestamp() != null) {
|
||||
cleanUpResourcesAndRemoveFinalizer(request.name());
|
||||
if (isDeleted(user)) {
|
||||
deleteUserConnections(request.name());
|
||||
removeFinalizers(user.getMetadata(), Set.of(FINALIZER_NAME));
|
||||
client.update(user);
|
||||
return;
|
||||
}
|
||||
|
||||
addFinalizerIfNecessary(user);
|
||||
ensureRoleNamesAnno(request.name());
|
||||
updatePermalink(request.name());
|
||||
handleAvatar(request.name());
|
||||
|
||||
addFinalizers(user.getMetadata(), Set.of(FINALIZER_NAME));
|
||||
ensureRoleNamesAnno(user);
|
||||
updatePermalink(user);
|
||||
handleAvatar(user);
|
||||
checkVerifiedEmail(user);
|
||||
client.update(user);
|
||||
});
|
||||
|
@ -92,147 +89,102 @@ public class UserReconciler implements Reconciler<Request> {
|
|||
.orElse(false);
|
||||
}
|
||||
|
||||
private void handleAvatar(String name) {
|
||||
client.fetch(User.class, name).ifPresent(user -> {
|
||||
Map<String, String> annotations = MetadataUtil.nullSafeAnnotations(user);
|
||||
private void handleAvatar(User user) {
|
||||
var annotations = Optional.ofNullable(user.getMetadata().getAnnotations())
|
||||
.orElseGet(HashMap::new);
|
||||
user.getMetadata().setAnnotations(annotations);
|
||||
|
||||
String avatarAttachmentName = annotations.get(User.AVATAR_ATTACHMENT_NAME_ANNO);
|
||||
String oldAvatarAttachmentName = annotations.get(User.LAST_AVATAR_ATTACHMENT_NAME_ANNO);
|
||||
var avatarAttachmentName = annotations.get(User.AVATAR_ATTACHMENT_NAME_ANNO);
|
||||
var oldAvatarAttachmentName =
|
||||
annotations.get(User.LAST_AVATAR_ATTACHMENT_NAME_ANNO);
|
||||
// remove old avatar if needed
|
||||
if (StringUtils.isNotBlank(oldAvatarAttachmentName)
|
||||
&& !StringUtils.equals(avatarAttachmentName, oldAvatarAttachmentName)) {
|
||||
client.fetch(Attachment.class, oldAvatarAttachmentName)
|
||||
.ifPresent(client::delete);
|
||||
annotations.remove(User.LAST_AVATAR_ATTACHMENT_NAME_ANNO);
|
||||
}
|
||||
|
||||
if (StringUtils.isNotBlank(oldAvatarAttachmentName)
|
||||
&& !StringUtils.equals(oldAvatarAttachmentName, avatarAttachmentName)) {
|
||||
client.fetch(Attachment.class, oldAvatarAttachmentName)
|
||||
.ifPresent(client::delete);
|
||||
annotations.remove(User.LAST_AVATAR_ATTACHMENT_NAME_ANNO);
|
||||
oldAvatarAttachmentName = null;
|
||||
var spec = user.getSpec();
|
||||
if (StringUtils.isBlank(avatarAttachmentName)) {
|
||||
if (StringUtils.isNotBlank(spec.getAvatar())) {
|
||||
log.info("Remove avatar for user({})", user.getMetadata().getName());
|
||||
}
|
||||
|
||||
if (StringUtils.isNotBlank(avatarAttachmentName)) {
|
||||
client.fetch(Attachment.class, avatarAttachmentName)
|
||||
.ifPresent(attachment -> {
|
||||
URI avatarUri = attachmentService.getPermalink(attachment).block();
|
||||
if (avatarUri == null) {
|
||||
log.warn("Failed to get avatar permalink for user [{}] with attachment "
|
||||
+ "[{}], re-enqueuing...", name, avatarAttachmentName);
|
||||
throw new RequeueException(new Result(true, null),
|
||||
"Failed to get avatar permalink.");
|
||||
}
|
||||
user.getSpec().setAvatar(avatarUri.toString());
|
||||
});
|
||||
} else if (StringUtils.isNotBlank(oldAvatarAttachmentName)) {
|
||||
user.getSpec().setAvatar(null);
|
||||
}
|
||||
|
||||
if (StringUtils.isBlank(oldAvatarAttachmentName)
|
||||
&& StringUtils.isNotBlank(avatarAttachmentName)) {
|
||||
annotations.put(User.LAST_AVATAR_ATTACHMENT_NAME_ANNO, avatarAttachmentName);
|
||||
}
|
||||
|
||||
client.update(user);
|
||||
});
|
||||
}
|
||||
|
||||
private void ensureRoleNamesAnno(String name) {
|
||||
client.fetch(User.class, name).ifPresent(user -> {
|
||||
Map<String, String> annotations = MetadataUtil.nullSafeAnnotations(user);
|
||||
Map<String, String> oldAnnotations = Map.copyOf(annotations);
|
||||
|
||||
List<String> roleNames = listRoleNamesRef(name);
|
||||
annotations.put(User.ROLE_NAMES_ANNO, JsonUtils.objectToJson(roleNames));
|
||||
|
||||
if (!oldAnnotations.equals(annotations)) {
|
||||
client.update(user);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
List<String> listRoleNamesRef(String username) {
|
||||
var subject = new RoleBinding.Subject(KIND, username, GROUP);
|
||||
return roleService.listRoleRefs(subject)
|
||||
.filter(this::isRoleRef)
|
||||
.map(RoleBinding.RoleRef::getName)
|
||||
.distinct()
|
||||
.collectList()
|
||||
.blockOptional()
|
||||
.orElse(List.of());
|
||||
}
|
||||
|
||||
private boolean isRoleRef(RoleBinding.RoleRef roleRef) {
|
||||
var roleGvk = new Role().groupVersionKind();
|
||||
var gk = new GroupKind(roleRef.getApiGroup(), roleRef.getKind());
|
||||
return gk.equals(roleGvk.groupKind());
|
||||
}
|
||||
|
||||
private void updatePermalink(String name) {
|
||||
client.fetch(User.class, name).ifPresent(user -> {
|
||||
if (AnonymousUserConst.isAnonymousUser(name)) {
|
||||
// anonymous user is not allowed to have permalink
|
||||
return;
|
||||
}
|
||||
if (user.getStatus() == null) {
|
||||
user.setStatus(new User.UserStatus());
|
||||
}
|
||||
User.UserStatus status = user.getStatus();
|
||||
String oldPermalink = status.getPermalink();
|
||||
|
||||
status.setPermalink(getUserPermalink(user));
|
||||
|
||||
if (!StringUtils.equals(oldPermalink, status.getPermalink())) {
|
||||
client.update(user);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private String getUserPermalink(User user) {
|
||||
return externalUrlSupplier.get()
|
||||
.resolve(PathUtils.combinePath("authors", user.getMetadata().getName()))
|
||||
.normalize().toString();
|
||||
}
|
||||
|
||||
private void addFinalizerIfNecessary(User oldUser) {
|
||||
Set<String> finalizers = oldUser.getMetadata().getFinalizers();
|
||||
if (finalizers != null && finalizers.contains(FINALIZER_NAME)) {
|
||||
spec.setAvatar(null);
|
||||
return;
|
||||
}
|
||||
client.fetch(User.class, oldUser.getMetadata().getName())
|
||||
.ifPresent(user -> {
|
||||
Set<String> newFinalizers = user.getMetadata().getFinalizers();
|
||||
if (newFinalizers == null) {
|
||||
newFinalizers = new HashSet<>();
|
||||
user.getMetadata().setFinalizers(newFinalizers);
|
||||
client.fetch(Attachment.class, avatarAttachmentName)
|
||||
.flatMap(attachment -> attachmentService.getPermalink(attachment)
|
||||
.blockOptional(Duration.ofMinutes(1))
|
||||
)
|
||||
.map(URI::toString)
|
||||
.ifPresentOrElse(avatar -> {
|
||||
if (!Objects.equals(avatar, spec.getAvatar())) {
|
||||
log.info(
|
||||
"Update avatar for user({}) to {}",
|
||||
user.getMetadata().getName(), avatar
|
||||
);
|
||||
}
|
||||
newFinalizers.add(FINALIZER_NAME);
|
||||
client.update(user);
|
||||
spec.setAvatar(avatar);
|
||||
// reset last avatar
|
||||
annotations.put(
|
||||
User.LAST_AVATAR_ATTACHMENT_NAME_ANNO,
|
||||
avatarAttachmentName
|
||||
);
|
||||
}, () -> {
|
||||
throw new RequeueException(
|
||||
new Result(true, null),
|
||||
"Avatar permalink(%s) is not available yet."
|
||||
.formatted(avatarAttachmentName)
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
private void cleanUpResourcesAndRemoveFinalizer(String userName) {
|
||||
client.fetch(User.class, userName).ifPresent(user -> {
|
||||
// wait for dependent resources to be deleted
|
||||
deleteUserConnections(userName);
|
||||
|
||||
// remove finalizer
|
||||
if (user.getMetadata().getFinalizers() != null) {
|
||||
user.getMetadata().getFinalizers().remove(FINALIZER_NAME);
|
||||
}
|
||||
client.update(user);
|
||||
});
|
||||
private void ensureRoleNamesAnno(User user) {
|
||||
roleService.getRolesByUsername(user.getMetadata().getName())
|
||||
.collectList()
|
||||
.map(JsonUtils::objectToJson)
|
||||
.doOnNext(roleNamesJson -> {
|
||||
var annotations = Optional.ofNullable(user.getMetadata().getAnnotations())
|
||||
.orElseGet(HashMap::new);
|
||||
user.getMetadata().setAnnotations(annotations);
|
||||
annotations.put(User.ROLE_NAMES_ANNO, roleNamesJson);
|
||||
})
|
||||
.block(Duration.ofMinutes(1));
|
||||
}
|
||||
|
||||
void deleteUserConnections(String userName) {
|
||||
listConnectionsByUsername(userName).forEach(client::delete);
|
||||
// wait for user connection to be deleted
|
||||
retryTemplate.execute(callback -> {
|
||||
if (listConnectionsByUsername(userName).size() > 0) {
|
||||
throw new IllegalStateException("User connection is not deleted yet");
|
||||
}
|
||||
return null;
|
||||
});
|
||||
private void updatePermalink(User user) {
|
||||
var name = user.getMetadata().getName();
|
||||
if (AnonymousUserConst.isAnonymousUser(name)) {
|
||||
// anonymous user is not allowed to have permalink
|
||||
return;
|
||||
}
|
||||
var status = Optional.ofNullable(user.getStatus())
|
||||
.orElseGet(User.UserStatus::new);
|
||||
user.setStatus(status);
|
||||
status.setPermalink(getUserPermalink(user));
|
||||
}
|
||||
|
||||
private String getUserPermalink(User user) {
|
||||
return UriComponentsBuilder.fromUri(externalUrlSupplier.get())
|
||||
.pathSegment("authors", user.getMetadata().getName())
|
||||
.toUriString();
|
||||
}
|
||||
|
||||
void deleteUserConnections(String username) {
|
||||
var userConnections = listConnectionsByUsername(username);
|
||||
if (CollectionUtils.isEmpty(userConnections)) {
|
||||
return;
|
||||
}
|
||||
userConnections.forEach(client::delete);
|
||||
throw new RequeueException(new Result(true, null), "User connections are not deleted yet");
|
||||
}
|
||||
|
||||
List<UserConnection> listConnectionsByUsername(String username) {
|
||||
return client.list(UserConnection.class,
|
||||
connection -> connection.getSpec().getUsername().equals(username), null);
|
||||
var listOptions = ListOptions.builder()
|
||||
.andQuery(equal("spec.username", username))
|
||||
.build();
|
||||
return client.listAll(UserConnection.class, listOptions, defaultSort());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -1,13 +1,17 @@
|
|||
package run.halo.app.core.extension.service;
|
||||
|
||||
import static run.halo.app.extension.Comparators.compareCreationTimestamp;
|
||||
import static run.halo.app.extension.ExtensionUtil.defaultSort;
|
||||
import static run.halo.app.extension.ExtensionUtil.notDeleting;
|
||||
import static run.halo.app.security.authorization.AuthorityUtils.containsSuperRole;
|
||||
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Collectors;
|
||||
|
@ -22,8 +26,12 @@ import run.halo.app.core.extension.Role;
|
|||
import run.halo.app.core.extension.RoleBinding;
|
||||
import run.halo.app.core.extension.RoleBinding.RoleRef;
|
||||
import run.halo.app.core.extension.RoleBinding.Subject;
|
||||
import run.halo.app.core.extension.User;
|
||||
import run.halo.app.extension.ExtensionUtil;
|
||||
import run.halo.app.extension.ListOptions;
|
||||
import run.halo.app.extension.MetadataUtil;
|
||||
import run.halo.app.extension.ReactiveExtensionClient;
|
||||
import run.halo.app.extension.index.query.QueryFactory;
|
||||
import run.halo.app.infra.utils.JsonUtils;
|
||||
import run.halo.app.security.SuperAdminInitializer;
|
||||
|
||||
|
@ -35,18 +43,58 @@ import run.halo.app.security.SuperAdminInitializer;
|
|||
@Service
|
||||
public class DefaultRoleService implements RoleService {
|
||||
|
||||
private final ReactiveExtensionClient extensionClient;
|
||||
private final ReactiveExtensionClient client;
|
||||
|
||||
public DefaultRoleService(ReactiveExtensionClient extensionClient) {
|
||||
this.extensionClient = extensionClient;
|
||||
public DefaultRoleService(ReactiveExtensionClient client) {
|
||||
this.client = client;
|
||||
}
|
||||
|
||||
private Flux<RoleRef> listRoleRefs(Subject subject) {
|
||||
return listRoleBindings(subject).map(RoleBinding::getRoleRef);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Flux<RoleRef> listRoleRefs(Subject subject) {
|
||||
return extensionClient.list(RoleBinding.class,
|
||||
binding -> binding.getSubjects().contains(subject),
|
||||
null)
|
||||
.map(RoleBinding::getRoleRef);
|
||||
public Flux<RoleBinding> listRoleBindings(Subject subject) {
|
||||
var listOptions = ListOptions.builder()
|
||||
.andQuery(notDeleting())
|
||||
.andQuery(QueryFactory.in("subjects", subject.toString()))
|
||||
.build();
|
||||
return client.listAll(RoleBinding.class, listOptions, defaultSort());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Flux<String> getRolesByUsername(String username) {
|
||||
return listRoleRefs(toUserSubject(username))
|
||||
.filter(DefaultRoleService::isRoleKind)
|
||||
.map(RoleRef::getName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Map<String, Collection<String>>> getRolesByUsernames(Collection<String> usernames) {
|
||||
if (CollectionUtils.isEmpty(usernames)) {
|
||||
return Mono.empty();
|
||||
}
|
||||
var subjects = usernames.stream().map(DefaultRoleService::toUserSubject)
|
||||
.map(Object::toString)
|
||||
.collect(Collectors.toSet());
|
||||
var listOptions = ListOptions.builder()
|
||||
.andQuery(notDeleting())
|
||||
.andQuery(QueryFactory.in("subjects", subjects))
|
||||
.build();
|
||||
|
||||
return client.listAll(RoleBinding.class, listOptions, defaultSort())
|
||||
.collect(HashMap::new, (map, roleBinding) -> {
|
||||
for (Subject subject : roleBinding.getSubjects()) {
|
||||
if (subjects.contains(subject.toString())) {
|
||||
var username = subject.getName();
|
||||
var roleRef = roleBinding.getRoleRef();
|
||||
if (isRoleKind(roleRef)) {
|
||||
var roleName = roleRef.getName();
|
||||
map.computeIfAbsent(username, k -> new HashSet<>()).add(roleName);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -54,7 +102,7 @@ public class DefaultRoleService implements RoleService {
|
|||
if (source.contains(SuperAdminInitializer.SUPER_ROLE_NAME)) {
|
||||
return Mono.just(true);
|
||||
}
|
||||
return listDependencies(new HashSet<>(source), shouldFilterHidden(false))
|
||||
return listWithDependencies(new HashSet<>(source), shouldExcludeHidden(false))
|
||||
.map(role -> role.getMetadata().getName())
|
||||
.collect(Collectors.toSet())
|
||||
.map(roleNames -> roleNames.containsAll(candidates));
|
||||
|
@ -64,55 +112,58 @@ public class DefaultRoleService implements RoleService {
|
|||
public Flux<Role> listPermissions(Set<String> names) {
|
||||
if (containsSuperRole(names)) {
|
||||
// search all permissions
|
||||
return extensionClient.list(Role.class,
|
||||
shouldFilterHidden(true),
|
||||
compareCreationTimestamp(true));
|
||||
return client.listAll(Role.class,
|
||||
shouldExcludeHidden(true),
|
||||
ExtensionUtil.defaultSort());
|
||||
}
|
||||
return listDependencies(names, shouldFilterHidden(true));
|
||||
return listWithDependencies(names, shouldExcludeHidden(true));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Flux<Role> listDependenciesFlux(Set<String> names) {
|
||||
return listDependencies(names, shouldFilterHidden(false));
|
||||
return listWithDependencies(names, shouldExcludeHidden(false));
|
||||
}
|
||||
|
||||
private Flux<Role> listRoles(Set<String> names, Predicate<Role> additionalPredicate) {
|
||||
private static boolean isRoleKind(RoleRef roleRef) {
|
||||
return Role.GROUP.equals(roleRef.getApiGroup()) && Role.KIND.equals(roleRef.getKind());
|
||||
}
|
||||
|
||||
private static Subject toUserSubject(String username) {
|
||||
var subject = new Subject();
|
||||
subject.setApiGroup(User.GROUP);
|
||||
subject.setKind(User.KIND);
|
||||
subject.setName(username);
|
||||
return subject;
|
||||
}
|
||||
|
||||
private Flux<Role> listRoles(Set<String> names, ListOptions additionalListOptions) {
|
||||
if (CollectionUtils.isEmpty(names)) {
|
||||
return Flux.empty();
|
||||
}
|
||||
|
||||
Predicate<Role> predicate = role -> names.contains(role.getMetadata().getName());
|
||||
if (additionalPredicate != null) {
|
||||
predicate = predicate.and(additionalPredicate);
|
||||
}
|
||||
return extensionClient.list(Role.class, predicate, compareCreationTimestamp(true));
|
||||
var listOptions = Optional.ofNullable(additionalListOptions)
|
||||
.map(ListOptions::builder)
|
||||
.orElseGet(ListOptions::builder)
|
||||
.andQuery(notDeleting())
|
||||
.andQuery(QueryFactory.in("metadata.name", names))
|
||||
.build();
|
||||
|
||||
return client.listAll(Role.class, listOptions, ExtensionUtil.defaultSort());
|
||||
}
|
||||
|
||||
private static Predicate<Role> shouldFilterHidden(boolean filterHidden) {
|
||||
if (!filterHidden) {
|
||||
return r -> true;
|
||||
private static ListOptions shouldExcludeHidden(boolean excludeHidden) {
|
||||
if (!excludeHidden) {
|
||||
return null;
|
||||
}
|
||||
return role -> {
|
||||
var labels = role.getMetadata().getLabels();
|
||||
if (labels == null) {
|
||||
return true;
|
||||
}
|
||||
var hiddenValue = labels.get(Role.HIDDEN_LABEL_NAME);
|
||||
return !Boolean.parseBoolean(hiddenValue);
|
||||
};
|
||||
return ListOptions.builder().labelSelector()
|
||||
.notEq(Role.HIDDEN_LABEL_NAME, Boolean.TRUE.toString())
|
||||
.end()
|
||||
.build();
|
||||
}
|
||||
|
||||
private static boolean isRoleTemplate(Role role) {
|
||||
var labels = role.getMetadata().getLabels();
|
||||
if (labels == null) {
|
||||
return false;
|
||||
}
|
||||
return Boolean.parseBoolean(labels.get(Role.TEMPLATE_LABEL_NAME));
|
||||
}
|
||||
|
||||
private Flux<Role> listDependencies(Set<String> names, Predicate<Role> additionalPredicate) {
|
||||
private Flux<Role> listWithDependencies(Set<String> names, ListOptions additionalListOptions) {
|
||||
var visited = new HashSet<String>();
|
||||
return listRoles(names, additionalPredicate)
|
||||
return listRoles(names, additionalListOptions)
|
||||
.expand(role -> {
|
||||
var name = role.getMetadata().getName();
|
||||
if (visited.contains(name)) {
|
||||
|
@ -128,29 +179,26 @@ public class DefaultRoleService implements RoleService {
|
|||
|
||||
return Flux.fromIterable(dependencies)
|
||||
.filter(dep -> !visited.contains(dep))
|
||||
.collect(Collectors.<String>toSet())
|
||||
.flatMapMany(deps -> listRoles(deps, additionalPredicate));
|
||||
.collect(Collectors.toSet())
|
||||
.flatMapMany(deps -> listRoles(deps, additionalListOptions));
|
||||
})
|
||||
.concatWith(Flux.defer(() -> listAggregatedRoles(visited, additionalPredicate)));
|
||||
.concatWith(Flux.defer(() -> listAggregatedRoles(visited, additionalListOptions)));
|
||||
}
|
||||
|
||||
private Flux<Role> listAggregatedRoles(Set<String> roleNames,
|
||||
Predicate<Role> additionalPredicate) {
|
||||
var aggregatedLabelNames = roleNames.stream()
|
||||
.map(roleName -> Role.ROLE_AGGREGATE_LABEL_PREFIX + roleName)
|
||||
.collect(Collectors.toSet());
|
||||
Predicate<Role> predicate = role -> {
|
||||
var labels = role.getMetadata().getLabels();
|
||||
if (labels == null) {
|
||||
return false;
|
||||
}
|
||||
return aggregatedLabelNames.stream()
|
||||
.anyMatch(aggregatedLabel -> Boolean.parseBoolean(labels.get(aggregatedLabel)));
|
||||
};
|
||||
if (additionalPredicate != null) {
|
||||
predicate = predicate.and(additionalPredicate);
|
||||
ListOptions additionalListOptions) {
|
||||
if (CollectionUtils.isEmpty(roleNames)) {
|
||||
return Flux.empty();
|
||||
}
|
||||
return extensionClient.list(Role.class, predicate, compareCreationTimestamp(true));
|
||||
var listOptionsBuilder = Optional.ofNullable(additionalListOptions)
|
||||
.map(ListOptions::builder)
|
||||
.orElseGet(ListOptions::builder);
|
||||
roleNames.stream()
|
||||
.map(roleName -> Role.ROLE_AGGREGATE_LABEL_PREFIX + roleName)
|
||||
.forEach(
|
||||
label -> listOptionsBuilder.labelSelector().eq(label, Boolean.TRUE.toString())
|
||||
);
|
||||
return client.listAll(Role.class, listOptionsBuilder.build(), ExtensionUtil.defaultSort());
|
||||
}
|
||||
|
||||
Predicate<RoleBinding> getRoleBindingPredicate(Subject targetSubject) {
|
||||
|
@ -175,11 +223,21 @@ public class DefaultRoleService implements RoleService {
|
|||
|
||||
@Override
|
||||
public Flux<Role> list(Set<String> roleNames) {
|
||||
return list(roleNames, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Flux<Role> list(Set<String> roleNames, boolean excludeHidden) {
|
||||
if (CollectionUtils.isEmpty(roleNames)) {
|
||||
return Flux.empty();
|
||||
}
|
||||
return Flux.fromIterable(roleNames)
|
||||
.flatMap(roleName -> extensionClient.fetch(Role.class, roleName));
|
||||
var builder = ListOptions.builder()
|
||||
.andQuery(notDeleting())
|
||||
.andQuery(QueryFactory.in("metadata.name", roleNames));
|
||||
if (excludeHidden) {
|
||||
builder.labelSelector().notEq(Role.HIDDEN_LABEL_NAME, Boolean.TRUE.toString());
|
||||
}
|
||||
return client.listAll(Role.class, builder.build(), defaultSort());
|
||||
}
|
||||
|
||||
@NonNull
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
package run.halo.app.core.extension.service;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
import run.halo.app.core.extension.Role;
|
||||
import run.halo.app.core.extension.RoleBinding.RoleRef;
|
||||
import run.halo.app.core.extension.RoleBinding;
|
||||
import run.halo.app.core.extension.RoleBinding.Subject;
|
||||
|
||||
/**
|
||||
|
@ -14,7 +15,11 @@ import run.halo.app.core.extension.RoleBinding.Subject;
|
|||
*/
|
||||
public interface RoleService {
|
||||
|
||||
Flux<RoleRef> listRoleRefs(Subject subject);
|
||||
Flux<RoleBinding> listRoleBindings(Subject subject);
|
||||
|
||||
Flux<String> getRolesByUsername(String username);
|
||||
|
||||
Mono<Map<String, Collection<String>>> getRolesByUsernames(Collection<String> usernames);
|
||||
|
||||
Mono<Boolean> contains(Collection<String> source, Collection<String> candidates);
|
||||
|
||||
|
@ -29,5 +34,20 @@ public interface RoleService {
|
|||
|
||||
Flux<Role> listDependenciesFlux(Set<String> names);
|
||||
|
||||
/**
|
||||
* List roles by role names.
|
||||
*
|
||||
* @param roleNames role names
|
||||
* @return roles
|
||||
*/
|
||||
Flux<Role> list(Set<String> roleNames);
|
||||
|
||||
/**
|
||||
* List roles by role names.
|
||||
*
|
||||
* @param roleNames role names
|
||||
* @param excludeHidden should exclude hidden roles
|
||||
* @return roles
|
||||
*/
|
||||
Flux<Role> list(Set<String> roleNames, boolean excludeHidden);
|
||||
}
|
||||
|
|
|
@ -3,7 +3,6 @@ package run.halo.app.core.extension.service;
|
|||
import java.util.Set;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
import run.halo.app.core.extension.Role;
|
||||
import run.halo.app.core.extension.User;
|
||||
|
||||
public interface UserService {
|
||||
|
@ -16,8 +15,6 @@ public interface UserService {
|
|||
|
||||
Mono<User> updateWithRawPassword(String username, String rawPassword);
|
||||
|
||||
Flux<Role> listRoles(String username);
|
||||
|
||||
Mono<User> grantRoles(String username, Set<String> roles);
|
||||
|
||||
Mono<User> signUp(User user, String password);
|
||||
|
|
|
@ -2,15 +2,19 @@ package run.halo.app.core.extension.service;
|
|||
|
||||
import static org.springframework.data.domain.Sort.Order.asc;
|
||||
import static org.springframework.data.domain.Sort.Order.desc;
|
||||
import static run.halo.app.core.extension.RoleBinding.containsUser;
|
||||
import static run.halo.app.extension.index.query.QueryFactory.equal;
|
||||
|
||||
import java.time.Clock;
|
||||
import java.time.Duration;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.commons.lang3.BooleanUtils;
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
import org.springframework.dao.OptimisticLockingFailureException;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
@ -21,6 +25,7 @@ import org.springframework.util.StringUtils;
|
|||
import org.springframework.web.server.ServerWebInputException;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
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;
|
||||
|
@ -48,11 +53,18 @@ public class UserServiceImpl implements UserService {
|
|||
|
||||
private final ApplicationEventPublisher eventPublisher;
|
||||
|
||||
private final RoleService roleService;
|
||||
|
||||
private Clock clock = Clock.systemUTC();
|
||||
|
||||
void setClock(Clock clock) {
|
||||
this.clock = clock;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<User> getUser(String username) {
|
||||
return client.get(User.class, username)
|
||||
.onErrorMap(ExtensionNotFoundException.class,
|
||||
e -> new UserNotFoundException(username));
|
||||
.onErrorMap(ExtensionNotFoundException.class, e -> new UserNotFoundException(username));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -91,22 +103,17 @@ public class UserServiceImpl implements UserService {
|
|||
}
|
||||
|
||||
@Override
|
||||
public Flux<Role> listRoles(String name) {
|
||||
return client.list(RoleBinding.class, containsUser(name), null)
|
||||
.filter(roleBinding -> Role.KIND.equals(roleBinding.getRoleRef().getKind()))
|
||||
.map(roleBinding -> roleBinding.getRoleRef().getName())
|
||||
.flatMap(roleName -> client.fetch(Role.class, roleName));
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public Mono<User> grantRoles(String username, Set<String> roles) {
|
||||
return client.get(User.class, username)
|
||||
.flatMap(user -> {
|
||||
var bindingsToUpdate = new HashSet<RoleBinding>();
|
||||
var bindingsToDelete = new HashSet<RoleBinding>();
|
||||
var existingRoles = new HashSet<String>();
|
||||
return client.list(RoleBinding.class, RoleBinding.containsUser(username), null)
|
||||
var subject = new RoleBinding.Subject();
|
||||
subject.setKind(User.KIND);
|
||||
subject.setApiGroup(User.GROUP);
|
||||
subject.setName(username);
|
||||
return roleService.listRoleBindings(subject)
|
||||
.doOnNext(binding -> {
|
||||
var roleName = binding.getRoleRef().getName();
|
||||
if (roles.contains(roleName)) {
|
||||
|
@ -129,7 +136,13 @@ public class UserServiceImpl implements UserService {
|
|||
return mutableRoles.stream()
|
||||
.map(roleName -> RoleBinding.create(username, roleName));
|
||||
}).flatMap(client::create))
|
||||
.then(Mono.just(user));
|
||||
.then(Mono.defer(() -> {
|
||||
var annotations = Optional.ofNullable(user.getMetadata().getAnnotations())
|
||||
.orElseGet(HashMap::new);
|
||||
user.getMetadata().setAnnotations(annotations);
|
||||
annotations.put(User.REQUEST_TO_UPDATE, clock.instant().toString());
|
||||
return client.update(user);
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -184,7 +197,12 @@ public class UserServiceImpl implements UserService {
|
|||
.then();
|
||||
})
|
||||
.then(Mono.defer(() -> client.create(user)
|
||||
.flatMap(newUser -> grantRoles(user.getMetadata().getName(), roleNames)))
|
||||
.flatMap(newUser -> grantRoles(user.getMetadata().getName(), roleNames)
|
||||
.retryWhen(
|
||||
Retry.backoff(5, Duration.ofMillis(100))
|
||||
.filter(OptimisticLockingFailureException.class::isInstance)
|
||||
)
|
||||
))
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -7,7 +7,9 @@ import static run.halo.app.extension.index.IndexAttributeFactory.multiValueAttri
|
|||
import static run.halo.app.extension.index.IndexAttributeFactory.simpleAttribute;
|
||||
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import org.apache.commons.lang3.BooleanUtils;
|
||||
import org.apache.commons.lang3.ObjectUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
@ -31,6 +33,7 @@ import run.halo.app.core.extension.Setting;
|
|||
import run.halo.app.core.extension.Theme;
|
||||
import run.halo.app.core.extension.User;
|
||||
import run.halo.app.core.extension.UserConnection;
|
||||
import run.halo.app.core.extension.UserConnection.UserConnectionSpec;
|
||||
import run.halo.app.core.extension.attachment.Attachment;
|
||||
import run.halo.app.core.extension.attachment.Group;
|
||||
import run.halo.app.core.extension.attachment.Policy;
|
||||
|
@ -51,6 +54,7 @@ import run.halo.app.core.extension.notification.Subscription;
|
|||
import run.halo.app.extension.ConfigMap;
|
||||
import run.halo.app.extension.DefaultSchemeManager;
|
||||
import run.halo.app.extension.DefaultSchemeWatcherManager;
|
||||
import run.halo.app.extension.MetadataOperator;
|
||||
import run.halo.app.extension.MetadataUtil;
|
||||
import run.halo.app.extension.Secret;
|
||||
import run.halo.app.extension.index.IndexSpec;
|
||||
|
@ -89,7 +93,22 @@ public class SchemeInitializer implements ApplicationListener<ApplicationContext
|
|||
));
|
||||
});
|
||||
|
||||
schemeManager.register(RoleBinding.class);
|
||||
schemeManager.register(RoleBinding.class, is -> {
|
||||
is.add(new IndexSpec()
|
||||
.setName("roleRef.name")
|
||||
.setIndexFunc(simpleAttribute(RoleBinding.class,
|
||||
roleBinding -> roleBinding.getRoleRef().getName())
|
||||
)
|
||||
);
|
||||
is.add(new IndexSpec()
|
||||
.setName("subjects")
|
||||
.setIndexFunc(multiValueAttribute(RoleBinding.class,
|
||||
roleBinding -> roleBinding.getSubjects().stream()
|
||||
.map(RoleBinding.Subject::toString)
|
||||
.collect(Collectors.toSet()))
|
||||
)
|
||||
);
|
||||
});
|
||||
schemeManager.register(User.class, indexSpecs -> {
|
||||
indexSpecs.add(new IndexSpec()
|
||||
.setName("spec.displayName")
|
||||
|
@ -103,16 +122,16 @@ public class SchemeInitializer implements ApplicationListener<ApplicationContext
|
|||
})));
|
||||
indexSpecs.add(new IndexSpec()
|
||||
.setName(User.USER_RELATED_ROLES_INDEX)
|
||||
.setIndexFunc(multiValueAttribute(User.class, user -> {
|
||||
var roleNamesAnno = MetadataUtil.nullSafeAnnotations(user)
|
||||
.get(User.ROLE_NAMES_ANNO);
|
||||
if (StringUtils.isBlank(roleNamesAnno)) {
|
||||
return Set.of();
|
||||
}
|
||||
return JsonUtils.jsonToObject(roleNamesAnno,
|
||||
new TypeReference<>() {
|
||||
});
|
||||
})));
|
||||
.setIndexFunc(multiValueAttribute(User.class, user ->
|
||||
Optional.ofNullable(user.getMetadata())
|
||||
.map(MetadataOperator::getAnnotations)
|
||||
.map(annotations -> annotations.get(User.ROLE_NAMES_ANNO))
|
||||
.filter(StringUtils::isNotBlank)
|
||||
.map(rolesJson -> JsonUtils.jsonToObject(rolesJson,
|
||||
new TypeReference<Set<String>>() {
|
||||
})
|
||||
)
|
||||
.orElseGet(Set::of))));
|
||||
});
|
||||
schemeManager.register(ReverseProxy.class);
|
||||
schemeManager.register(Setting.class);
|
||||
|
@ -453,7 +472,15 @@ public class SchemeInitializer implements ApplicationListener<ApplicationContext
|
|||
schemeManager.register(Counter.class);
|
||||
// auth.halo.run
|
||||
schemeManager.register(AuthProvider.class);
|
||||
schemeManager.register(UserConnection.class);
|
||||
schemeManager.register(UserConnection.class, is -> {
|
||||
is.add(new IndexSpec()
|
||||
.setName("spec.username")
|
||||
.setIndexFunc(simpleAttribute(UserConnection.class,
|
||||
connection -> Optional.ofNullable(connection.getSpec())
|
||||
.map(UserConnectionSpec::getUsername)
|
||||
.orElse(null)
|
||||
)));
|
||||
});
|
||||
|
||||
// security.halo.run
|
||||
schemeManager.register(PersonalAccessToken.class);
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
package run.halo.app.security;
|
||||
|
||||
import static java.util.Objects.requireNonNullElse;
|
||||
import static run.halo.app.core.extension.User.GROUP;
|
||||
import static run.halo.app.core.extension.User.KIND;
|
||||
import static run.halo.app.security.authorization.AuthorityUtils.ANONYMOUS_ROLE_NAME;
|
||||
import static run.halo.app.security.authorization.AuthorityUtils.AUTHENTICATED_ROLE_NAME;
|
||||
import static run.halo.app.security.authorization.AuthorityUtils.ROLE_PREFIX;
|
||||
|
@ -15,12 +13,8 @@ import org.springframework.security.core.userdetails.ReactiveUserDetailsService;
|
|||
import org.springframework.security.core.userdetails.User;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import reactor.core.publisher.Mono;
|
||||
import run.halo.app.core.extension.Role;
|
||||
import run.halo.app.core.extension.RoleBinding.RoleRef;
|
||||
import run.halo.app.core.extension.RoleBinding.Subject;
|
||||
import run.halo.app.core.extension.service.RoleService;
|
||||
import run.halo.app.core.extension.service.UserService;
|
||||
import run.halo.app.extension.GroupKind;
|
||||
import run.halo.app.infra.exception.UserNotFoundException;
|
||||
import run.halo.app.security.authentication.login.HaloUser;
|
||||
import run.halo.app.security.authentication.twofactor.TwoFactorUtils;
|
||||
|
@ -56,13 +50,10 @@ public class DefaultUserDetailService
|
|||
e -> new BadCredentialsException("Invalid Credentials"))
|
||||
.flatMap(user -> {
|
||||
var name = user.getMetadata().getName();
|
||||
var subject = new Subject(KIND, name, GROUP);
|
||||
var userBuilder = User.withUsername(name)
|
||||
.password(user.getSpec().getPassword())
|
||||
.disabled(requireNonNullElse(user.getSpec().getDisabled(), false));
|
||||
var setAuthorities = roleService.listRoleRefs(subject)
|
||||
.filter(this::isRoleRef)
|
||||
.map(RoleRef::getName)
|
||||
var setAuthorities = roleService.getRolesByUsername(name)
|
||||
// every authenticated user should have authenticated and anonymous roles.
|
||||
.concatWithValues(AUTHENTICATED_ROLE_NAME, ANONYMOUS_ROLE_NAME)
|
||||
.map(roleName -> new SimpleGrantedAuthority(ROLE_PREFIX + roleName))
|
||||
|
@ -82,12 +73,6 @@ public class DefaultUserDetailService
|
|||
});
|
||||
}
|
||||
|
||||
private boolean isRoleRef(RoleRef roleRef) {
|
||||
var roleGvk = new Role().groupVersionKind();
|
||||
var gk = new GroupKind(roleRef.getApiGroup(), roleRef.getKind());
|
||||
return gk.equals(roleGvk.groupKind());
|
||||
}
|
||||
|
||||
private UserDetails withNewPassword(UserDetails userDetails, String newPassword) {
|
||||
return User.withUserDetails(userDetails)
|
||||
.password(newPassword)
|
||||
|
|
|
@ -39,8 +39,11 @@ public enum AuthorityUtils {
|
|||
Collection<? extends GrantedAuthority> authorities) {
|
||||
return authorities.stream()
|
||||
.map(GrantedAuthority::getAuthority)
|
||||
.filter(authority -> StringUtils.startsWith(authority, ROLE_PREFIX))
|
||||
.map(authority -> StringUtils.removeStart(authority, ROLE_PREFIX))
|
||||
.map(authority -> {
|
||||
authority = StringUtils.removeStart(authority, SCOPE_PREFIX);
|
||||
authority = StringUtils.removeStart(authority, ROLE_PREFIX);
|
||||
return authority;
|
||||
})
|
||||
.collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
|
|
|
@ -1,14 +1,10 @@
|
|||
package run.halo.app.security.authorization;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.stream.Collectors;
|
||||
import lombok.Data;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import reactor.core.publisher.Mono;
|
||||
import run.halo.app.core.extension.service.RoleService;
|
||||
|
@ -30,7 +26,7 @@ public class DefaultRuleResolver implements AuthorizationRuleResolver {
|
|||
@Override
|
||||
public Mono<AuthorizingVisitor> visitRules(Authentication authentication,
|
||||
RequestInfo requestInfo) {
|
||||
var roleNames = listBoundRoleNames(authentication.getAuthorities());
|
||||
var roleNames = AuthorityUtils.authoritiesToRoles(authentication.getAuthorities());
|
||||
var record = new AttributesRecord(authentication, requestInfo);
|
||||
var visitor = new AuthorizingVisitor(record);
|
||||
|
||||
|
@ -75,15 +71,4 @@ public class DefaultRuleResolver implements AuthorizationRuleResolver {
|
|||
return String.format("Binding role [%s] to [%s]", roleName, subject);
|
||||
}
|
||||
|
||||
private static Set<String> listBoundRoleNames(
|
||||
Collection<? extends GrantedAuthority> authorities) {
|
||||
return authorities.stream()
|
||||
.map(GrantedAuthority::getAuthority)
|
||||
.map(authority -> {
|
||||
authority = StringUtils.removeStart(authority, AuthorityUtils.SCOPE_PREFIX);
|
||||
authority = StringUtils.removeStart(authority, AuthorityUtils.ROLE_PREFIX);
|
||||
return authority;
|
||||
})
|
||||
.collect(Collectors.toSet());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package run.halo.app.core.extension;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
|
@ -40,4 +41,20 @@ class RoleBindingTest {
|
|||
assertFalse(RoleBinding.containsUser("non-exist-fake-name").test(binding));
|
||||
}
|
||||
|
||||
@Test
|
||||
void subjectToStringTest() {
|
||||
assertEquals("User/fake-name", createSubject("fake-name", "", "User").toString());
|
||||
assertEquals(
|
||||
"fake.group/User/fake-name",
|
||||
createSubject("fake-name", "fake.group", "User").toString()
|
||||
);
|
||||
}
|
||||
|
||||
RoleBinding.Subject createSubject(String name, String apiGroup, String kind) {
|
||||
var subject = new RoleBinding.Subject();
|
||||
subject.setName(name);
|
||||
subject.setApiGroup(apiGroup);
|
||||
subject.setKind(kind);
|
||||
return subject;
|
||||
}
|
||||
}
|
|
@ -6,6 +6,8 @@ import static org.springframework.security.test.web.reactive.server.SecurityMock
|
|||
|
||||
import java.time.Instant;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Nested;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
@ -17,6 +19,7 @@ import org.springframework.security.test.context.support.WithMockUser;
|
|||
import org.springframework.test.annotation.DirtiesContext;
|
||||
import org.springframework.test.web.reactive.server.WebTestClient;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
import run.halo.app.core.extension.Role;
|
||||
import run.halo.app.core.extension.User;
|
||||
import run.halo.app.core.extension.service.RoleService;
|
||||
|
@ -46,7 +49,7 @@ public class UserEndpointIntegrationTest {
|
|||
.build();
|
||||
var role = new Role();
|
||||
role.setMetadata(new Metadata());
|
||||
role.getMetadata().setName("super-role");
|
||||
role.getMetadata().setName("fake-super-role");
|
||||
role.setRules(List.of(rule));
|
||||
when(roleService.listDependenciesFlux(anySet())).thenReturn(Flux.just(role));
|
||||
webClient = webClient.mutateWith(csrf());
|
||||
|
@ -68,6 +71,9 @@ public class UserEndpointIntegrationTest {
|
|||
client.create(unexpectedUser2).block();
|
||||
|
||||
when(roleService.list(anySet())).thenReturn(Flux.empty());
|
||||
when(roleService.getRolesByUsernames(
|
||||
List.of("fake-user-2")
|
||||
)).thenReturn(Mono.just(Map.of("fake-user-2", Set.of("fake-super-role"))));
|
||||
|
||||
webClient.get().uri("/apis/api.console.halo.run/v1alpha1/users?keyword=Expected")
|
||||
.exchange()
|
||||
|
@ -92,6 +98,8 @@ public class UserEndpointIntegrationTest {
|
|||
client.create(unexpectedUser2).block();
|
||||
|
||||
when(roleService.list(anySet())).thenReturn(Flux.empty());
|
||||
when(roleService.getRolesByUsernames(List.of("fake-user")))
|
||||
.thenReturn(Mono.just(Map.of("fake-user", Set.of("fake-super-role"))));
|
||||
|
||||
webClient.get().uri("/apis/api.console.halo.run/v1alpha1/users?keyword=fake-user")
|
||||
.exchange()
|
||||
|
|
|
@ -88,6 +88,8 @@ class UserEndpointTest {
|
|||
|
||||
@Test
|
||||
void shouldListEmptyUsersWhenNoUsers() {
|
||||
when(roleService.getRolesByUsernames(any())).thenReturn(Mono.just(Map.of()));
|
||||
when(roleService.list(any())).thenReturn(Flux.empty());
|
||||
when(client.listBy(same(User.class), any(), any(PageRequest.class)))
|
||||
.thenReturn(Mono.just(ListResult.emptyResult()));
|
||||
|
||||
|
@ -109,6 +111,7 @@ class UserEndpointTest {
|
|||
createUser("fake-user-3")
|
||||
);
|
||||
var expectResult = new ListResult<>(users);
|
||||
when(roleService.getRolesByUsernames(any())).thenReturn(Mono.just(Map.of()));
|
||||
when(roleService.list(anySet())).thenReturn(Flux.empty());
|
||||
when(client.listBy(same(User.class), any(), any(PageRequest.class)))
|
||||
.thenReturn(Mono.just(expectResult));
|
||||
|
@ -144,6 +147,7 @@ class UserEndpointTest {
|
|||
var expectResult = new ListResult<>(users);
|
||||
when(client.listBy(same(User.class), any(), any(PageRequest.class)))
|
||||
.thenReturn(Mono.just(expectResult));
|
||||
when(roleService.getRolesByUsernames(any())).thenReturn(Mono.just(Map.of()));
|
||||
when(roleService.list(anySet())).thenReturn(Flux.empty());
|
||||
|
||||
bindToRouterFunction(endpoint.endpoint())
|
||||
|
@ -160,6 +164,7 @@ class UserEndpointTest {
|
|||
var expectResult = new ListResult<>(List.of(expectUser));
|
||||
when(client.listBy(same(User.class), any(), any(PageRequest.class)))
|
||||
.thenReturn(Mono.just(expectResult));
|
||||
when(roleService.getRolesByUsernames(any())).thenReturn(Mono.just(Map.of()));
|
||||
when(roleService.list(anySet())).thenReturn(Flux.empty());
|
||||
|
||||
bindToRouterFunction(endpoint.endpoint())
|
||||
|
@ -219,22 +224,19 @@ class UserEndpointTest {
|
|||
metadata.setName("fake-user");
|
||||
var user = new User();
|
||||
user.setMetadata(metadata);
|
||||
Map<String, String> annotations =
|
||||
Map.of(User.ROLE_NAMES_ANNO, JsonUtils.objectToJson(Set.of("role-A")));
|
||||
user.getMetadata().setAnnotations(annotations);
|
||||
when(userService.getUser("fake-user")).thenReturn(Mono.just(user));
|
||||
Role role = new Role();
|
||||
role.setMetadata(new Metadata());
|
||||
role.getMetadata().setName("role-A");
|
||||
role.getMetadata().setName("fake-super-role");
|
||||
role.setRules(List.of());
|
||||
when(roleService.list(anySet())).thenReturn(Flux.just(role));
|
||||
when(roleService.list(Set.of("fake-super-role"), true)).thenReturn(Flux.just(role));
|
||||
webClient.get().uri("/users/-")
|
||||
.exchange()
|
||||
.expectStatus().isOk()
|
||||
.expectHeader().contentType(MediaType.APPLICATION_JSON)
|
||||
.expectBody(UserEndpoint.DetailedUser.class)
|
||||
.isEqualTo(new UserEndpoint.DetailedUser(user, List.of(role)));
|
||||
verify(roleService).list(eq(Set.of("role-A")));
|
||||
// verify(roleService).list(eq(Set.of("role-A")));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -385,16 +387,17 @@ class UserEndpointTest {
|
|||
"metadata": {
|
||||
"name": "test-A",
|
||||
"annotations": {
|
||||
"rbac.authorization.halo.run/ui-permissions": "[\\"permission-A\\"]"
|
||||
"rbac.authorization.halo.run/ui-permissions": \
|
||||
"[\\"permission-A\\", \\"permission-A\\"]"
|
||||
}
|
||||
},
|
||||
"rules": []
|
||||
}
|
||||
""", Role.class);
|
||||
when(roleService.listPermissions(eq(Set.of("test-A")))).thenReturn(Flux.just(roleA));
|
||||
when(userService.listRoles(eq("fake-user"))).thenReturn(
|
||||
Flux.fromIterable(List.of(roleA)));
|
||||
when(roleService.listDependenciesFlux(anySet())).thenReturn(Flux.just(roleA));
|
||||
when(roleService.getRolesByUsername("fake-user")).thenReturn(Flux.just("test-A"));
|
||||
when(roleService.list(Set.of("test-A"), true)).thenReturn(Flux.just(roleA));
|
||||
|
||||
webClient.get().uri("/users/fake-user/permissions")
|
||||
.exchange()
|
||||
|
@ -402,12 +405,10 @@ class UserEndpointTest {
|
|||
.isOk()
|
||||
.expectBody(UserEndpoint.UserPermission.class)
|
||||
.value(userPermission -> {
|
||||
assertEquals(Set.of(roleA), userPermission.getRoles());
|
||||
assertEquals(List.of(roleA), userPermission.getRoles());
|
||||
assertEquals(List.of(roleA), userPermission.getPermissions());
|
||||
assertEquals(Set.of("permission-A"), userPermission.getUiPermissions());
|
||||
assertEquals(List.of("permission-A"), userPermission.getUiPermissions());
|
||||
});
|
||||
|
||||
verify(userService, times(1)).listRoles(eq("fake-user"));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,195 +0,0 @@
|
|||
package run.halo.app.core.extension.reconciler;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.ArgumentMatchers.argThat;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.ArgumentMatchers.isA;
|
||||
import static org.mockito.ArgumentMatchers.same;
|
||||
import static org.mockito.Mockito.lenient;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
import static run.halo.app.core.extension.User.ROLE_NAMES_ANNO;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import run.halo.app.core.extension.RoleBinding;
|
||||
import run.halo.app.core.extension.RoleBinding.RoleRef;
|
||||
import run.halo.app.core.extension.RoleBinding.Subject;
|
||||
import run.halo.app.core.extension.User;
|
||||
import run.halo.app.extension.ExtensionClient;
|
||||
import run.halo.app.extension.Metadata;
|
||||
import run.halo.app.extension.controller.Reconciler;
|
||||
import run.halo.app.extension.controller.Reconciler.Result;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class RoleBindingReconcilerTest {
|
||||
|
||||
@Mock
|
||||
ExtensionClient client;
|
||||
|
||||
@InjectMocks
|
||||
RoleBindingReconciler reconciler;
|
||||
|
||||
final Result doNotReEnQueue = new Result(false, null);
|
||||
|
||||
@Test
|
||||
void shouldDoNothingIfRequestNotFound() {
|
||||
var bindingName = "fake-binding-name";
|
||||
when(client.fetch(RoleBinding.class, bindingName))
|
||||
.thenReturn(Optional.empty());
|
||||
|
||||
var result = reconciler.reconcile(new Reconciler.Request(bindingName));
|
||||
assertEquals(doNotReEnQueue, result);
|
||||
|
||||
verify(client, times(1)).fetch(same(RoleBinding.class), anyString());
|
||||
verify(client, never()).list(same(RoleBinding.class), any(), any());
|
||||
verify(client, never()).fetch(same(User.class), anyString());
|
||||
verify(client, never()).update(isA(User.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldDoNothingIfNotContainAnyUserSubject() {
|
||||
var bindingName = "fake-binding-name";
|
||||
var binding = mock(RoleBinding.class);
|
||||
|
||||
when(client.fetch(RoleBinding.class, bindingName))
|
||||
.thenReturn(Optional.of(binding));
|
||||
when(binding.getSubjects()).thenReturn(List.of());
|
||||
|
||||
var result = reconciler.reconcile(new Reconciler.Request(bindingName));
|
||||
assertEquals(doNotReEnQueue, result);
|
||||
|
||||
verify(client, times(1)).fetch(same(RoleBinding.class), anyString());
|
||||
verify(client, never()).list(same(RoleBinding.class), any(), any());
|
||||
verify(client, never()).fetch(same(User.class), anyString());
|
||||
verify(client, never()).update(isA(User.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldDoNothingIfUserNotFound() {
|
||||
var bindingName = "fake-binding-name";
|
||||
var userName = "fake-user-name";
|
||||
var binding = mock(RoleBinding.class);
|
||||
var subject = mock(Subject.class);
|
||||
|
||||
when(client.fetch(RoleBinding.class, bindingName))
|
||||
.thenReturn(Optional.of(binding));
|
||||
when(binding.getSubjects()).thenReturn(List.of(subject));
|
||||
when(subject.getKind()).thenReturn("User");
|
||||
when(subject.getName()).thenReturn(userName);
|
||||
when(client.fetch(User.class, userName)).thenReturn(Optional.empty());
|
||||
|
||||
var result = reconciler.reconcile(new Reconciler.Request(bindingName));
|
||||
assertEquals(doNotReEnQueue, result);
|
||||
|
||||
verify(client, times(1)).fetch(same(RoleBinding.class), anyString());
|
||||
verify(client, times(1)).list(same(RoleBinding.class), any(), any());
|
||||
verify(client, times(1)).fetch(same(User.class), eq(userName));
|
||||
|
||||
verify(client, never()).update(isA(User.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldUpdateRoleNamesIfNoBindingRelatedTheUser() {
|
||||
var bindingName = "fake-binding-name";
|
||||
var userName = "fake-user-name";
|
||||
var subject = mock(Subject.class);
|
||||
var binding = createRoleBinding("Role", "fake-role", false, subject);
|
||||
var user = mock(User.class);
|
||||
var userMetadata = mock(Metadata.class);
|
||||
|
||||
when(client.fetch(RoleBinding.class, bindingName))
|
||||
.thenReturn(Optional.of(binding));
|
||||
when(binding.getSubjects()).thenReturn(List.of(subject));
|
||||
when(subject.getKind()).thenReturn("User");
|
||||
when(subject.getName()).thenReturn(userName);
|
||||
when(client.fetch(User.class, userName)).thenReturn(Optional.of(user));
|
||||
when(user.getMetadata()).thenReturn(userMetadata);
|
||||
when(client.list(same(RoleBinding.class), any(), any())).thenReturn(List.of());
|
||||
|
||||
var result = reconciler.reconcile(new Reconciler.Request(bindingName));
|
||||
assertEquals(doNotReEnQueue, result);
|
||||
|
||||
verify(client, times(1)).fetch(same(RoleBinding.class), anyString());
|
||||
verify(client, times(1)).list(same(RoleBinding.class), any(), any());
|
||||
verify(client, times(1)).fetch(same(User.class), anyString());
|
||||
verify(client, times(1)).update(isA(User.class));
|
||||
|
||||
verify(userMetadata).setAnnotations(argThat(annotation -> {
|
||||
String roleNames = annotation.get(ROLE_NAMES_ANNO);
|
||||
return roleNames != null && roleNames.equals("[]");
|
||||
}));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldUpdateRoleNames() {
|
||||
var bindingName = "fake-binding-name";
|
||||
var userName = "fake-user-name";
|
||||
var subject = mock(Subject.class);
|
||||
when(subject.getKind()).thenReturn("User");
|
||||
when(subject.getName()).thenReturn(userName);
|
||||
when(subject.getApiGroup()).thenReturn("");
|
||||
|
||||
var user = mock(User.class);
|
||||
var userMetadata = mock(Metadata.class);
|
||||
var binding = createRoleBinding("Role", "fake-role", false, subject);
|
||||
|
||||
when(client.fetch(RoleBinding.class, bindingName))
|
||||
.thenReturn(Optional.of(binding));
|
||||
when(binding.getSubjects()).thenReturn(List.of(subject));
|
||||
|
||||
when(client.fetch(User.class, userName)).thenReturn(Optional.of(user));
|
||||
when(user.getMetadata()).thenReturn(userMetadata);
|
||||
var bindings = List.of(
|
||||
createRoleBinding("Role", "fake-role-01", false, subject),
|
||||
createRoleBinding("Role", "fake-role-03", false, subject),
|
||||
createRoleBinding("Role", "fake-role-02", false, subject),
|
||||
createRoleBinding("NotRole", "fake-role-04", false, subject),
|
||||
createRoleBinding("Role", "fake-role-05", true, subject)
|
||||
);
|
||||
when(client.list(same(RoleBinding.class), any(), any())).thenReturn(bindings);
|
||||
|
||||
var result = reconciler.reconcile(new Reconciler.Request(bindingName));
|
||||
assertEquals(doNotReEnQueue, result);
|
||||
|
||||
verify(client, times(1)).fetch(same(RoleBinding.class), anyString());
|
||||
verify(client, times(1)).list(same(RoleBinding.class), any(), any());
|
||||
verify(client, times(1)).fetch(same(User.class), anyString());
|
||||
verify(client, times(1)).update(isA(User.class));
|
||||
|
||||
verify(userMetadata).setAnnotations(argThat(annotation -> {
|
||||
var roleNames = annotation.get(ROLE_NAMES_ANNO);
|
||||
return roleNames != null && roleNames.equals("""
|
||||
["fake-role-01","fake-role-02","fake-role-03"]""");
|
||||
}));
|
||||
}
|
||||
|
||||
RoleBinding createRoleBinding(String roleRefKind,
|
||||
String roleRefName,
|
||||
boolean deleting,
|
||||
Subject subject) {
|
||||
var binding = mock(RoleBinding.class);
|
||||
var roleRef = mock(RoleRef.class);
|
||||
var metadata = mock(Metadata.class);
|
||||
lenient().when(roleRef.getKind()).thenReturn(roleRefKind);
|
||||
lenient().when(roleRef.getName()).thenReturn(roleRefName);
|
||||
lenient().when(roleRef.getApiGroup()).thenReturn("");
|
||||
lenient().when(metadata.getDeletionTimestamp()).thenReturn(deleting ? Instant.now() : null);
|
||||
lenient().when(binding.getRoleRef()).thenReturn(roleRef);
|
||||
lenient().when(binding.getMetadata()).thenReturn(metadata);
|
||||
lenient().when(binding.getSubjects()).thenReturn(List.of(subject));
|
||||
return binding;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,14 +1,12 @@
|
|||
package run.halo.app.core.extension.reconciler;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.assertArg;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.lenient;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
import static run.halo.app.core.extension.User.GROUP;
|
||||
import static run.halo.app.core.extension.User.KIND;
|
||||
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
|
@ -17,14 +15,11 @@ import java.util.Set;
|
|||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
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.extension.service.RoleService;
|
||||
import run.halo.app.extension.ExtensionClient;
|
||||
|
@ -60,58 +55,51 @@ class UserReconcilerTest {
|
|||
@BeforeEach
|
||||
void setUp() {
|
||||
lenient().when(notificationCenter.unsubscribe(any(), any())).thenReturn(Mono.empty());
|
||||
lenient().when(roleService.listRoleRefs(any())).thenReturn(Flux.empty());
|
||||
}
|
||||
|
||||
@Test
|
||||
void permalinkForFakeUser() throws URISyntaxException {
|
||||
when(externalUrlSupplier.get()).thenReturn(new URI("http://localhost:8090"));
|
||||
|
||||
when(roleService.getRolesByUsername("fake-user"))
|
||||
.thenReturn(Flux.empty());
|
||||
|
||||
when(client.fetch(eq(User.class), eq("fake-user")))
|
||||
.thenReturn(Optional.of(user("fake-user")));
|
||||
userReconciler.reconcile(new Reconciler.Request("fake-user"));
|
||||
verify(client, times(4)).update(any(User.class));
|
||||
|
||||
ArgumentCaptor<User> captor = ArgumentCaptor.forClass(User.class);
|
||||
verify(client, times(4)).update(captor.capture());
|
||||
assertThat(captor.getValue().getStatus().getPermalink())
|
||||
.isEqualTo("http://localhost:8090/authors/fake-user");
|
||||
verify(client).<User>update(assertArg(user ->
|
||||
assertEquals(
|
||||
"http://localhost:8090/authors/fake-user",
|
||||
user.getStatus().getPermalink()
|
||||
)
|
||||
));
|
||||
}
|
||||
|
||||
@Test
|
||||
void permalinkForAnonymousUser() {
|
||||
when(client.fetch(eq(User.class), eq(AnonymousUserConst.PRINCIPAL)))
|
||||
.thenReturn(Optional.of(user(AnonymousUserConst.PRINCIPAL)));
|
||||
when(roleService.getRolesByUsername(AnonymousUserConst.PRINCIPAL)).thenReturn(Flux.empty());
|
||||
userReconciler.reconcile(new Reconciler.Request(AnonymousUserConst.PRINCIPAL));
|
||||
verify(client, times(3)).update(any(User.class));
|
||||
verify(client).update(any(User.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void ensureRoleNamesAnno() {
|
||||
RoleBinding.RoleRef roleRef = new RoleBinding.RoleRef();
|
||||
roleRef.setName("fake-role");
|
||||
roleRef.setKind(Role.KIND);
|
||||
|
||||
roleRef.setApiGroup(Role.GROUP);
|
||||
RoleBinding.RoleRef notworkRef = new RoleBinding.RoleRef();
|
||||
notworkRef.setName("super-role");
|
||||
notworkRef.setKind("Fake");
|
||||
notworkRef.setApiGroup("fake.halo.run");
|
||||
|
||||
RoleBinding.Subject subject = new RoleBinding.Subject(KIND, "fake-user", GROUP);
|
||||
when(roleService.listRoleRefs(eq(subject))).thenReturn(Flux.just(roleRef, notworkRef));
|
||||
|
||||
when(roleService.getRolesByUsername("fake-user")).thenReturn(Flux.just("fake-role"));
|
||||
when(client.fetch(eq(User.class), eq("fake-user")))
|
||||
.thenReturn(Optional.of(user("fake-user")));
|
||||
|
||||
when(externalUrlSupplier.get()).thenReturn(URI.create("/"));
|
||||
|
||||
userReconciler.reconcile(new Reconciler.Request("fake-user"));
|
||||
ArgumentCaptor<User> captor = ArgumentCaptor.forClass(User.class);
|
||||
verify(client, times(4)).update(captor.capture());
|
||||
User user = captor.getAllValues().get(1);
|
||||
assertThat(user.getMetadata().getAnnotations().get(User.ROLE_NAMES_ANNO))
|
||||
.isEqualTo("[\"fake-role\"]");
|
||||
|
||||
verify(client).update(assertArg(user -> {
|
||||
assertEquals("""
|
||||
["fake-role"]\
|
||||
""",
|
||||
user.getMetadata().getAnnotations().get(User.ROLE_NAMES_ANNO));
|
||||
}));
|
||||
}
|
||||
|
||||
User user(String name) {
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package run.halo.app.core.extension.service;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.ArgumentMatchers.same;
|
||||
import static org.mockito.Mockito.eq;
|
||||
import static org.mockito.Mockito.never;
|
||||
|
@ -22,10 +21,12 @@ import org.junit.jupiter.api.extension.ExtendWith;
|
|||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.test.StepVerifier;
|
||||
import run.halo.app.core.extension.Role;
|
||||
import run.halo.app.core.extension.RoleBinding;
|
||||
import run.halo.app.extension.ListOptions;
|
||||
import run.halo.app.extension.Metadata;
|
||||
import run.halo.app.extension.ReactiveExtensionClient;
|
||||
import run.halo.app.infra.utils.JsonUtils;
|
||||
|
@ -39,7 +40,7 @@ import run.halo.app.infra.utils.JsonUtils;
|
|||
@ExtendWith(MockitoExtension.class)
|
||||
class DefaultRoleServiceTest {
|
||||
@Mock
|
||||
private ReactiveExtensionClient extensionClient;
|
||||
private ReactiveExtensionClient client;
|
||||
|
||||
@InjectMocks
|
||||
private DefaultRoleService roleService;
|
||||
|
@ -56,7 +57,7 @@ class DefaultRoleServiceTest {
|
|||
var roleNames = Set.of("role1");
|
||||
|
||||
|
||||
when(extensionClient.list(same(Role.class), any(), any()))
|
||||
when(client.listAll(same(Role.class), any(ListOptions.class), any(Sort.class)))
|
||||
.thenReturn(Flux.just(role1))
|
||||
.thenReturn(Flux.just(role2))
|
||||
.thenReturn(Flux.just(role3))
|
||||
|
@ -73,7 +74,11 @@ class DefaultRoleServiceTest {
|
|||
.verifyComplete();
|
||||
|
||||
// verify the mock invocations
|
||||
verify(extensionClient, times(4)).list(same(Role.class), any(), any());
|
||||
verify(client, times(4)).listAll(
|
||||
same(Role.class),
|
||||
any(ListOptions.class),
|
||||
any(Sort.class)
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -86,7 +91,7 @@ class DefaultRoleServiceTest {
|
|||
var roleNames = Set.of("role1");
|
||||
|
||||
// setup mocks
|
||||
when(extensionClient.list(same(Role.class), any(), any()))
|
||||
when(client.listAll(same(Role.class), any(ListOptions.class), any(Sort.class)))
|
||||
.thenReturn(Flux.just(role1))
|
||||
.thenReturn(Flux.just(role2))
|
||||
.thenReturn(Flux.just(role3))
|
||||
|
@ -103,7 +108,11 @@ class DefaultRoleServiceTest {
|
|||
.verifyComplete();
|
||||
|
||||
// verify the mock invocations
|
||||
verify(extensionClient, times(4)).list(same(Role.class), any(), any());
|
||||
verify(client, times(4)).listAll(
|
||||
same(Role.class),
|
||||
any(ListOptions.class),
|
||||
any(Sort.class)
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -118,13 +127,12 @@ class DefaultRoleServiceTest {
|
|||
|
||||
var roleNames = Set.of("role1");
|
||||
|
||||
when(extensionClient.list(same(Role.class), any(), any()))
|
||||
when(client.listAll(same(Role.class), any(ListOptions.class), any(Sort.class)))
|
||||
.thenReturn(Flux.just(role1))
|
||||
.thenReturn(Flux.just(role2))
|
||||
.thenReturn(Flux.just(role3))
|
||||
.thenReturn(Flux.just(role4))
|
||||
.thenReturn(Flux.empty())
|
||||
;
|
||||
.thenReturn(Flux.empty());
|
||||
|
||||
// call the method under test
|
||||
var result = roleService.listDependenciesFlux(roleNames);
|
||||
|
@ -138,7 +146,11 @@ class DefaultRoleServiceTest {
|
|||
.verifyComplete();
|
||||
|
||||
// verify the mock invocations
|
||||
verify(extensionClient, times(5)).list(same(Role.class), any(), any());
|
||||
verify(client, times(5)).listAll(
|
||||
same(Role.class),
|
||||
any(ListOptions.class),
|
||||
any(Sort.class)
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -153,7 +165,7 @@ class DefaultRoleServiceTest {
|
|||
|
||||
Set<String> roleNames = Set.of("role1");
|
||||
|
||||
when(extensionClient.list(same(Role.class), any(), any()))
|
||||
when(client.listAll(same(Role.class), any(ListOptions.class), any(Sort.class)))
|
||||
.thenReturn(Flux.just(role1))
|
||||
.thenReturn(Flux.just(role4, role2))
|
||||
.thenReturn(Flux.just(role3))
|
||||
|
@ -171,7 +183,7 @@ class DefaultRoleServiceTest {
|
|||
.verifyComplete();
|
||||
|
||||
// verify the mock invocations
|
||||
verify(extensionClient, times(4)).list(same(Role.class), any(), any());
|
||||
verify(client, times(4)).listAll(same(Role.class), any(), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -186,7 +198,7 @@ class DefaultRoleServiceTest {
|
|||
|
||||
Set<String> roleNames = Set.of("role2");
|
||||
|
||||
when(extensionClient.list(same(Role.class), any(), any()))
|
||||
when(client.listAll(same(Role.class), any(ListOptions.class), any(Sort.class)))
|
||||
.thenReturn(Flux.just(role2))
|
||||
.thenReturn(Flux.just(role3))
|
||||
.thenReturn(Flux.empty());
|
||||
|
@ -201,17 +213,17 @@ class DefaultRoleServiceTest {
|
|||
.verifyComplete();
|
||||
|
||||
// verify the mock invocations
|
||||
verify(extensionClient, times(3)).list(same(Role.class), any(), any());
|
||||
verify(client, times(3)).listAll(
|
||||
same(Role.class),
|
||||
any(ListOptions.class),
|
||||
any(Sort.class)
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
void listDependenciesWithNullParam() {
|
||||
var result = roleService.listDependenciesFlux(null);
|
||||
|
||||
when(extensionClient.list(same(Role.class), any(), any()))
|
||||
.thenReturn(Flux.empty())
|
||||
.thenReturn(Flux.empty());
|
||||
|
||||
// verify the result
|
||||
StepVerifier.create(result)
|
||||
.verifyComplete();
|
||||
|
@ -221,7 +233,11 @@ class DefaultRoleServiceTest {
|
|||
.verifyComplete();
|
||||
|
||||
// verify the mock invocations
|
||||
verify(extensionClient, never()).fetch(eq(Role.class), anyString());
|
||||
verify(client, never()).listAll(
|
||||
eq(Role.class),
|
||||
any(ListOptions.class),
|
||||
any(Sort.class)
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -232,7 +248,7 @@ class DefaultRoleServiceTest {
|
|||
|
||||
var roleNames = Set.of("role1");
|
||||
|
||||
when(extensionClient.list(same(Role.class), any(), any()))
|
||||
when(client.listAll(same(Role.class), any(ListOptions.class), any(Sort.class)))
|
||||
.thenReturn(Flux.just(role1))
|
||||
.thenReturn(Flux.just(role2))
|
||||
.thenReturn(Flux.just(role4))
|
||||
|
@ -248,7 +264,11 @@ class DefaultRoleServiceTest {
|
|||
.verifyComplete();
|
||||
|
||||
// verify the mock invocations
|
||||
verify(extensionClient, times(4)).list(same(Role.class), any(), any());
|
||||
verify(client, times(4)).listAll(
|
||||
same(Role.class),
|
||||
any(ListOptions.class),
|
||||
any(Sort.class)
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
@ -8,9 +8,7 @@ import static org.mockito.ArgumentMatchers.anyString;
|
|||
import static org.mockito.ArgumentMatchers.argThat;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.ArgumentMatchers.isA;
|
||||
import static org.mockito.ArgumentMatchers.same;
|
||||
import static org.mockito.Mockito.doReturn;
|
||||
import static org.mockito.Mockito.lenient;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.spy;
|
||||
|
@ -19,7 +17,6 @@ import static org.mockito.Mockito.verify;
|
|||
import static org.mockito.Mockito.when;
|
||||
import static run.halo.app.extension.GroupVersionKind.fromExtension;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Nested;
|
||||
|
@ -35,6 +32,7 @@ import reactor.core.publisher.Mono;
|
|||
import reactor.test.StepVerifier;
|
||||
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.event.user.PasswordChangedEvent;
|
||||
import run.halo.app.extension.Metadata;
|
||||
|
@ -45,7 +43,6 @@ import run.halo.app.infra.SystemSetting;
|
|||
import run.halo.app.infra.exception.AccessDeniedException;
|
||||
import run.halo.app.infra.exception.DuplicateNameException;
|
||||
import run.halo.app.infra.exception.UserNotFoundException;
|
||||
import run.halo.app.infra.utils.JsonUtils;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class UserServiceImplTest {
|
||||
|
@ -62,6 +59,9 @@ class UserServiceImplTest {
|
|||
@Mock
|
||||
ApplicationEventPublisher eventPublisher;
|
||||
|
||||
@Mock
|
||||
RoleService roleService;
|
||||
|
||||
@InjectMocks
|
||||
UserServiceImpl userService;
|
||||
|
||||
|
@ -108,119 +108,6 @@ class UserServiceImplTest {
|
|||
verify(eventPublisher).publishEvent(any(PasswordChangedEvent.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldListRolesIfUserFoundInExtension() {
|
||||
User fakeUser = new User();
|
||||
Metadata metadata = new Metadata();
|
||||
metadata.setName("faker");
|
||||
fakeUser.setMetadata(metadata);
|
||||
fakeUser.setSpec(new User.UserSpec());
|
||||
|
||||
when(client.list(eq(RoleBinding.class), any(), any())).thenReturn(
|
||||
Flux.fromIterable(getRoleBindings()));
|
||||
Role roleA = new Role();
|
||||
Metadata metadataA = new Metadata();
|
||||
metadataA.setName("test-A");
|
||||
roleA.setMetadata(metadataA);
|
||||
|
||||
Role roleB = new Role();
|
||||
Metadata metadataB = new Metadata();
|
||||
metadataB.setName("test-B");
|
||||
roleB.setMetadata(metadataB);
|
||||
|
||||
Role roleC = new Role();
|
||||
Metadata metadataC = new Metadata();
|
||||
metadataC.setName("ddd");
|
||||
roleC.setMetadata(metadataC);
|
||||
|
||||
when(client.fetch(eq(Role.class), eq("test-A"))).thenReturn(Mono.just(roleA));
|
||||
when(client.fetch(eq(Role.class), eq("test-B"))).thenReturn(Mono.just(roleB));
|
||||
lenient().when(client.fetch(eq(Role.class), eq("ddd"))).thenReturn(Mono.just(roleC));
|
||||
|
||||
StepVerifier.create(userService.listRoles("faker"))
|
||||
.expectNext(roleA)
|
||||
.expectNext(roleB)
|
||||
.verifyComplete();
|
||||
|
||||
verify(client, times(1)).list(eq(RoleBinding.class), any(), any());
|
||||
|
||||
verify(client, times(1)).fetch(eq(Role.class), eq("test-A"));
|
||||
verify(client, times(1)).fetch(eq(Role.class), eq("test-B"));
|
||||
verify(client, times(0)).fetch(eq(Role.class), eq("ddd"));
|
||||
}
|
||||
|
||||
List<RoleBinding> getRoleBindings() {
|
||||
String bindA = """
|
||||
{
|
||||
|
||||
"apiVersion": "v1alpha1",
|
||||
"kind": "RoleBinding",
|
||||
"metadata": {
|
||||
"name": "bind-A"
|
||||
},
|
||||
"subjects": [{
|
||||
"kind": "User",
|
||||
"name": "faker",
|
||||
"apiGroup": ""
|
||||
}],
|
||||
"roleRef": {
|
||||
"kind": "Role",
|
||||
"name": "test-A",
|
||||
"apiGroup": ""
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
String bindB = """
|
||||
{
|
||||
|
||||
"apiVersion": "v1alpha1",
|
||||
"kind": "RoleBinding",
|
||||
"metadata": {
|
||||
"name": "bind-B"
|
||||
},
|
||||
"subjects": [{
|
||||
"kind": "User",
|
||||
"name": "faker",
|
||||
"apiGroup": ""
|
||||
},
|
||||
{
|
||||
"kind": "User",
|
||||
"name": "zhangsan",
|
||||
"apiGroup": ""
|
||||
}],
|
||||
"roleRef": {
|
||||
"kind": "Role",
|
||||
"name": "test-B",
|
||||
"apiGroup": ""
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
String bindC = """
|
||||
{
|
||||
"apiVersion": "v1alpha1",
|
||||
"kind": "RoleBinding",
|
||||
"metadata": {
|
||||
"name": "bind-C"
|
||||
},
|
||||
"subjects": [{
|
||||
"kind": "User",
|
||||
"name": "faker",
|
||||
"apiGroup": ""
|
||||
}],
|
||||
"roleRef": {
|
||||
"kind": "Fake",
|
||||
"name": "ddd",
|
||||
"apiGroup": ""
|
||||
}
|
||||
}
|
||||
""";
|
||||
return List.of(JsonUtils.jsonToObject(bindA, RoleBinding.class),
|
||||
JsonUtils.jsonToObject(bindB, RoleBinding.class),
|
||||
JsonUtils.jsonToObject(bindC, RoleBinding.class));
|
||||
}
|
||||
|
||||
@Nested
|
||||
@DisplayName("UpdateWithRawPassword")
|
||||
class UpdateWithRawPasswordTest {
|
||||
|
@ -312,6 +199,9 @@ class UserServiceImplTest {
|
|||
|
||||
User createUser(String password) {
|
||||
var user = new User();
|
||||
Metadata metadata = new Metadata();
|
||||
metadata.setName("fake-user");
|
||||
user.setMetadata(metadata);
|
||||
user.setSpec(new User.UserSpec());
|
||||
user.getSpec().setPassword(password);
|
||||
return user;
|
||||
|
@ -336,46 +226,47 @@ class UserServiceImplTest {
|
|||
|
||||
@Test
|
||||
void shouldCreateRoleBindingIfNotExist() {
|
||||
var user = createUser("fake-password");
|
||||
when(client.get(User.class, "fake-user"))
|
||||
.thenReturn(Mono.just(createUser("fake-password")));
|
||||
when(client.list(same(RoleBinding.class), any(), any())).thenReturn(Flux.empty());
|
||||
.thenReturn(Mono.just(user));
|
||||
when(roleService.listRoleBindings(any(Subject.class))).thenReturn(Flux.empty());
|
||||
when(client.create(isA(RoleBinding.class))).thenReturn(
|
||||
Mono.just(mock(RoleBinding.class)));
|
||||
when(client.update(user)).thenReturn(Mono.just(user));
|
||||
|
||||
var grantRolesMono = userService.grantRoles("fake-user", Set.of("fake-role"));
|
||||
StepVerifier.create(grantRolesMono)
|
||||
.expectNextCount(1)
|
||||
.verifyComplete();
|
||||
|
||||
verify(client).get(User.class, "fake-user");
|
||||
verify(client).list(same(RoleBinding.class), any(), any());
|
||||
verify(client).create(isA(RoleBinding.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldDeleteRoleBindingIfNotProvided() {
|
||||
when(client.get(User.class, "fake-user")).thenReturn(Mono.just(mock(User.class)));
|
||||
var user = createUser("fake-password");
|
||||
when(client.get(User.class, "fake-user")).thenReturn(Mono.just(user));
|
||||
var notProvidedRoleBinding = RoleBinding.create("fake-user", "non-provided-fake-role");
|
||||
var existingRoleBinding = RoleBinding.create("fake-user", "fake-role");
|
||||
when(client.list(same(RoleBinding.class), any(), any())).thenReturn(
|
||||
Flux.fromIterable(List.of(notProvidedRoleBinding, existingRoleBinding)));
|
||||
when(roleService.listRoleBindings(any(Subject.class)))
|
||||
.thenReturn(Flux.just(notProvidedRoleBinding, existingRoleBinding));
|
||||
when(client.delete(isA(RoleBinding.class)))
|
||||
.thenReturn(Mono.just(mock(RoleBinding.class)));
|
||||
when(client.update(user)).thenReturn(Mono.just(user));
|
||||
|
||||
StepVerifier.create(userService.grantRoles("fake-user", Set.of("fake-role")))
|
||||
.expectNextCount(1)
|
||||
.verifyComplete();
|
||||
|
||||
verify(client).get(User.class, "fake-user");
|
||||
verify(client).list(same(RoleBinding.class), any(), any());
|
||||
verify(client).delete(notProvidedRoleBinding);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldUpdateRoleBindingIfExists() {
|
||||
when(client.get(User.class, "fake-user")).thenReturn(Mono.just(mock(User.class)));
|
||||
var user = createUser("fake-password");
|
||||
when(client.get(User.class, "fake-user")).thenReturn(Mono.just(user));
|
||||
// add another subject
|
||||
var anotherSubject = new RoleBinding.Subject();
|
||||
var anotherSubject = new Subject();
|
||||
anotherSubject.setName("another-fake-user");
|
||||
anotherSubject.setKind(User.KIND);
|
||||
anotherSubject.setApiGroup(User.GROUP);
|
||||
|
@ -384,17 +275,16 @@ class UserServiceImplTest {
|
|||
|
||||
var existingRoleBinding = RoleBinding.create("fake-user", "fake-role");
|
||||
|
||||
when(client.list(same(RoleBinding.class), any(), any())).thenReturn(
|
||||
Flux.fromIterable(List.of(notProvidedRoleBinding, existingRoleBinding)));
|
||||
when(roleService.listRoleBindings(any(Subject.class)))
|
||||
.thenReturn(Flux.just(notProvidedRoleBinding, existingRoleBinding));
|
||||
when(client.update(isA(RoleBinding.class)))
|
||||
.thenReturn(Mono.just(mock(RoleBinding.class)));
|
||||
when(client.update(user)).thenReturn(Mono.just(user));
|
||||
|
||||
StepVerifier.create(userService.grantRoles("fake-user", Set.of("fake-role")))
|
||||
.expectNextCount(1)
|
||||
.verifyComplete();
|
||||
|
||||
verify(client).get(User.class, "fake-user");
|
||||
verify(client).list(same(RoleBinding.class), any(), any());
|
||||
verify(client).update(notProvidedRoleBinding);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,7 +4,6 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
|
|||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
@ -24,8 +23,6 @@ import reactor.core.publisher.Flux;
|
|||
import reactor.core.publisher.Mono;
|
||||
import reactor.test.StepVerifier;
|
||||
import run.halo.app.core.extension.Role;
|
||||
import run.halo.app.core.extension.RoleBinding.RoleRef;
|
||||
import run.halo.app.core.extension.RoleBinding.Subject;
|
||||
import run.halo.app.core.extension.service.RoleService;
|
||||
import run.halo.app.core.extension.service.UserService;
|
||||
import run.halo.app.extension.Metadata;
|
||||
|
@ -85,17 +82,8 @@ class DefaultUserDetailServiceTest {
|
|||
void shouldFindUserDetailsByExistingUsername() {
|
||||
var foundUser = createFakeUser();
|
||||
|
||||
var roleGvk = new Role().groupVersionKind();
|
||||
var roleRef = new RoleRef();
|
||||
roleRef.setKind(roleGvk.kind());
|
||||
roleRef.setApiGroup(roleGvk.group());
|
||||
roleRef.setName("fake-role");
|
||||
|
||||
var userGvk = foundUser.groupVersionKind();
|
||||
var subject = new Subject(userGvk.kind(), "faker", userGvk.group());
|
||||
|
||||
when(userService.getUser("faker")).thenReturn(Mono.just(foundUser));
|
||||
when(roleService.listRoleRefs(subject)).thenReturn(Flux.just(roleRef));
|
||||
when(roleService.getRolesByUsername("faker")).thenReturn(Flux.just("fake-role"));
|
||||
|
||||
var userDetailsMono = userDetailService.findByUsername("faker");
|
||||
|
||||
|
@ -115,7 +103,7 @@ class DefaultUserDetailServiceTest {
|
|||
void shouldFindHaloUserDetailsWith2faDisabledWhen2faNotEnabled() {
|
||||
var fakeUser = createFakeUser();
|
||||
when(userService.getUser("faker")).thenReturn(Mono.just(fakeUser));
|
||||
when(roleService.listRoleRefs(any())).thenReturn(Flux.empty());
|
||||
when(roleService.getRolesByUsername("faker")).thenReturn(Flux.empty());
|
||||
userDetailService.findByUsername("faker")
|
||||
.as(StepVerifier::create)
|
||||
.assertNext(userDetails -> {
|
||||
|
@ -130,7 +118,7 @@ class DefaultUserDetailServiceTest {
|
|||
var fakeUser = createFakeUser();
|
||||
fakeUser.getSpec().setTwoFactorAuthEnabled(true);
|
||||
when(userService.getUser("faker")).thenReturn(Mono.just(fakeUser));
|
||||
when(roleService.listRoleRefs(any())).thenReturn(Flux.empty());
|
||||
when(roleService.getRolesByUsername("faker")).thenReturn(Flux.empty());
|
||||
userDetailService.findByUsername("faker")
|
||||
.as(StepVerifier::create)
|
||||
.assertNext(userDetails -> {
|
||||
|
@ -146,7 +134,7 @@ class DefaultUserDetailServiceTest {
|
|||
fakeUser.getSpec().setTwoFactorAuthEnabled(true);
|
||||
fakeUser.getSpec().setTotpEncryptedSecret("fake-totp-encrypted-secret");
|
||||
when(userService.getUser("faker")).thenReturn(Mono.just(fakeUser));
|
||||
when(roleService.listRoleRefs(any())).thenReturn(Flux.empty());
|
||||
when(roleService.getRolesByUsername("faker")).thenReturn(Flux.empty());
|
||||
userDetailService.findByUsername("faker")
|
||||
.as(StepVerifier::create)
|
||||
.assertNext(userDetails -> {
|
||||
|
@ -163,7 +151,7 @@ class DefaultUserDetailServiceTest {
|
|||
fakeUser.getSpec().setTwoFactorAuthEnabled(true);
|
||||
fakeUser.getSpec().setTotpEncryptedSecret("fake-totp-encrypted-secret");
|
||||
when(userService.getUser("faker")).thenReturn(Mono.just(fakeUser));
|
||||
when(roleService.listRoleRefs(any())).thenReturn(Flux.empty());
|
||||
when(roleService.getRolesByUsername("faker")).thenReturn(Flux.empty());
|
||||
userDetailService.findByUsername("faker")
|
||||
.as(StepVerifier::create)
|
||||
.assertNext(userDetails -> {
|
||||
|
@ -173,50 +161,14 @@ class DefaultUserDetailServiceTest {
|
|||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldFindUserDetailsByExistingUsernameButKindOfRoleRefIsNotRole() {
|
||||
var foundUser = createFakeUser();
|
||||
|
||||
var roleRef = new RoleRef();
|
||||
roleRef.setKind("FakeRole");
|
||||
roleRef.setApiGroup("fake.halo.run");
|
||||
roleRef.setName("fake-role");
|
||||
|
||||
var userGvk = foundUser.groupVersionKind();
|
||||
var subject = new Subject(userGvk.kind(), "faker", userGvk.group());
|
||||
|
||||
when(userService.getUser("faker")).thenReturn(Mono.just(foundUser));
|
||||
when(roleService.listRoleRefs(subject)).thenReturn(Flux.just(roleRef));
|
||||
|
||||
var userDetailsMono = userDetailService.findByUsername("faker");
|
||||
|
||||
StepVerifier.create(userDetailsMono)
|
||||
.expectSubscription()
|
||||
.assertNext(gotUser -> {
|
||||
assertEquals(foundUser.getMetadata().getName(), gotUser.getUsername());
|
||||
assertEquals(foundUser.getSpec().getPassword(), gotUser.getPassword());
|
||||
assertEquals(2, gotUser.getAuthorities().size());
|
||||
assertEquals(
|
||||
Set.of("ROLE_anonymous", "ROLE_authenticated"),
|
||||
authorityListToSet(gotUser.getAuthorities())
|
||||
);
|
||||
})
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldFindUserDetailsByExistingUsernameButWithoutAnyRoles() {
|
||||
var foundUser = createFakeUser();
|
||||
|
||||
var userGvk = foundUser.groupVersionKind();
|
||||
var subject = new Subject(userGvk.kind(), "faker", userGvk.group());
|
||||
|
||||
when(userService.getUser("faker")).thenReturn(Mono.just(foundUser));
|
||||
when(roleService.listRoleRefs(subject)).thenReturn(Flux.empty());
|
||||
when(roleService.getRolesByUsername("faker")).thenReturn(Flux.empty());
|
||||
|
||||
var userDetailsMono = userDetailService.findByUsername("faker");
|
||||
|
||||
StepVerifier.create(userDetailsMono)
|
||||
StepVerifier.create(userDetailService.findByUsername("faker"))
|
||||
.expectSubscription()
|
||||
.assertNext(gotUser -> {
|
||||
assertEquals(foundUser.getMetadata().getName(), gotUser.getUsername());
|
||||
|
@ -240,6 +192,13 @@ class DefaultUserDetailServiceTest {
|
|||
.verify();
|
||||
}
|
||||
|
||||
Role createRole(String roleName) {
|
||||
var role = new Role();
|
||||
role.setMetadata(new Metadata());
|
||||
role.getMetadata().setName(roleName);
|
||||
return role;
|
||||
}
|
||||
|
||||
UserDetails createFakeUserDetails() {
|
||||
return User.builder()
|
||||
.username("faker")
|
||||
|
|
|
@ -29,7 +29,7 @@ class AuthorityUtilsTest {
|
|||
|
||||
var roles = authoritiesToRoles(authorities);
|
||||
|
||||
assertEquals(Set.of("admin", "owner", "manager"), roles);
|
||||
assertEquals(Set.of("admin", "owner", "manager", "faker", "system:read"), roles);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
Loading…
Reference in New Issue