Fix the incorrect list options builder while listing aggregated roles (#6530)

#### What type of PR is this?

/kind bug
/area core
/milestone 2.19.0

#### What this PR does / why we need it:

This PR corrects list options builder for listing aggregated roles, because I wrongly used the label selector in <https://github.com/halo-dev/halo/pull/6471>.

#### Special notes for your reviewer:

1. Try to install the plugin <https://www.halo.run/store/apps/app-YXyaD>
2. Enable the plugin and enable setting `匿名评论需要验证码`
3. **Anonymous** request any of posts with comment enabled
4. Check the captcha in comment area

#### Does this PR introduce a user-facing change?

```release-note
修复可能无法正常访问插件提供的接口的问题
```
pull/6533/head
John Niang 2024-08-27 15:09:18 +08:00 committed by GitHub
parent ab6c20a204
commit b6222f48a4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 71 additions and 22 deletions

View File

@ -190,15 +190,12 @@ public class DefaultRoleService implements RoleService {
if (CollectionUtils.isEmpty(roleNames)) { if (CollectionUtils.isEmpty(roleNames)) {
return Flux.empty(); return Flux.empty();
} }
var listOptionsBuilder = Optional.ofNullable(additionalListOptions) var listOptions = Optional.ofNullable(additionalListOptions)
.map(ListOptions::builder) .map(ListOptions::builder)
.orElseGet(ListOptions::builder); .orElseGet(ListOptions::builder)
roleNames.stream() .andQuery(QueryFactory.in("labels.aggregateToRoles", roleNames))
.map(roleName -> Role.ROLE_AGGREGATE_LABEL_PREFIX + roleName) .build();
.forEach( return client.listAll(Role.class, listOptions, ExtensionUtil.defaultSort());
label -> listOptionsBuilder.labelSelector().eq(label, Boolean.TRUE.toString())
);
return client.listAll(Role.class, listOptionsBuilder.build(), ExtensionUtil.defaultSort());
} }
Predicate<RoleBinding> getRoleBindingPredicate(Subject targetSubject) { Predicate<RoleBinding> getRoleBindingPredicate(Subject targetSubject) {

View File

@ -3,6 +3,7 @@ package run.halo.app.infra;
import static org.apache.commons.lang3.BooleanUtils.isTrue; import static org.apache.commons.lang3.BooleanUtils.isTrue;
import static org.apache.commons.lang3.BooleanUtils.toStringTrueFalse; import static org.apache.commons.lang3.BooleanUtils.toStringTrueFalse;
import static org.apache.commons.lang3.ObjectUtils.defaultIfNull; import static org.apache.commons.lang3.ObjectUtils.defaultIfNull;
import static run.halo.app.core.extension.Role.ROLE_AGGREGATE_LABEL_PREFIX;
import static run.halo.app.extension.index.IndexAttributeFactory.multiValueAttribute; import static run.halo.app.extension.index.IndexAttributeFactory.multiValueAttribute;
import static run.halo.app.extension.index.IndexAttributeFactory.simpleAttribute; import static run.halo.app.extension.index.IndexAttributeFactory.simpleAttribute;
@ -75,7 +76,23 @@ public class SchemeInitializer implements ApplicationListener<ApplicationContext
public void onApplicationEvent(@NonNull ApplicationContextInitializedEvent event) { public void onApplicationEvent(@NonNull ApplicationContextInitializedEvent event) {
var schemeManager = createSchemeManager(event); var schemeManager = createSchemeManager(event);
schemeManager.register(Role.class); schemeManager.register(Role.class, is -> {
is.add(new IndexSpec()
.setName("labels.aggregateToRoles")
.setIndexFunc(multiValueAttribute(Role.class,
role -> Optional.ofNullable(role.getMetadata().getLabels())
.map(labels -> labels.keySet()
.stream()
.filter(key -> key.startsWith(ROLE_AGGREGATE_LABEL_PREFIX))
.filter(key -> Boolean.parseBoolean(labels.get(key)))
.map(
key -> StringUtils.removeStart(key, ROLE_AGGREGATE_LABEL_PREFIX)
)
.collect(Collectors.toSet())
)
.orElseGet(Set::of)))
);
});
// plugin.halo.run // plugin.halo.run
schemeManager.register(Plugin.class); schemeManager.register(Plugin.class);

View File

@ -6,19 +6,18 @@ import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
import static org.springframework.security.test.web.reactive.server.SecurityMockServerConfigurers.csrf; import static org.springframework.security.test.web.reactive.server.SecurityMockServerConfigurers.csrf;
import static org.springframework.web.reactive.function.server.RequestPredicates.GET; import static run.halo.app.core.extension.Role.ROLE_AGGREGATE_LABEL_PREFIX;
import static org.springframework.web.reactive.function.server.RequestPredicates.PUT;
import static org.springframework.web.reactive.function.server.RequestPredicates.accept;
import static org.springframework.web.reactive.function.server.RouterFunctions.route;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.reactive.AutoConfigureWebTestClient; import org.springframework.boot.test.autoconfigure.web.reactive.AutoConfigureWebTestClient;
import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.TestConfiguration; import org.springframework.boot.test.context.TestConfiguration;
import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.boot.test.mock.mockito.SpyBean;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Import; import org.springframework.context.annotation.Import;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
@ -26,8 +25,10 @@ import org.springframework.lang.NonNull;
import org.springframework.security.core.userdetails.ReactiveUserDetailsPasswordService; import org.springframework.security.core.userdetails.ReactiveUserDetailsPasswordService;
import org.springframework.security.core.userdetails.ReactiveUserDetailsService; import org.springframework.security.core.userdetails.ReactiveUserDetailsService;
import org.springframework.security.test.context.support.WithMockUser; import org.springframework.security.test.context.support.WithMockUser;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.web.reactive.server.WebTestClient; import org.springframework.test.web.reactive.server.WebTestClient;
import org.springframework.web.reactive.function.server.RouterFunction; import org.springframework.web.reactive.function.server.RouterFunction;
import org.springframework.web.reactive.function.server.RouterFunctions;
import org.springframework.web.reactive.function.server.ServerRequest; import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.reactive.function.server.ServerResponse; import org.springframework.web.reactive.function.server.ServerResponse;
import reactor.core.publisher.Flux; import reactor.core.publisher.Flux;
@ -35,26 +36,31 @@ import reactor.core.publisher.Mono;
import run.halo.app.core.extension.Role; import run.halo.app.core.extension.Role;
import run.halo.app.core.extension.Role.PolicyRule; import run.halo.app.core.extension.Role.PolicyRule;
import run.halo.app.core.extension.service.RoleService; import run.halo.app.core.extension.service.RoleService;
import run.halo.app.extension.ExtensionClient;
import run.halo.app.extension.Metadata; import run.halo.app.extension.Metadata;
import run.halo.app.infra.AnonymousUserConst; import run.halo.app.infra.AnonymousUserConst;
@SpringBootTest @SpringBootTest
@AutoConfigureWebTestClient @AutoConfigureWebTestClient
@DirtiesContext
@Import(AuthorizationTest.TestConfig.class) @Import(AuthorizationTest.TestConfig.class)
class AuthorizationTest { class AuthorizationTest {
@Autowired @Autowired
WebTestClient webClient; WebTestClient webClient;
@MockBean @SpyBean
ReactiveUserDetailsService userDetailsService; ReactiveUserDetailsService userDetailsService;
@MockBean @SpyBean
ReactiveUserDetailsPasswordService userDetailsPasswordService; ReactiveUserDetailsPasswordService userDetailsPasswordService;
@MockBean @SpyBean
RoleService roleService; RoleService roleService;
@Autowired
ExtensionClient client;
@BeforeEach @BeforeEach
void setUp() { void setUp() {
webClient = webClient.mutateWith(csrf()); webClient = webClient.mutateWith(csrf());
@ -122,16 +128,45 @@ class AuthorizationTest {
verify(roleService).listDependenciesFlux(anySet()); verify(roleService).listDependenciesFlux(anySet());
} }
@Test
void anonymousUserShouldAccessResourcesByAggregatedRoles() {
// create a role
var role = new Role();
role.setMetadata(new Metadata());
role.getMetadata().setName("fake-role-with-aggregate-to-anonymous");
role.getMetadata().setLabels(new HashMap<>(Map.of(
ROLE_AGGREGATE_LABEL_PREFIX + AnonymousUserConst.Role, "true"
)));
role.setRules(new ArrayList<>());
var policyRule = new PolicyRule.Builder()
.apiGroups("fake.halo.run")
.verbs("list")
.resources("fakes")
.build();
role.getRules().add(policyRule);
client.create(role);
webClient.get().uri("/apis/fake.halo.run/v1/fakes").exchange()
.expectStatus()
.isOk();
}
@TestConfiguration @TestConfiguration
static class TestConfig { static class TestConfig {
@Bean @Bean
public RouterFunction<ServerResponse> postRoute() { public RouterFunction<ServerResponse> postRoute() {
return route( return RouterFunctions.route()
GET("/apis/fake.halo.run/v1/posts").and(accept(MediaType.APPLICATION_JSON)), .GET("/apis/fake.halo.run/v1/posts", request -> ServerResponse.ok()
this::queryPosts).andRoute( .contentType(MediaType.TEXT_PLAIN)
PUT("/apis/fake.halo.run/v1/posts/{name}").and(accept(MediaType.APPLICATION_JSON)), .bodyValue("returned posts")
this::updatePost); )
.PUT("/apis/fake.halo.run/v1/posts/{name}", request -> ServerResponse.ok()
.contentType(MediaType.TEXT_PLAIN)
.bodyValue("updated post " + request.pathVariable("name"))
)
.GET("/apis/fake.halo.run/v1/fakes", request -> ServerResponse.ok().build())
.build();
} }
@NonNull @NonNull