mirror of https://github.com/halo-dev/halo
refactor: plugin custom api generate rules (#2211)
* refactor: plugin custom api generate rules * refactor: boolean convert * fix: unit testpull/2212/head
parent
27435a1aeb
commit
f7945081a5
|
@ -6,7 +6,6 @@ import lombok.Data;
|
||||||
import lombok.EqualsAndHashCode;
|
import lombok.EqualsAndHashCode;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.ToString;
|
import lombok.ToString;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
|
||||||
import run.halo.app.extension.AbstractExtension;
|
import run.halo.app.extension.AbstractExtension;
|
||||||
import run.halo.app.extension.GVK;
|
import run.halo.app.extension.GVK;
|
||||||
|
|
||||||
|
@ -74,19 +73,13 @@ public class Role extends AbstractExtension {
|
||||||
*/
|
*/
|
||||||
final String[] verbs;
|
final String[] verbs;
|
||||||
|
|
||||||
/**
|
|
||||||
* If the plugin name exists, it means that the API is provided by the plugin.
|
|
||||||
*/
|
|
||||||
final String pluginName;
|
|
||||||
|
|
||||||
public PolicyRule() {
|
public PolicyRule() {
|
||||||
this(null, null, null, null, null, null);
|
this(null, null, null, null, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public PolicyRule(String pluginName, String[] apiGroups, String[] resources,
|
public PolicyRule(String[] apiGroups, String[] resources,
|
||||||
String[] resourceNames,
|
String[] resourceNames,
|
||||||
String[] nonResourceURLs, String[] verbs) {
|
String[] nonResourceURLs, String[] verbs) {
|
||||||
this.pluginName = StringUtils.defaultString(pluginName);
|
|
||||||
this.apiGroups = nullElseEmpty(apiGroups);
|
this.apiGroups = nullElseEmpty(apiGroups);
|
||||||
this.resources = nullElseEmpty(resources);
|
this.resources = nullElseEmpty(resources);
|
||||||
this.resourceNames = nullElseEmpty(resourceNames);
|
this.resourceNames = nullElseEmpty(resourceNames);
|
||||||
|
@ -114,11 +107,6 @@ public class Role extends AbstractExtension {
|
||||||
|
|
||||||
String pluginName;
|
String pluginName;
|
||||||
|
|
||||||
public Builder pluginName(String pluginName) {
|
|
||||||
this.pluginName = pluginName;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder apiGroups(String... apiGroups) {
|
public Builder apiGroups(String... apiGroups) {
|
||||||
this.apiGroups = apiGroups;
|
this.apiGroups = apiGroups;
|
||||||
return this;
|
return this;
|
||||||
|
@ -145,7 +133,7 @@ public class Role extends AbstractExtension {
|
||||||
}
|
}
|
||||||
|
|
||||||
public PolicyRule build() {
|
public PolicyRule build() {
|
||||||
return new PolicyRule(pluginName, apiGroups, resources, resourceNames,
|
return new PolicyRule(apiGroups, resources, resourceNames,
|
||||||
nonResourceURLs,
|
nonResourceURLs,
|
||||||
verbs);
|
verbs);
|
||||||
}
|
}
|
||||||
|
|
|
@ -121,6 +121,7 @@ public class PluginRequestMappingHandlerMapping extends RequestMappingHandlerMap
|
||||||
|
|
||||||
protected String buildPrefix(String pluginId, String version) {
|
protected String buildPrefix(String pluginId, String version) {
|
||||||
GroupVersion groupVersion = GroupVersion.parseAPIVersion(version);
|
GroupVersion groupVersion = GroupVersion.parseAPIVersion(version);
|
||||||
return String.format("/api/%s/plugins/%s", groupVersion.version(), pluginId);
|
return String.format("/apis/plugin.api.halo.run/%s/plugins/%s", groupVersion.version(),
|
||||||
|
pluginId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -66,5 +66,5 @@ public interface Attributes {
|
||||||
*/
|
*/
|
||||||
String getPath();
|
String getPath();
|
||||||
|
|
||||||
String pluginName();
|
String getSubName();
|
||||||
}
|
}
|
||||||
|
|
|
@ -69,7 +69,7 @@ public class AttributesRecord implements Attributes {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String pluginName() {
|
public String getSubName() {
|
||||||
return requestInfo.getPluginName();
|
return requestInfo.getSubName();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,6 @@ import java.util.Objects;
|
||||||
import org.apache.commons.lang3.ArrayUtils;
|
import org.apache.commons.lang3.ArrayUtils;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import run.halo.app.core.extension.Role;
|
import run.halo.app.core.extension.Role;
|
||||||
import run.halo.app.core.extension.Role.PolicyRule;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author guqing
|
* @author guqing
|
||||||
|
@ -35,17 +34,26 @@ public class RbacRequestEvaluation {
|
||||||
combinedResource =
|
combinedResource =
|
||||||
requestAttributes.getResource() + "/" + requestAttributes.getSubresource();
|
requestAttributes.getResource() + "/" + requestAttributes.getSubresource();
|
||||||
}
|
}
|
||||||
|
|
||||||
return verbMatches(rule, requestAttributes.getVerb())
|
return verbMatches(rule, requestAttributes.getVerb())
|
||||||
&& apiGroupMatches(rule, requestAttributes.getApiGroup())
|
&& apiGroupMatches(rule, requestAttributes.getApiGroup())
|
||||||
&& resourceMatches(rule, combinedResource, requestAttributes.getSubresource())
|
&& resourceMatches(rule, combinedResource, requestAttributes.getSubresource())
|
||||||
&& resourceNameMatches(rule, requestAttributes.getName())
|
&& resourceNameMatches(rule,
|
||||||
&& pluginNameMatches(rule, requestAttributes.pluginName());
|
combineResourceName(requestAttributes.getName(), requestAttributes.getSubName()));
|
||||||
}
|
}
|
||||||
return verbMatches(rule, requestAttributes.getVerb())
|
return verbMatches(rule, requestAttributes.getVerb())
|
||||||
&& nonResourceURLMatches(rule, requestAttributes.getPath());
|
&& nonResourceURLMatches(rule, requestAttributes.getPath());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private String combineResourceName(String name, String subName) {
|
||||||
|
if (StringUtils.isBlank(name)) {
|
||||||
|
return subName;
|
||||||
|
}
|
||||||
|
if (StringUtils.isBlank(subName)) {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
return name + "/" + subName;
|
||||||
|
}
|
||||||
|
|
||||||
protected boolean verbMatches(Role.PolicyRule rule, String requestedVerb) {
|
protected boolean verbMatches(Role.PolicyRule rule, String requestedVerb) {
|
||||||
for (String ruleVerb : rule.getVerbs()) {
|
for (String ruleVerb : rule.getVerbs()) {
|
||||||
if (Objects.equals(ruleVerb, WildCard.VerbAll)) {
|
if (Objects.equals(ruleVerb, WildCard.VerbAll)) {
|
||||||
|
@ -71,7 +79,7 @@ public class RbacRequestEvaluation {
|
||||||
}
|
}
|
||||||
|
|
||||||
protected boolean resourceMatches(Role.PolicyRule rule, String combinedRequestedResource,
|
protected boolean resourceMatches(Role.PolicyRule rule, String combinedRequestedResource,
|
||||||
String requestedSubresource) {
|
String requestedSubresource) {
|
||||||
for (String ruleResource : rule.getResources()) {
|
for (String ruleResource : rule.getResources()) {
|
||||||
// if everything is allowed, we match
|
// if everything is allowed, we match
|
||||||
if (Objects.equals(ruleResource, WildCard.ResourceAll)) {
|
if (Objects.equals(ruleResource, WildCard.ResourceAll)) {
|
||||||
|
@ -125,8 +133,4 @@ public class RbacRequestEvaluation {
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected boolean pluginNameMatches(PolicyRule rule, String pluginName) {
|
|
||||||
return StringUtils.equals(rule.getPluginName(), pluginName);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,34 +17,40 @@ import org.springframework.http.server.reactive.ServerHttpRequest;
|
||||||
public class RequestInfo {
|
public class RequestInfo {
|
||||||
boolean isResourceRequest;
|
boolean isResourceRequest;
|
||||||
final String path;
|
final String path;
|
||||||
String pluginName;
|
String namespace;
|
||||||
String verb;
|
String verb;
|
||||||
String apiPrefix;
|
String apiPrefix;
|
||||||
String apiGroup;
|
String apiGroup;
|
||||||
String apiVersion;
|
String apiVersion;
|
||||||
String resource;
|
String resource;
|
||||||
String subresource;
|
|
||||||
String name;
|
String name;
|
||||||
|
|
||||||
|
String subresource;
|
||||||
|
|
||||||
|
String subName;
|
||||||
|
|
||||||
String[] parts;
|
String[] parts;
|
||||||
|
|
||||||
public RequestInfo(boolean isResourceRequest, String path, String verb) {
|
public RequestInfo(boolean isResourceRequest, String path, String verb) {
|
||||||
this(isResourceRequest, path, null, verb, null, null, null, null, null, null, null);
|
this(isResourceRequest, path, null, verb, null, null, null, null, null, null, null, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public RequestInfo(boolean isResourceRequest, String path, String pluginName, String verb,
|
public RequestInfo(boolean isResourceRequest, String path, String namespace, String verb,
|
||||||
String apiPrefix,
|
String apiPrefix,
|
||||||
String apiGroup,
|
String apiGroup,
|
||||||
String apiVersion, String resource, String subresource, String name,
|
String apiVersion, String resource, String name, String subresource, String subName,
|
||||||
String[] parts) {
|
String[] parts) {
|
||||||
this.isResourceRequest = isResourceRequest;
|
this.isResourceRequest = isResourceRequest;
|
||||||
this.path = StringUtils.defaultString(path, "");
|
this.path = StringUtils.defaultString(path, "");
|
||||||
this.pluginName = StringUtils.defaultString(pluginName, "");
|
this.namespace = StringUtils.defaultString(namespace, "");
|
||||||
this.verb = StringUtils.defaultString(verb, "");
|
this.verb = StringUtils.defaultString(verb, "");
|
||||||
this.apiPrefix = StringUtils.defaultString(apiPrefix, "");
|
this.apiPrefix = StringUtils.defaultString(apiPrefix, "");
|
||||||
this.apiGroup = StringUtils.defaultString(apiGroup, "");
|
this.apiGroup = StringUtils.defaultString(apiGroup, "");
|
||||||
this.apiVersion = StringUtils.defaultString(apiVersion, "");
|
this.apiVersion = StringUtils.defaultString(apiVersion, "");
|
||||||
this.resource = StringUtils.defaultString(resource, "");
|
this.resource = StringUtils.defaultString(resource, "");
|
||||||
this.subresource = StringUtils.defaultString(subresource, "");
|
this.subresource = StringUtils.defaultString(subresource, "");
|
||||||
|
this.subName = StringUtils.defaultString(subName, "");
|
||||||
this.name = StringUtils.defaultString(name, "");
|
this.name = StringUtils.defaultString(name, "");
|
||||||
this.parts = Objects.requireNonNullElseGet(parts, () -> new String[] {});
|
this.parts = Objects.requireNonNullElseGet(parts, () -> new String[] {});
|
||||||
}
|
}
|
||||||
|
|
|
@ -52,10 +52,11 @@ public class RequestInfoFactory {
|
||||||
* Valid Inputs:
|
* Valid Inputs:
|
||||||
* <p>Resource paths</p>
|
* <p>Resource paths</p>
|
||||||
* <pre>
|
* <pre>
|
||||||
* /api/{version}/plugins
|
* /apis/{api-group}/{version}/namespaces
|
||||||
* /api/{version}/plugins/{pluginName}
|
* /api/{version}/namespaces
|
||||||
* /api/{version}/plugins/{pluginName}/{resource}
|
* /api/{version}/namespaces/{namespace}
|
||||||
* /api/{version}/plugins/{pluginName}/{resource}/{resourceName}
|
* /api/{version}/namespaces/{namespace}/{resource}
|
||||||
|
* /api/{version}/namespaces/{namespace}/{resource}/{resourceName}
|
||||||
* /api/{version}/{resource}
|
* /api/{version}/{resource}
|
||||||
* /api/{version}/{resource}/{resourceName}
|
* /api/{version}/{resource}/{resourceName}
|
||||||
* </pre>
|
* </pre>
|
||||||
|
@ -138,22 +139,22 @@ public class RequestInfoFactory {
|
||||||
default -> "";
|
default -> "";
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
// URL forms: /plugins/{plugin-name}/{kind}/*, where parts are adjusted to be relative
|
// URL forms: /namespaces/{namespace}/{kind}/*, where parts are adjusted to be relative
|
||||||
// to kind
|
// to kind
|
||||||
if (Objects.equals(currentParts[0], "plugins")
|
Set<String> namespaceSubresources = Set.of("status", "finalize");
|
||||||
&& StringUtils.isEmpty(requestInfo.getApiGroup())) {
|
if (Objects.equals(currentParts[0], "namespaces")) {
|
||||||
if (currentParts.length > 1) {
|
if (currentParts.length > 1) {
|
||||||
requestInfo.pluginName = currentParts[1];
|
requestInfo.namespace = currentParts[1];
|
||||||
|
|
||||||
// if there is another step after the plugin name and it is not a known
|
// if there is another step after the namespace name and it is not a known
|
||||||
// plugins subresource
|
// namespace subresource
|
||||||
// move currentParts to include it as a resource in its own right
|
// move currentParts to include it as a resource in its own right
|
||||||
if (currentParts.length > 2) {
|
if (currentParts.length > 2 && !namespaceSubresources.contains(currentParts[2])) {
|
||||||
currentParts = Arrays.copyOfRange(currentParts, 2, currentParts.length);
|
currentParts = Arrays.copyOfRange(currentParts, 2, currentParts.length);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
requestInfo.pluginName = "";
|
requestInfo.namespace = "";
|
||||||
}
|
}
|
||||||
|
|
||||||
// parsing successful, so we now know the proper value for .Parts
|
// parsing successful, so we now know the proper value for .Parts
|
||||||
|
@ -163,6 +164,10 @@ public class RequestInfoFactory {
|
||||||
if (requestInfo.parts.length >= 3 && !specialVerbs.contains(
|
if (requestInfo.parts.length >= 3 && !specialVerbs.contains(
|
||||||
requestInfo.verb)) {
|
requestInfo.verb)) {
|
||||||
requestInfo.subresource = requestInfo.parts[2];
|
requestInfo.subresource = requestInfo.parts[2];
|
||||||
|
// if there is another step after the subresource name and it is not a known
|
||||||
|
if (requestInfo.parts.length >= 4) {
|
||||||
|
requestInfo.subName = requestInfo.parts[3];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (requestInfo.parts.length >= 2) {
|
if (requestInfo.parts.length >= 2) {
|
||||||
|
@ -177,7 +182,7 @@ public class RequestInfoFactory {
|
||||||
// verb is a list or a watch
|
// verb is a list or a watch
|
||||||
if (requestInfo.name.length() == 0 && "get".equals(requestInfo.verb)) {
|
if (requestInfo.name.length() == 0 && "get".equals(requestInfo.verb)) {
|
||||||
var watch = request.getQueryParams().getFirst("watch");
|
var watch = request.getQueryParams().getFirst("watch");
|
||||||
if (isWatch(watch)) {
|
if (Boolean.parseBoolean(watch)) {
|
||||||
requestInfo.verb = "watch";
|
requestInfo.verb = "watch";
|
||||||
} else {
|
} else {
|
||||||
requestInfo.verb = "list";
|
requestInfo.verb = "list";
|
||||||
|
@ -185,17 +190,15 @@ public class RequestInfoFactory {
|
||||||
}
|
}
|
||||||
// if there's no name on the request and we thought it was a deleted before, then the
|
// if there's no name on the request and we thought it was a deleted before, then the
|
||||||
// actual verb is deletecollection
|
// actual verb is deletecollection
|
||||||
if (requestInfo.name.length() == 0
|
if (Objects.equals(requestInfo.verb, "delete")) {
|
||||||
&& Objects.equals(requestInfo.verb, "delete")) {
|
var deleteAll = request.getQueryParams().getFirst("all");
|
||||||
requestInfo.verb = "deletecollection";
|
if (Boolean.parseBoolean(deleteAll)) {
|
||||||
|
requestInfo.verb = "deletecollection";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return requestInfo;
|
return requestInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean isWatch(String requestParam) {
|
|
||||||
return "1".equals(requestParam) || "true".equals(requestParam);
|
|
||||||
}
|
|
||||||
|
|
||||||
private String[] splitPath(String path) {
|
private String[] splitPath(String path) {
|
||||||
path = StringUtils.strip(path, "/");
|
path = StringUtils.strip(path, "/");
|
||||||
if (StringUtils.isEmpty(path)) {
|
if (StringUtils.isEmpty(path)) {
|
||||||
|
|
|
@ -69,7 +69,8 @@ class PluginRequestMappingHandlerMappingTest {
|
||||||
assertThat(info).isNotNull();
|
assertThat(info).isNotNull();
|
||||||
assertThat(info.getPatternsCondition().getPatterns()).isEqualTo(
|
assertThat(info.getPatternsCondition().getPatterns()).isEqualTo(
|
||||||
Collections.singleton(
|
Collections.singleton(
|
||||||
new PathPatternParser().parse("/api/v1alpha1/plugins/fakePlugin/user/{id}")));
|
new PathPatternParser().parse(
|
||||||
|
"/apis/plugin.api.halo.run/v1alpha1/plugins/fakePlugin/user/{id}")));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -94,7 +95,7 @@ class PluginRequestMappingHandlerMappingTest {
|
||||||
List<RequestMappingInfo> mappings = handlerMapping.getMappings("fakePlugin");
|
List<RequestMappingInfo> mappings = handlerMapping.getMappings("fakePlugin");
|
||||||
assertThat(mappings).hasSize(1);
|
assertThat(mappings).hasSize(1);
|
||||||
assertThat(mappings.get(0).toString()).isEqualTo(
|
assertThat(mappings.get(0).toString()).isEqualTo(
|
||||||
"{GET /api/v1alpha1/plugins/fakePlugin/user/{id}}");
|
"{GET /apis/plugin.api.halo.run/v1alpha1/plugins/fakePlugin/user/{id}}");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -120,7 +121,8 @@ class PluginRequestMappingHandlerMappingTest {
|
||||||
|
|
||||||
// get handler by mock exchange
|
// get handler by mock exchange
|
||||||
ServerWebExchange exchange =
|
ServerWebExchange exchange =
|
||||||
MockServerWebExchange.from(get("/api/v1alpha1/plugins/fakePlugin/foo"));
|
MockServerWebExchange.from(
|
||||||
|
get("/apis/plugin.api.halo.run/v1alpha1/plugins/fakePlugin/foo"));
|
||||||
HandlerMethod hm = (HandlerMethod) this.handlerMapping.getHandler(exchange).block();
|
HandlerMethod hm = (HandlerMethod) this.handlerMapping.getHandler(exchange).block();
|
||||||
|
|
||||||
assertThat(hm).isNotNull();
|
assertThat(hm).isNotNull();
|
||||||
|
@ -135,7 +137,7 @@ class PluginRequestMappingHandlerMappingTest {
|
||||||
Method expected =
|
Method expected =
|
||||||
ResolvableMethod.on(TestController.class).annot(getMapping("/foo").params("p")).build();
|
ResolvableMethod.on(TestController.class).annot(getMapping("/foo").params("p")).build();
|
||||||
|
|
||||||
String requestPath = "/api/v1alpha1/plugins/fakePlugin/foo?p=anything";
|
String requestPath = "/apis/plugin.api.halo.run/v1alpha1/plugins/fakePlugin/foo?p=anything";
|
||||||
ServerWebExchange exchange = MockServerWebExchange.from(get(requestPath));
|
ServerWebExchange exchange = MockServerWebExchange.from(get(requestPath));
|
||||||
HandlerMethod hm = (HandlerMethod) this.handlerMapping.getHandler(exchange).block();
|
HandlerMethod hm = (HandlerMethod) this.handlerMapping.getHandler(exchange).block();
|
||||||
|
|
||||||
|
@ -150,7 +152,7 @@ class PluginRequestMappingHandlerMappingTest {
|
||||||
Method expected =
|
Method expected =
|
||||||
ResolvableMethod.on(TestController.class).annot(getMapping("")).build();
|
ResolvableMethod.on(TestController.class).annot(getMapping("")).build();
|
||||||
|
|
||||||
String requestPath = "/api/v1alpha1/plugins/fakePlugin";
|
String requestPath = "/apis/plugin.api.halo.run/v1alpha1/plugins/fakePlugin";
|
||||||
ServerWebExchange exchange = MockServerWebExchange.from(get(requestPath));
|
ServerWebExchange exchange = MockServerWebExchange.from(get(requestPath));
|
||||||
HandlerMethod hm = (HandlerMethod) this.handlerMapping.getHandler(exchange).block();
|
HandlerMethod hm = (HandlerMethod) this.handlerMapping.getHandler(exchange).block();
|
||||||
|
|
||||||
|
@ -163,7 +165,7 @@ class PluginRequestMappingHandlerMappingTest {
|
||||||
// register handler methods first
|
// register handler methods first
|
||||||
handlerMapping.registerHandlerMethods("fakePlugin", new TestController());
|
handlerMapping.registerHandlerMethods("fakePlugin", new TestController());
|
||||||
|
|
||||||
String requestPath = "/api/v1alpha1/plugins/fakePlugin/bar";
|
String requestPath = "/apis/plugin.api.halo.run/v1alpha1/plugins/fakePlugin/bar";
|
||||||
ServerWebExchange exchange = MockServerWebExchange.from(post(requestPath));
|
ServerWebExchange exchange = MockServerWebExchange.from(post(requestPath));
|
||||||
Mono<Object> mono = this.handlerMapping.getHandler(exchange);
|
Mono<Object> mono = this.handlerMapping.getHandler(exchange);
|
||||||
|
|
||||||
|
|
|
@ -28,11 +28,10 @@ class PolicyRuleTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void constructPolicyRule() throws JsonProcessingException, JSONException {
|
public void constructPolicyRule() throws JsonProcessingException, JSONException {
|
||||||
Role.PolicyRule policyRule = new Role.PolicyRule(null, null, null, null, null, null);
|
Role.PolicyRule policyRule = new Role.PolicyRule(null, null, null, null, null);
|
||||||
assertThat(policyRule).isNotNull();
|
assertThat(policyRule).isNotNull();
|
||||||
JSONAssert.assertEquals("""
|
JSONAssert.assertEquals("""
|
||||||
{
|
{
|
||||||
"pluginName": "",
|
|
||||||
"apiGroups": [],
|
"apiGroups": [],
|
||||||
"resources": [],
|
"resources": [],
|
||||||
"resourceNames": [],
|
"resourceNames": [],
|
||||||
|
@ -46,7 +45,6 @@ class PolicyRuleTest {
|
||||||
Role.PolicyRule policyByBuilder = new Role.PolicyRule.Builder().build();
|
Role.PolicyRule policyByBuilder = new Role.PolicyRule.Builder().build();
|
||||||
JSONAssert.assertEquals("""
|
JSONAssert.assertEquals("""
|
||||||
{
|
{
|
||||||
"pluginName": "",
|
|
||||||
"apiGroups": [],
|
"apiGroups": [],
|
||||||
"resources": [],
|
"resources": [],
|
||||||
"resourceNames": [],
|
"resourceNames": [],
|
||||||
|
@ -58,7 +56,6 @@ class PolicyRuleTest {
|
||||||
true);
|
true);
|
||||||
|
|
||||||
Role.PolicyRule policyNonNull = new Role.PolicyRule.Builder()
|
Role.PolicyRule policyNonNull = new Role.PolicyRule.Builder()
|
||||||
.pluginName("fakePluginName")
|
|
||||||
.apiGroups("group")
|
.apiGroups("group")
|
||||||
.resources("resource-1", "resource-2")
|
.resources("resource-1", "resource-2")
|
||||||
.resourceNames("resourceName")
|
.resourceNames("resourceName")
|
||||||
|
@ -68,7 +65,6 @@ class PolicyRuleTest {
|
||||||
|
|
||||||
JsonNode expected = objectMapper.readTree("""
|
JsonNode expected = objectMapper.readTree("""
|
||||||
{
|
{
|
||||||
"pluginName": "fakePluginName",
|
|
||||||
"apiGroups": [
|
"apiGroups": [
|
||||||
"group"
|
"group"
|
||||||
],
|
],
|
||||||
|
|
|
@ -87,23 +87,28 @@ public class RequestInfoResolverTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void pluginsScopedAndPluginManage() {
|
void pluginsScopedAndPluginManage() {
|
||||||
List<SuccessCase> testCases =
|
List<CustomSuccessCase> testCases =
|
||||||
List.of(new SuccessCase("DELETE", "/apis/extensions/v1/plugins/other/posts",
|
List.of(
|
||||||
"delete", "apis", "extensions", "v1", "", "plugins", "posts", "other",
|
new CustomSuccessCase("DELETE", "/apis/plugin.api.halo.run/v1/plugins/other/posts",
|
||||||
|
"delete", "apis", "plugin.api.halo.run", "v1", "", "plugins", "posts", "", "",
|
||||||
new String[] {"plugins", "other", "posts"}),
|
new String[] {"plugins", "other", "posts"}),
|
||||||
|
|
||||||
// api group identification
|
// api group identification
|
||||||
new SuccessCase("POST", "/apis/extensions/v1/plugins/other/posts", "create", "apis",
|
new CustomSuccessCase("POST",
|
||||||
"extensions", "v1", "", "plugins", "posts", "other",
|
"/apis/plugin.api.halo.run/v1/plugins/other/posts/foo",
|
||||||
new String[] {"plugins", "other", "posts"}),
|
"create", "apis",
|
||||||
|
"plugin.api.halo.run", "v1", "", "plugins", "posts", "other", "foo",
|
||||||
|
new String[] {"plugins", "other", "posts", "foo"}),
|
||||||
|
|
||||||
// api version identification
|
// api version identification
|
||||||
new SuccessCase("POST", "/apis/extensions/v1beta3/plugins/other/posts", "create",
|
new CustomSuccessCase("POST",
|
||||||
"apis", "extensions", "v1beta3", "", "plugins", "posts", "other",
|
"/apis/plugin.api.halo.run/v1beta3/plugins/other/posts/bar", "create",
|
||||||
new String[] {"plugins", "other", "posts"}));
|
"apis", "plugin.api.halo.run", "v1beta3", "", "plugins", "posts", "other",
|
||||||
|
"bar",
|
||||||
|
new String[] {"plugins", "other", "posts", "bar"}));
|
||||||
|
|
||||||
// 以 /apis 开头的 plugins 资源为 core 中管理插件使用的资源
|
// 以 /apis 开头的 plugins 资源为 core 中管理插件使用的资源
|
||||||
for (SuccessCase successCase : testCases) {
|
for (CustomSuccessCase successCase : testCases) {
|
||||||
var request =
|
var request =
|
||||||
method(HttpMethod.valueOf(successCase.method),
|
method(HttpMethod.valueOf(successCase.method),
|
||||||
successCase.url).build();
|
successCase.url).build();
|
||||||
|
@ -112,21 +117,19 @@ public class RequestInfoResolverTest {
|
||||||
assertRequestInfoCase(successCase, requestInfo);
|
assertRequestInfoCase(successCase, requestInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
List<SuccessCase> pluginScopedCases =
|
List<CustomSuccessCase> pluginScopedCases =
|
||||||
List.of(new SuccessCase("DELETE", "/api/v1/plugins/other/posts",
|
List.of(
|
||||||
"deletecollection", "api", "", "v1", "other", "posts", "", "",
|
new CustomSuccessCase("DELETE", "/apis/plugin.api.halo.run/v1/plugins/other/posts",
|
||||||
new String[] {"posts"}),
|
"delete", "apis", "plugin.api.halo.run", "v1", "", "plugins", "posts",
|
||||||
|
"other", "", new String[] {"plugins", "other", "posts"}),
|
||||||
|
|
||||||
// api group identification
|
// api group identification
|
||||||
new SuccessCase("POST", "/api/v1/plugins/other/posts", "create", "api",
|
new CustomSuccessCase("POST",
|
||||||
"", "v1", "other", "posts", "", "", new String[] {"posts"}),
|
"/apis/plugin.api.halo.run/v1/plugins/other/posts/some-name", "create", "apis",
|
||||||
|
"plugin.api.halo.run", "v1", "other", "plugins", "posts", "other", "some-name",
|
||||||
|
new String[] {"plugins", "other", "posts", "some-name"}));
|
||||||
|
|
||||||
// api version identification
|
for (CustomSuccessCase pluginScopedCase : pluginScopedCases) {
|
||||||
new SuccessCase("POST", "/api/v1beta3/plugins/other/posts", "create",
|
|
||||||
"api", "", "v1beta3", "other", "posts", "", "",
|
|
||||||
new String[] {"posts"}));
|
|
||||||
|
|
||||||
for (SuccessCase pluginScopedCase : pluginScopedCases) {
|
|
||||||
var request =
|
var request =
|
||||||
method(HttpMethod.valueOf(pluginScopedCase.method),
|
method(HttpMethod.valueOf(pluginScopedCase.method),
|
||||||
pluginScopedCase.url).build();
|
pluginScopedCase.url).build();
|
||||||
|
@ -136,14 +139,16 @@ public class RequestInfoResolverTest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void assertRequestInfoCase(SuccessCase pluginScopedCase, RequestInfo requestInfo) {
|
private void assertRequestInfoCase(CustomSuccessCase pluginScopedCase,
|
||||||
assertThat(requestInfo.getPluginName()).isEqualTo(pluginScopedCase.expectedPluginName);
|
RequestInfo requestInfo) {
|
||||||
assertThat(requestInfo.getVerb()).isEqualTo(pluginScopedCase.expectedVerb);
|
assertThat(requestInfo.getVerb()).isEqualTo(pluginScopedCase.expectedVerb);
|
||||||
assertThat(requestInfo.getParts()).isEqualTo(pluginScopedCase.expectedParts);
|
assertThat(requestInfo.getParts()).isEqualTo(pluginScopedCase.expectedParts);
|
||||||
assertThat(requestInfo.getApiGroup()).isEqualTo(pluginScopedCase.expectedAPIGroup);
|
assertThat(requestInfo.getApiGroup()).isEqualTo(pluginScopedCase.expectedAPIGroup);
|
||||||
assertThat(requestInfo.getResource()).isEqualTo(pluginScopedCase.expectedResource);
|
assertThat(requestInfo.getResource()).isEqualTo(pluginScopedCase.expectedResource);
|
||||||
assertThat(requestInfo.getSubresource())
|
assertThat(requestInfo.getSubresource())
|
||||||
.isEqualTo(pluginScopedCase.expectedSubresource());
|
.isEqualTo(pluginScopedCase.expectedSubresource());
|
||||||
|
assertThat(requestInfo.getSubName())
|
||||||
|
.isEqualTo(pluginScopedCase.expectedSubName());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -174,11 +179,11 @@ public class RequestInfoResolverTest {
|
||||||
List<PolicyRule> rules = List.of(
|
List<PolicyRule> rules = List.of(
|
||||||
new PolicyRule.Builder().apiGroups("").resources("posts").verbs("list", "get")
|
new PolicyRule.Builder().apiGroups("").resources("posts").verbs("list", "get")
|
||||||
.build(),
|
.build(),
|
||||||
new PolicyRule.Builder().pluginName("fakePlugin").apiGroups("").resources("posts")
|
|
||||||
.verbs("list", "get").build(),
|
|
||||||
new PolicyRule.Builder().pluginName("fakePlugin").apiGroups("")
|
|
||||||
.resources("posts/tags").verbs("list", "get").build(),
|
|
||||||
new PolicyRule.Builder().apiGroups("").resources("categories").verbs("*").build(),
|
new PolicyRule.Builder().apiGroups("").resources("categories").verbs("*").build(),
|
||||||
|
new PolicyRule.Builder().apiGroups("plugin.api.halo.run").resources("plugins/users")
|
||||||
|
.resourceNames("foo/bar").verbs("*").build(),
|
||||||
|
new PolicyRule.Builder().apiGroups("plugin.api.halo.run").resources("plugins/users")
|
||||||
|
.resourceNames("foo").verbs("*").build(),
|
||||||
new PolicyRule.Builder().nonResourceURLs("/healthy").verbs("get", "post", "head")
|
new PolicyRule.Builder().nonResourceURLs("/healthy").verbs("get", "post", "head")
|
||||||
.build());
|
.build());
|
||||||
role.setRules(rules);
|
role.setRules(rules);
|
||||||
|
@ -230,14 +235,17 @@ public class RequestInfoResolverTest {
|
||||||
new RequestResolveCase("/api/v1/posts", "DELETE", false),
|
new RequestResolveCase("/api/v1/posts", "DELETE", false),
|
||||||
new RequestResolveCase("/api/v1/posts/aName", "UPDATE", false),
|
new RequestResolveCase("/api/v1/posts/aName", "UPDATE", false),
|
||||||
|
|
||||||
// plugin resource
|
|
||||||
new RequestResolveCase("/api/v1/plugins/fakePlugin/posts", "GET", true),
|
|
||||||
new RequestResolveCase("/api/v1/plugins/fakePlugin/posts/some-name", "GET", true),
|
|
||||||
new RequestResolveCase("/api/v1/plugins/fakePlugin/posts/some-name/tags", "GET", true),
|
|
||||||
|
|
||||||
// group resource url
|
// group resource url
|
||||||
new RequestResolveCase("/apis/group/v1/posts", "GET", false),
|
new RequestResolveCase("/apis/group/v1/posts", "GET", false),
|
||||||
|
|
||||||
|
// plugin custom resource url
|
||||||
|
new RequestResolveCase("/apis/plugin.api.halo.run/v1alpha1/plugins/foo/users", "GET",
|
||||||
|
true),
|
||||||
|
new RequestResolveCase("/apis/plugin.api.halo.run/v1alpha1/plugins/foo/users/bar",
|
||||||
|
"GET", true),
|
||||||
|
new RequestResolveCase("/apis/plugin.api.halo.run/v1alpha1/plugins/foo/posts/bar",
|
||||||
|
"GET", false),
|
||||||
|
|
||||||
// non resource url
|
// non resource url
|
||||||
new RequestResolveCase("/healthy", "GET", true),
|
new RequestResolveCase("/healthy", "GET", true),
|
||||||
new RequestResolveCase("/healthy", "POST", true),
|
new RequestResolveCase("/healthy", "POST", true),
|
||||||
|
@ -255,40 +263,47 @@ public class RequestInfoResolverTest {
|
||||||
|
|
||||||
public record SuccessCase(String method, String url, String expectedVerb,
|
public record SuccessCase(String method, String url, String expectedVerb,
|
||||||
String expectedAPIPrefix, String expectedAPIGroup,
|
String expectedAPIPrefix, String expectedAPIGroup,
|
||||||
String expectedAPIVersion, String expectedPluginName,
|
String expectedAPIVersion, String expectedNamespace,
|
||||||
String expectedResource, String expectedSubresource,
|
String expectedResource, String expectedSubresource,
|
||||||
String expectedName, String[] expectedParts) {
|
String expectedName, String[] expectedParts) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public record CustomSuccessCase(String method, String url, String expectedVerb,
|
||||||
|
String expectedAPIPrefix, String expectedAPIGroup,
|
||||||
|
String expectedAPIVersion, String expectedNamespace,
|
||||||
|
String expectedResource, String expectedSubresource,
|
||||||
|
String expectedName, String expectedSubName,
|
||||||
|
String[] expectedParts) {
|
||||||
|
}
|
||||||
|
|
||||||
List<SuccessCase> getTestRequestInfos() {
|
List<SuccessCase> getTestRequestInfos() {
|
||||||
String namespaceAll = "*";
|
String namespaceAll = "*";
|
||||||
return List.of(
|
return List.of(
|
||||||
new SuccessCase("GET", "/api/v1/plugins", "list", "api", "", "v1", "", "plugins",
|
new SuccessCase("GET", "/api/v1/namespaces", "list", "api", "", "v1", "", "namespaces",
|
||||||
"", "", new String[] {"plugins"}),
|
"", "", new String[] {"namespaces"}),
|
||||||
new SuccessCase("GET", "/api/v1/plugins/other", "get", "api", "", "v1", "other",
|
new SuccessCase("GET", "/api/v1/namespaces/other", "get", "api", "", "v1", "other",
|
||||||
"plugins", "", "other", new String[] {"plugins", "other"}),
|
"namespaces", "", "other", new String[] {"namespaces", "other"}),
|
||||||
|
|
||||||
new SuccessCase("GET", "/api/v1/plugins/other/posts", "list", "api", "", "v1",
|
new SuccessCase("GET", "/api/v1/namespaces/other/posts", "list", "api", "", "v1",
|
||||||
"other", "posts", "", "", new String[] {"posts"}),
|
"other", "posts", "", "", new String[] {"posts"}),
|
||||||
new SuccessCase("GET", "/api/v1/plugins/other/posts/foo", "get", "api", "", "v1",
|
new SuccessCase("GET", "/api/v1/namespaces/other/posts/foo", "get", "api", "", "v1",
|
||||||
"other", "posts", "", "foo", new String[] {"posts", "foo"}),
|
"other", "posts", "", "foo", new String[] {"posts", "foo"}),
|
||||||
new SuccessCase("HEAD", "/api/v1/plugins/other/posts/foo", "get", "api", "", "v1",
|
new SuccessCase("HEAD", "/api/v1/namespaces/other/posts/foo", "get", "api", "", "v1",
|
||||||
"other", "posts", "", "foo", new String[] {"posts", "foo"}),
|
"other", "posts", "", "foo", new String[] {"posts", "foo"}),
|
||||||
new SuccessCase("GET", "/api/v1/posts", "list", "api", "", "v1", namespaceAll, "posts",
|
new SuccessCase("GET", "/api/v1/posts", "list", "api", "", "v1", namespaceAll, "posts",
|
||||||
"", "", new String[] {"posts"}),
|
"", "", new String[] {"posts"}),
|
||||||
new SuccessCase("HEAD", "/api/v1/posts", "list", "api", "", "v1", namespaceAll, "posts",
|
new SuccessCase("HEAD", "/api/v1/posts", "list", "api", "", "v1", namespaceAll, "posts",
|
||||||
"", "", new String[] {"posts"}),
|
"", "", new String[] {"posts"}),
|
||||||
new SuccessCase("GET", "/api/v1/plugins/other/posts/foo", "get", "api", "", "v1",
|
new SuccessCase("GET", "/api/v1/namespaces/other/posts/foo", "get", "api", "", "v1",
|
||||||
"other", "posts", "", "foo", new String[] {"posts", "foo"}),
|
"other", "posts", "", "foo", new String[] {"posts", "foo"}),
|
||||||
new SuccessCase("GET", "/api/v1/plugins/other/posts", "list", "api", "", "v1",
|
new SuccessCase("GET", "/api/v1/namespaces/other/posts", "list", "api", "", "v1",
|
||||||
"other", "posts", "", "", new String[] {"posts"}),
|
"other", "posts", "", "", new String[] {"posts"}),
|
||||||
|
|
||||||
// special verbs
|
// special verbs
|
||||||
new SuccessCase("GET", "/api/v1/proxy/plugins/other/posts/foo", "proxy", "api", "",
|
new SuccessCase("GET", "/api/v1/proxy/namespaces/other/posts/foo", "proxy", "api", "",
|
||||||
"v1", "other", "posts", "", "foo", new String[] {"posts", "foo"}),
|
"v1", "other", "posts", "", "foo", new String[] {"posts", "foo"}),
|
||||||
new SuccessCase("GET",
|
new SuccessCase("GET",
|
||||||
"/api/v1/proxy/plugins/other/posts/foo/subpath/not/a/subresource", "proxy",
|
"/api/v1/proxy/namespaces/other/posts/foo/subpath/not/a/subresource", "proxy",
|
||||||
"api", "", "v1", "other", "posts", "", "foo",
|
"api", "", "v1", "other", "posts", "", "foo",
|
||||||
new String[] {"posts", "foo", "subpath", "not", "a", "subresource"}),
|
new String[] {"posts", "foo", "subpath", "not", "a", "subresource"}),
|
||||||
new SuccessCase("GET", "/api/v1/watch/posts", "watch", "api", "", "v1", namespaceAll,
|
new SuccessCase("GET", "/api/v1/watch/posts", "watch", "api", "", "v1", namespaceAll,
|
||||||
|
@ -297,35 +312,55 @@ public class RequestInfoResolverTest {
|
||||||
namespaceAll, "posts", "", "", new String[] {"posts"}),
|
namespaceAll, "posts", "", "", new String[] {"posts"}),
|
||||||
new SuccessCase("GET", "/api/v1/posts?watch=false", "list", "api", "", "v1",
|
new SuccessCase("GET", "/api/v1/posts?watch=false", "list", "api", "", "v1",
|
||||||
namespaceAll, "posts", "", "", new String[] {"posts"}),
|
namespaceAll, "posts", "", "", new String[] {"posts"}),
|
||||||
new SuccessCase("GET", "/api/v1/watch/plugins/other/posts", "watch", "api", "", "v1",
|
new SuccessCase("GET", "/api/v1/watch/namespaces/other/posts", "watch", "api", "", "v1",
|
||||||
"other", "posts", "", "", new String[] {"posts"}),
|
"other", "posts", "", "", new String[] {"posts"}),
|
||||||
new SuccessCase("GET", "/api/v1/plugins/other/posts?watch=1", "watch", "api", "",
|
new SuccessCase("GET", "/api/v1/namespaces/other/posts?watch=true", "watch", "api", "",
|
||||||
"v1", "other", "posts", "", "", new String[] {"posts"}),
|
"v1", "other", "posts", "", "", new String[] {"posts"}),
|
||||||
new SuccessCase("GET", "/api/v1/plugins/other/posts?watch=0", "list", "api", "",
|
new SuccessCase("GET", "/api/v1/namespaces/other/posts?watch=false", "list", "api", "",
|
||||||
"v1", "other", "posts", "", "", new String[] {"posts"}),
|
"v1", "other", "posts", "", "", new String[] {"posts"}),
|
||||||
|
|
||||||
// subresource identification
|
// subresource identification
|
||||||
new SuccessCase("GET", "/api/v1/plugins/other/posts/foo/status", "get", "api", "",
|
new SuccessCase("GET", "/api/v1/namespaces/other/posts/foo/status", "get", "api", "",
|
||||||
"v1", "other", "posts", "status", "foo", new String[] {"posts", "foo", "status"}),
|
"v1", "other", "posts", "status", "foo", new String[] {"posts", "foo", "status"}),
|
||||||
new SuccessCase("GET", "/api/v1/plugins/other/posts/foo/proxy/subpath", "get", "api",
|
new SuccessCase("GET", "/api/v1/namespaces/other/posts/foo/proxy/subpath", "get", "api",
|
||||||
"", "v1", "other", "posts", "proxy", "foo",
|
"", "v1", "other", "posts", "proxy", "foo",
|
||||||
new String[] {"posts", "foo", "proxy", "subpath"}),
|
new String[] {"posts", "foo", "proxy", "subpath"}),
|
||||||
|
new SuccessCase("PUT", "/api/v1/namespaces/other/finalize", "update", "api", "", "v1",
|
||||||
|
"other", "namespaces", "finalize", "other",
|
||||||
|
new String[] {"namespaces", "other", "finalize"}),
|
||||||
|
new SuccessCase("PUT", "/api/v1/namespaces/other/status", "update", "api", "", "v1",
|
||||||
|
"other", "namespaces", "status", "other",
|
||||||
|
new String[] {"namespaces", "other", "status"}),
|
||||||
|
|
||||||
// verb identification
|
// verb identification
|
||||||
new SuccessCase("PATCH", "/api/v1/plugins/other/posts/foo", "patch", "api", "", "v1",
|
new SuccessCase("PATCH", "/api/v1/namespaces/other/posts/foo", "patch", "api", "", "v1",
|
||||||
"other", "posts", "", "foo", new String[] {"posts", "foo"}),
|
"other", "posts", "", "foo", new String[] {"posts", "foo"}),
|
||||||
new SuccessCase("DELETE", "/api/v1/plugins/other/posts/foo", "delete", "api", "",
|
new SuccessCase("DELETE", "/api/v1/namespaces/other/posts/foo", "delete", "api", "",
|
||||||
"v1", "other", "posts", "", "foo", new String[] {"posts", "foo"}),
|
"v1", "other", "posts", "", "foo", new String[] {"posts", "foo"}),
|
||||||
new SuccessCase("POST", "/api/v1/plugins/other/posts", "create", "api", "", "v1",
|
new SuccessCase("POST", "/api/v1/namespaces/other/posts", "create", "api", "", "v1",
|
||||||
"other", "posts", "", "", new String[] {"posts"}),
|
"other", "posts", "", "", new String[] {"posts"}),
|
||||||
|
|
||||||
// deletecollection verb identification
|
// deletecollection verb identification
|
||||||
new SuccessCase("DELETE", "/api/v1/nodes", "deletecollection", "api", "", "v1", "",
|
new SuccessCase("DELETE", "/api/v1/nodes?all=true", "deletecollection", "api", "", "v1",
|
||||||
|
"",
|
||||||
"nodes", "", "", new String[] {"nodes"}),
|
"nodes", "", "", new String[] {"nodes"}),
|
||||||
new SuccessCase("DELETE", "/api/v1/plugins", "deletecollection", "api", "", "v1", "",
|
new SuccessCase("DELETE", "/api/v1/namespaces?all=false", "delete", "api", "",
|
||||||
"plugins", "", "", new String[] {"plugins"}),
|
"v1", "",
|
||||||
new SuccessCase("DELETE", "/api/v1/plugins/other/posts", "deletecollection", "api",
|
"namespaces", "", "", new String[] {"namespaces"}),
|
||||||
"", "v1", "other", "posts", "", "", new String[] {"posts"}));
|
new SuccessCase("DELETE", "/api/v1/namespaces/other/posts?all=true", "deletecollection",
|
||||||
|
"api",
|
||||||
|
"", "v1", "other", "posts", "", "", new String[] {"posts"}),
|
||||||
|
new SuccessCase("DELETE", "/apis/extensions/v1/namespaces/other/posts?all=true",
|
||||||
|
"deletecollection", "apis", "extensions", "v1", "other", "posts", "", "",
|
||||||
|
new String[] {"posts"}),
|
||||||
|
|
||||||
|
// api group identification
|
||||||
|
new SuccessCase("POST", "/apis/extensions/v1/namespaces/other/posts", "create", "apis",
|
||||||
|
"extensions", "v1", "other", "posts", "", "", new String[] {"posts"}),
|
||||||
|
|
||||||
|
// api version identification
|
||||||
|
new SuccessCase("POST", "/apis/extensions/v1beta3/namespaces/other/posts", "create",
|
||||||
|
"apis", "extensions", "v1beta3", "other", "posts", "", "", new String[] {"posts"}));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue