Skip to content

Commit 435a0cc

Browse files
authored
CreateClassLoaderEntitlement + extensions to parse logic (#117754)
1 parent 8134d02 commit 435a0cc

File tree

4 files changed

+109
-34
lines changed

4 files changed

+109
-34
lines changed
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the "Elastic License
4+
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
5+
* Public License v 1"; you may not use this file except in compliance with, at
6+
* your election, the "Elastic License 2.0", the "GNU Affero General Public
7+
* License v3.0 only", or the "Server Side Public License, v 1".
8+
*/
9+
10+
package org.elasticsearch.entitlement.runtime.policy;
11+
12+
public class CreateClassLoaderEntitlement implements Entitlement {
13+
@ExternalEntitlement
14+
public CreateClassLoaderEntitlement() {}
15+
16+
}

libs/entitlement/src/main/java/org/elasticsearch/entitlement/runtime/policy/PolicyParser.java

Lines changed: 51 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -19,22 +19,43 @@
1919
import java.lang.reflect.Constructor;
2020
import java.lang.reflect.InvocationTargetException;
2121
import java.util.ArrayList;
22+
import java.util.Arrays;
2223
import java.util.List;
24+
import java.util.Locale;
2325
import java.util.Map;
2426
import java.util.Objects;
25-
26-
import static org.elasticsearch.entitlement.runtime.policy.PolicyParserException.newPolicyParserException;
27+
import java.util.function.Function;
28+
import java.util.function.Predicate;
29+
import java.util.stream.Collectors;
30+
import java.util.stream.Stream;
2731

2832
/**
2933
* A parser to parse policy files for entitlements.
3034
*/
3135
public class PolicyParser {
3236

33-
protected static final String entitlementPackageName = Entitlement.class.getPackage().getName();
37+
private static final Map<String, Class<?>> EXTERNAL_ENTITLEMENTS = Stream.of(FileEntitlement.class, CreateClassLoaderEntitlement.class)
38+
.collect(Collectors.toUnmodifiableMap(PolicyParser::getEntitlementTypeName, Function.identity()));
3439

3540
protected final XContentParser policyParser;
3641
protected final String policyName;
3742

43+
static String getEntitlementTypeName(Class<? extends Entitlement> entitlementClass) {
44+
var entitlementClassName = entitlementClass.getSimpleName();
45+
46+
if (entitlementClassName.endsWith("Entitlement") == false) {
47+
throw new IllegalArgumentException(
48+
entitlementClassName + " is not a valid Entitlement class name. A valid class name must end with 'Entitlement'"
49+
);
50+
}
51+
52+
var strippedClassName = entitlementClassName.substring(0, entitlementClassName.indexOf("Entitlement"));
53+
return Arrays.stream(strippedClassName.split("(?=\\p{Lu})"))
54+
.filter(Predicate.not(String::isEmpty))
55+
.map(s -> s.toLowerCase(Locale.ROOT))
56+
.collect(Collectors.joining("_"));
57+
}
58+
3859
public PolicyParser(InputStream inputStream, String policyName) throws IOException {
3960
this.policyParser = YamlXContent.yamlXContent.createParser(XContentParserConfiguration.EMPTY, Objects.requireNonNull(inputStream));
4061
this.policyName = policyName;
@@ -67,18 +88,23 @@ protected Scope parseScope(String scopeName) throws IOException {
6788
}
6889
List<Entitlement> entitlements = new ArrayList<>();
6990
while (policyParser.nextToken() != XContentParser.Token.END_ARRAY) {
70-
if (policyParser.currentToken() != XContentParser.Token.START_OBJECT) {
71-
throw newPolicyParserException(scopeName, "expected object <entitlement type>");
72-
}
73-
if (policyParser.nextToken() != XContentParser.Token.FIELD_NAME) {
91+
if (policyParser.currentToken() == XContentParser.Token.VALUE_STRING) {
92+
String entitlementType = policyParser.text();
93+
Entitlement entitlement = parseEntitlement(scopeName, entitlementType);
94+
entitlements.add(entitlement);
95+
} else if (policyParser.currentToken() == XContentParser.Token.START_OBJECT) {
96+
if (policyParser.nextToken() != XContentParser.Token.FIELD_NAME) {
97+
throw newPolicyParserException(scopeName, "expected object <entitlement type>");
98+
}
99+
String entitlementType = policyParser.currentName();
100+
Entitlement entitlement = parseEntitlement(scopeName, entitlementType);
101+
entitlements.add(entitlement);
102+
if (policyParser.nextToken() != XContentParser.Token.END_OBJECT) {
103+
throw newPolicyParserException(scopeName, "expected closing object");
104+
}
105+
} else {
74106
throw newPolicyParserException(scopeName, "expected object <entitlement type>");
75107
}
76-
String entitlementType = policyParser.currentName();
77-
Entitlement entitlement = parseEntitlement(scopeName, entitlementType);
78-
entitlements.add(entitlement);
79-
if (policyParser.nextToken() != XContentParser.Token.END_OBJECT) {
80-
throw newPolicyParserException(scopeName, "expected closing object");
81-
}
82108
}
83109
return new Scope(scopeName, entitlements);
84110
} catch (IOException ioe) {
@@ -87,34 +113,29 @@ protected Scope parseScope(String scopeName) throws IOException {
87113
}
88114

89115
protected Entitlement parseEntitlement(String scopeName, String entitlementType) throws IOException {
90-
Class<?> entitlementClass;
91-
try {
92-
entitlementClass = Class.forName(
93-
entitlementPackageName
94-
+ "."
95-
+ Character.toUpperCase(entitlementType.charAt(0))
96-
+ entitlementType.substring(1)
97-
+ "Entitlement"
98-
);
99-
} catch (ClassNotFoundException cnfe) {
100-
throw newPolicyParserException(scopeName, "unknown entitlement type [" + entitlementType + "]");
101-
}
102-
if (Entitlement.class.isAssignableFrom(entitlementClass) == false) {
116+
Class<?> entitlementClass = EXTERNAL_ENTITLEMENTS.get(entitlementType);
117+
118+
if (entitlementClass == null) {
103119
throw newPolicyParserException(scopeName, "unknown entitlement type [" + entitlementType + "]");
104120
}
121+
105122
Constructor<?> entitlementConstructor = entitlementClass.getConstructors()[0];
106123
ExternalEntitlement entitlementMetadata = entitlementConstructor.getAnnotation(ExternalEntitlement.class);
107124
if (entitlementMetadata == null) {
108125
throw newPolicyParserException(scopeName, "unknown entitlement type [" + entitlementType + "]");
109126
}
110127

111-
if (policyParser.nextToken() != XContentParser.Token.START_OBJECT) {
112-
throw newPolicyParserException(scopeName, entitlementType, "expected entitlement parameters");
128+
Class<?>[] parameterTypes = entitlementConstructor.getParameterTypes();
129+
String[] parametersNames = entitlementMetadata.parameterNames();
130+
131+
if (parameterTypes.length != 0 || parametersNames.length != 0) {
132+
if (policyParser.nextToken() != XContentParser.Token.START_OBJECT) {
133+
throw newPolicyParserException(scopeName, entitlementType, "expected entitlement parameters");
134+
}
113135
}
136+
114137
Map<String, Object> parsedValues = policyParser.map();
115138

116-
Class<?>[] parameterTypes = entitlementConstructor.getParameterTypes();
117-
String[] parametersNames = entitlementMetadata.parameterNames();
118139
Object[] parameterValues = new Object[parameterTypes.length];
119140
for (int parameterIndex = 0; parameterIndex < parameterTypes.length; ++parameterIndex) {
120141
String parameterName = parametersNames[parameterIndex];

libs/entitlement/src/test/java/org/elasticsearch/entitlement/runtime/policy/PolicyParserFailureTests.java

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212
import org.elasticsearch.test.ESTestCase;
1313

1414
import java.io.ByteArrayInputStream;
15-
import java.io.IOException;
1615
import java.nio.charset.StandardCharsets;
1716

1817
public class PolicyParserFailureTests extends ESTestCase {
@@ -26,7 +25,7 @@ public void testParserSyntaxFailures() {
2625
assertEquals("[1:1] policy parsing error for [test-failure-policy.yaml]: expected object <scope name>", ppe.getMessage());
2726
}
2827

29-
public void testEntitlementDoesNotExist() throws IOException {
28+
public void testEntitlementDoesNotExist() {
3029
PolicyParserException ppe = expectThrows(PolicyParserException.class, () -> new PolicyParser(new ByteArrayInputStream("""
3130
entitlement-module-name:
3231
- does_not_exist: {}
@@ -38,7 +37,7 @@ public void testEntitlementDoesNotExist() throws IOException {
3837
);
3938
}
4039

41-
public void testEntitlementMissingParameter() throws IOException {
40+
public void testEntitlementMissingParameter() {
4241
PolicyParserException ppe = expectThrows(PolicyParserException.class, () -> new PolicyParser(new ByteArrayInputStream("""
4342
entitlement-module-name:
4443
- file: {}
@@ -61,7 +60,7 @@ public void testEntitlementMissingParameter() throws IOException {
6160
);
6261
}
6362

64-
public void testEntitlementExtraneousParameter() throws IOException {
63+
public void testEntitlementExtraneousParameter() {
6564
PolicyParserException ppe = expectThrows(PolicyParserException.class, () -> new PolicyParser(new ByteArrayInputStream("""
6665
entitlement-module-name:
6766
- file:

libs/entitlement/src/test/java/org/elasticsearch/entitlement/runtime/policy/PolicyParserTests.java

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,31 @@
1111

1212
import org.elasticsearch.test.ESTestCase;
1313

14+
import java.io.ByteArrayInputStream;
1415
import java.io.IOException;
16+
import java.nio.charset.StandardCharsets;
1517
import java.util.List;
1618

19+
import static org.elasticsearch.test.LambdaMatchers.transformedMatch;
20+
import static org.hamcrest.Matchers.both;
21+
import static org.hamcrest.Matchers.contains;
22+
import static org.hamcrest.Matchers.equalTo;
23+
import static org.hamcrest.Matchers.instanceOf;
24+
1725
public class PolicyParserTests extends ESTestCase {
1826

27+
private static class TestWrongEntitlementName implements Entitlement {}
28+
29+
public void testGetEntitlementTypeName() {
30+
assertEquals("create_class_loader", PolicyParser.getEntitlementTypeName(CreateClassLoaderEntitlement.class));
31+
32+
var ex = expectThrows(IllegalArgumentException.class, () -> PolicyParser.getEntitlementTypeName(TestWrongEntitlementName.class));
33+
assertThat(
34+
ex.getMessage(),
35+
equalTo("TestWrongEntitlementName is not a valid Entitlement class name. A valid class name must end with 'Entitlement'")
36+
);
37+
}
38+
1939
public void testPolicyBuilder() throws IOException {
2040
Policy parsedPolicy = new PolicyParser(PolicyParserTests.class.getResourceAsStream("test-policy.yaml"), "test-policy.yaml")
2141
.parsePolicy();
@@ -25,4 +45,23 @@ public void testPolicyBuilder() throws IOException {
2545
);
2646
assertEquals(parsedPolicy, builtPolicy);
2747
}
48+
49+
public void testParseCreateClassloader() throws IOException {
50+
Policy parsedPolicy = new PolicyParser(new ByteArrayInputStream("""
51+
entitlement-module-name:
52+
- create_class_loader
53+
""".getBytes(StandardCharsets.UTF_8)), "test-policy.yaml").parsePolicy();
54+
Policy builtPolicy = new Policy(
55+
"test-policy.yaml",
56+
List.of(new Scope("entitlement-module-name", List.of(new CreateClassLoaderEntitlement())))
57+
);
58+
assertThat(
59+
parsedPolicy.scopes,
60+
contains(
61+
both(transformedMatch((Scope scope) -> scope.name, equalTo("entitlement-module-name"))).and(
62+
transformedMatch(scope -> scope.entitlements, contains(instanceOf(CreateClassLoaderEntitlement.class)))
63+
)
64+
)
65+
);
66+
}
2867
}

0 commit comments

Comments
 (0)