Skip to content

Commit

Permalink
users with administrative permissions do not need to be audited
Browse files Browse the repository at this point in the history
  • Loading branch information
LIlGG committed Apr 11, 2024
1 parent 9fa9a0c commit dc981ac
Show file tree
Hide file tree
Showing 6 changed files with 188 additions and 13 deletions.
6 changes: 5 additions & 1 deletion console/src/components/MomentEdit.vue
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,11 @@ const supportVideoTypes: string[] = ["video/*"];
const supportAudioTypes: string[] = ["audio/*"];
const accepts = [...supportImageTypes, ...supportVideoTypes, ...supportAudioTypes];
const accepts = [
...supportImageTypes,
...supportVideoTypes,
...supportAudioTypes,
];
const mediumWhitelist: Map<string, MomentMediaTypeEnum> = new Map([
["image", "PHOTO"],
Expand Down
9 changes: 8 additions & 1 deletion console/src/uc/MomentEdit.vue
Original file line number Diff line number Diff line change
Expand Up @@ -144,11 +144,18 @@ const supportImageTypes: string[] = [
const supportVideoTypes: string[] = ["video/*"];
const accepts = [...supportImageTypes, ...supportVideoTypes];
const supportAudioTypes: string[] = ["audio/*"];
const accepts = [
...supportImageTypes,
...supportVideoTypes,
...supportAudioTypes,
];
const mediumWhitelist: Map<string, MomentMediaTypeEnum> = new Map([
["image", "PHOTO"],
["video", "VIDEO"],
["audio", "AUDIO"],
]);
const onAttachmentsSelect = async (attachments: AttachmentLike[]) => {
Expand Down
8 changes: 8 additions & 0 deletions src/main/java/run/halo/moments/service/RoleService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package run.halo.moments.service;

import java.util.Collection;
import reactor.core.publisher.Mono;

public interface RoleService {
Mono<Boolean> contains(Collection<String> source, Collection<String> candidates);
}
103 changes: 103 additions & 0 deletions src/main/java/run/halo/moments/service/impl/DefaultRoleService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
package run.halo.moments.service.impl;

import static run.halo.app.extension.Comparators.compareCreationTimestamp;

import com.fasterxml.jackson.core.type.TypeReference;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.lang.NonNull;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import run.halo.app.core.extension.Role;
import run.halo.app.extension.MetadataUtil;
import run.halo.app.extension.ReactiveExtensionClient;
import run.halo.app.infra.utils.JsonUtils;
import run.halo.moments.service.RoleService;
import run.halo.moments.util.AuthorityUtils;

@Slf4j
@Component
@RequiredArgsConstructor
public class DefaultRoleService implements RoleService {

private final ReactiveExtensionClient client;

@Override
public Mono<Boolean> contains(Collection<String> source, Collection<String> candidates) {
if (source.contains(AuthorityUtils.SUPER_ROLE_NAME)) {
return Mono.just(true);
}
return listDependencies(new HashSet<>(source))
.map(role -> role.getMetadata().getName())
.collect(Collectors.toSet())
.map(roleNames -> roleNames.containsAll(candidates));
}

private Flux<Role> listDependencies(Set<String> names) {
var visited = new HashSet<String>();
return listRoles(names)
.expand(role -> {
var name = role.getMetadata().getName();
if (visited.contains(name)) {
return Flux.empty();
}
if (log.isTraceEnabled()) {
log.trace("Expand role: {}", role.getMetadata().getName());
}
visited.add(name);
var annotations = MetadataUtil.nullSafeAnnotations(role);
var dependenciesJson = annotations.get(Role.ROLE_DEPENDENCIES_ANNO);
var dependencies = stringToList(dependenciesJson);

return Flux.fromIterable(dependencies)
.filter(dep -> !visited.contains(dep))
.collect(Collectors.<String>toSet())
.flatMapMany(this::listRoles);
})
.concatWith(Flux.defer(() -> listAggregatedRoles(visited)));
}

private Flux<Role> listAggregatedRoles(Set<String> roleNames) {
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)));
};
return client.list(Role.class, predicate, compareCreationTimestamp(true));
}

private Flux<Role> listRoles(Set<String> names) {
if (CollectionUtils.isEmpty(names)) {
return Flux.empty();
}

Predicate<Role> predicate = role -> names.contains(role.getMetadata().getName());
return client.list(Role.class, predicate, compareCreationTimestamp(true));
}

@NonNull
private List<String> stringToList(String str) {
if (StringUtils.isBlank(str)) {
return Collections.emptyList();
}
return JsonUtils.jsonToObject(str,
new TypeReference<>() {
});
}
}
38 changes: 27 additions & 11 deletions src/main/java/run/halo/moments/uc/UcMomentEndpoint.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
import static org.springdoc.core.fn.builders.requestbody.Builder.requestBodyBuilder;

import io.swagger.v3.oas.annotations.enums.ParameterIn;
import java.time.Instant;
import java.util.Set;
import lombok.AllArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import org.springdoc.core.fn.builders.schema.Builder;
Expand All @@ -29,6 +31,8 @@
import run.halo.moments.Moment;
import run.halo.moments.MomentQuery;
import run.halo.moments.service.MomentService;
import run.halo.moments.service.RoleService;
import run.halo.moments.util.AuthorityUtils;

/**
* A custom endpoint for {@link run.halo.moments.Moment}.
Expand All @@ -42,6 +46,8 @@ public class UcMomentEndpoint implements CustomEndpoint {

private final MomentService momentService;

private final RoleService roleService;

@Override
public RouterFunction<ServerResponse> endpoint() {
final var tag = groupVersion() + "/moment";
Expand Down Expand Up @@ -148,7 +154,7 @@ private Mono<ServerResponse> getMyMoment(ServerRequest request) {

private Mono<Moment> getMyMoment(String momentName) {
return getCurrentUser()
.flatMap(username -> momentService.getByUsername(momentName, username)
.flatMap(user -> momentService.getByUsername(momentName, user.getName())
.switchIfEmpty(
Mono.error(() -> new ResponseStatusException(HttpStatus.NOT_FOUND,
"The moment was not found or deleted"))
Expand All @@ -158,33 +164,43 @@ private Mono<Moment> getMyMoment(String momentName) {

private Mono<ServerResponse> createMyMoment(ServerRequest request) {
return getCurrentUser()
.flatMap(username -> request.bodyToMono(Moment.class)
.doOnNext(post -> {
post.getSpec().setOwner(username);
// TODO: 如果是具有审核权限的用户,则不需要审核
.flatMap(user -> request.bodyToMono(Moment.class)
.flatMap(post -> {
post.getSpec().setApproved(false);
}))
post.getSpec().setOwner(user.getName());
var roles = AuthorityUtils.authoritiesToRoles(user.getAuthorities());
return roleService.contains(roles,
Set.of(AuthorityUtils.MOMENT_MANAGEMENT_ROLE_NAME))
.doOnNext(result -> {
if (result) {
// If it is a user with audit authority, there is no need to review.
post.getSpec().setApproved(true);
post.getSpec().setApprovedTime(Instant.now());
}
})
.thenReturn(post);
})
)
.flatMap(momentService::create)
.flatMap(moment -> ServerResponse.ok().bodyValue(moment));
}

private Mono<ServerResponse> listMyMoment(ServerRequest request) {
return getCurrentUser()
.map(username -> new MomentQuery(request.exchange(), username))
.map(user -> new MomentQuery(request.exchange(), user.getName()))
.flatMap(momentService::listMoment)
.flatMap(listedMoments -> ServerResponse.ok().bodyValue(listedMoments));
}

private Mono<String> getCurrentUser() {
private Mono<Authentication> getCurrentUser() {
return ReactiveSecurityContextHolder.getContext()
.map(SecurityContext::getAuthentication)
.map(Authentication::getName);
.map(SecurityContext::getAuthentication);
}

private Mono<ServerResponse> listMyTags(ServerRequest request) {
String name = request.queryParam("name").orElse(null);
return getCurrentUser()
.map(username -> new MomentQuery(request.exchange(), username))
.map(user -> new MomentQuery(request.exchange(), user.getName()))
.flatMapMany(momentService::listAllTags)
.filter(tagName -> StringUtils.isBlank(name) || StringUtils.containsIgnoreCase(tagName,
name))
Expand Down
37 changes: 37 additions & 0 deletions src/main/java/run/halo/moments/util/AuthorityUtils.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package run.halo.moments.util;

import java.util.Collection;
import java.util.Set;
import java.util.stream.Collectors;
import org.apache.commons.lang3.StringUtils;
import org.springframework.security.core.GrantedAuthority;
/**
* Utility methods for manipulating GrantedAuthority collection.
*
* @author johnniang
*/
public enum AuthorityUtils {
;
public static final String ROLE_PREFIX = "ROLE_";
public static final String SUPER_ROLE_NAME = "super-role";

public static final String MOMENT_MANAGEMENT_ROLE_NAME = "role-template-moments-manage";

/**
* Converts an array of GrantedAuthority objects to a role set.
*
* @return a Set of the Strings obtained from each call to
* GrantedAuthority.getAuthority() and filtered by prefix "ROLE_".
*/
public static Set<String> authoritiesToRoles(
Collection<? extends GrantedAuthority> authorities) {
return authorities.stream()
.map(GrantedAuthority::getAuthority)
.filter(authority -> StringUtils.startsWith(authority, ROLE_PREFIX))
.map(authority -> StringUtils.removeStart(authority, ROLE_PREFIX))
.collect(Collectors.toSet());
}
public static boolean containsSuperRole(Collection<String> roles) {
return roles.contains(SUPER_ROLE_NAME);
}
}

0 comments on commit dc981ac

Please sign in to comment.