From d7cc7bac136f2fb4be9638ab6aba91eaac5e9709 Mon Sep 17 00:00:00 2001 From: Jack Conradson Date: Fri, 4 Oct 2024 16:18:54 -0700 Subject: [PATCH 1/9] start of policy parser --- libs/entitlement-runtime/build.gradle | 10 +- .../src/main/java/module-info.java | 3 + .../entitlement/policy/FileEntitlement.java | 32 +++++ .../entitlement/policy/Policy.java | 15 ++ .../entitlement/policy/PolicyBuilder.java | 135 ++++++++++++++++++ .../policy/PolicyBuilderTests.java | 19 +++ .../entitlement/policy/test-policy.yaml | 9 ++ 7 files changed, 218 insertions(+), 5 deletions(-) create mode 100644 libs/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/policy/FileEntitlement.java create mode 100644 libs/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/policy/Policy.java create mode 100644 libs/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/policy/PolicyBuilder.java create mode 100644 libs/entitlement-runtime/src/test/java/org/elasticsearch/entitlement/policy/PolicyBuilderTests.java create mode 100644 libs/entitlement-runtime/src/test/resources/org/elasticsearch/entitlement/policy/test-policy.yaml diff --git a/libs/entitlement-runtime/build.gradle b/libs/entitlement-runtime/build.gradle index a552dd7d5ba47..3e6c56e5be934 100644 --- a/libs/entitlement-runtime/build.gradle +++ b/libs/entitlement-runtime/build.gradle @@ -11,12 +11,12 @@ apply plugin: 'elasticsearch.publish' dependencies { compileOnly project(':libs:elasticsearch-core') + compileOnly project(":libs:elasticsearch-logging") + compileOnly project(":libs:elasticsearch-x-content") - testImplementation project(":test:framework") -} - -tasks.named('forbiddenApisMain').configure { - replaceSignatureFiles 'jdk-signatures' + testImplementation(project(":test:framework")) { + exclude group: 'org.elasticsearch', module: 'elasticsearch-entitlement-runtime' + } } tasks.named('forbiddenApisMain').configure { diff --git a/libs/entitlement-runtime/src/main/java/module-info.java b/libs/entitlement-runtime/src/main/java/module-info.java index 13849f0658d72..306387eefb9f3 100644 --- a/libs/entitlement-runtime/src/main/java/module-info.java +++ b/libs/entitlement-runtime/src/main/java/module-info.java @@ -9,6 +9,9 @@ module org.elasticsearch.entitlement.runtime { requires org.elasticsearch.base; + requires org.elasticsearch.xcontent; + requires java.logging; + requires org.elasticsearch.logging; exports org.elasticsearch.entitlement.runtime.api to org.elasticsearch.entitlement.agent; } diff --git a/libs/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/policy/FileEntitlement.java b/libs/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/policy/FileEntitlement.java new file mode 100644 index 0000000000000..75c8b569b6c26 --- /dev/null +++ b/libs/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/policy/FileEntitlement.java @@ -0,0 +1,32 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +package org.elasticsearch.entitlement.policy; + +public class FileEntitlement { + + public static final int READ_OPERATION = 0x1; + public static final int WRITE_OPERATION = 0x2; + + private final String path; + private final int operations; + + public FileEntitlement(String path, int operations) { + this.path = path; + this.operations = operations; + } + + public String getPath() { + return path; + } + + public int getOperations() { + return operations; + } +} diff --git a/libs/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/policy/Policy.java b/libs/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/policy/Policy.java new file mode 100644 index 0000000000000..4351f0ac9a3aa --- /dev/null +++ b/libs/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/policy/Policy.java @@ -0,0 +1,15 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +package org.elasticsearch.entitlement.policy; + +public class Policy { + + +} diff --git a/libs/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/policy/PolicyBuilder.java b/libs/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/policy/PolicyBuilder.java new file mode 100644 index 0000000000000..7c880de364418 --- /dev/null +++ b/libs/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/policy/PolicyBuilder.java @@ -0,0 +1,135 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +package org.elasticsearch.entitlement.policy; + +import org.elasticsearch.xcontent.ParseField; +import org.elasticsearch.xcontent.XContentParser; +import org.elasticsearch.xcontent.XContentParserConfiguration; +import org.elasticsearch.xcontent.yaml.YamlXContent; + +import java.io.IOException; +import java.io.InputStream; + +public class PolicyBuilder { + + public static ParseField POLICY_PARSEFIELD = new ParseField("policy"); + public static ParseField MODULE_PARSEFIELD = new ParseField("module"); + public static ParseField NAME_PARSEFIELD = new ParseField("name"); + public static ParseField ENTITLEMENTS_PARSEFIELD = new ParseField("entitlements"); + + /* + START_OBJECT + FIELD_NAME: policy + START_OBJECT + FIELD_NAME: module + START_OBJECT + FIELD_NAME: name + VALUE_STRING: entitlement-test + FIELD_NAME: entitlements + START_ARRAY + START_OBJECT + FIELD_NAME: file + START_OBJECT + FIELD_NAME: path + VALUE_STRING: test/path/to/file + FIELD_NAME: actions + START_ARRAY + VALUE_STRING: read + VALUE_STRING: write + END_ARRAY + END_OBJECT + END_OBJECT + END_ARRAY + END_OBJECT + END_OBJECT + END_OBJECT + */ + + public static void parsePolicy(String name, InputStream is) { + try { + XContentParser parser = YamlXContent.yamlXContent.createParser(XContentParserConfiguration.EMPTY, is); + parser.nextToken(); + if (parser.currentToken() != XContentParser.Token.START_OBJECT) { + throw new IllegalArgumentException("expected policy [" + name + "] to specify 'policy: ...'"); + } + parser.nextToken(); + if (parser.currentToken() != XContentParser.Token.FIELD_NAME || parser.currentName().equals(POLICY_PARSEFIELD.getPreferredName()) == false) { + throw new IllegalArgumentException("expected policy [" + name + "] to specify 'policy: ...'"); + } + parser.nextToken(); + if (parser.currentToken() != XContentParser.Token.START_ARRAY) { + throw new IllegalArgumentException("expected policy [" + name + "] to specify 'module: ...'"); + } + while (parser.nextToken() != XContentParser.Token.END_ARRAY) { + if (parser.currentToken() != XContentParser.Token.START_OBJECT) { + throw new IllegalArgumentException("expected policy [" + name + "] to specify 'module: ...'"); + } + parser.nextToken(); + if (parser.currentToken() != XContentParser.Token.FIELD_NAME || parser.currentName().equals(MODULE_PARSEFIELD.getPreferredName()) == false) { + throw new IllegalArgumentException("expected policy [" + name + "] to specify 'module: ...'"); + } + parser.nextToken(); + if (parser.currentToken() != XContentParser.Token.START_OBJECT) { + throw new IllegalArgumentException("expected policy [" + name + "] to specify 'module: ...'"); + } + parseModulePolicy(name, parser); + } + } catch (IOException ioe) { + throw new RuntimeException(ioe); + } + } + + private static void parseModulePolicy(String name, XContentParser parser) throws IOException { + while (parser.nextToken() != XContentParser.Token.END_OBJECT) { + if (parser.currentToken() != XContentParser.Token.FIELD_NAME) { + throw new IllegalArgumentException("expected policy [" + name + "] to specify 'module: ...'"); + } + String currentFieldName = parser.currentName(); + if (currentFieldName.equals(NAME_PARSEFIELD.getPreferredName())) { + parser.nextToken(); + if (parser.currentToken() != XContentParser.Token.VALUE_STRING) { + throw new IllegalArgumentException("expected policy [" + name + "] to specify value for 'name: '"); + } + // TODO: store module name + } else if (currentFieldName.equals(ENTITLEMENTS_PARSEFIELD.getPreferredName())) { + parser.nextToken(); + if (parser.currentToken() != XContentParser.Token.START_ARRAY) { + throw new IllegalArgumentException("expected policy [" + name + "] to specify 'entitlements: [...]'"); + } + parseEntitlements(name, parser); + } else { + throw new IllegalArgumentException("unexpected field name [" + currentFieldName + "] for policy [" + name + "]"); + } + } + } + + private static void parseEntitlements(String name, XContentParser parser) throws IOException { + while (parser.nextToken() != XContentParser.Token.END_ARRAY) { + if (parser.currentToken() != XContentParser.Token.START_OBJECT) { + throw new IllegalArgumentException("expected policy [" + name + "] to specify '- : ...'"); + } + if (parser.nextToken() != XContentParser.Token.FIELD_NAME) { + throw new IllegalArgumentException("expected policy [" + name + "] to specify '- : ...'"); + } + String currentFieldName = parser.currentName(); + if (currentFieldName.equals("file")) { + parseFileEntitlement(name, parser); + } + } + } + + private static void parseFileEntitlement(String name, XContentParser parser) { + + } + + private PolicyBuilder() { + // do nothing + } +} diff --git a/libs/entitlement-runtime/src/test/java/org/elasticsearch/entitlement/policy/PolicyBuilderTests.java b/libs/entitlement-runtime/src/test/java/org/elasticsearch/entitlement/policy/PolicyBuilderTests.java new file mode 100644 index 0000000000000..22dd24b59eea8 --- /dev/null +++ b/libs/entitlement-runtime/src/test/java/org/elasticsearch/entitlement/policy/PolicyBuilderTests.java @@ -0,0 +1,19 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +package org.elasticsearch.entitlement.policy; + +import org.elasticsearch.test.ESTestCase; + +public class PolicyBuilderTests extends ESTestCase { + + public void testPolicyBuilder() { + PolicyBuilder.parsePolicy("test-policy.yaml", PolicyBuilderTests.class.getResourceAsStream("test-policy.yaml")); + } +} diff --git a/libs/entitlement-runtime/src/test/resources/org/elasticsearch/entitlement/policy/test-policy.yaml b/libs/entitlement-runtime/src/test/resources/org/elasticsearch/entitlement/policy/test-policy.yaml new file mode 100644 index 0000000000000..b4c0e671c3a54 --- /dev/null +++ b/libs/entitlement-runtime/src/test/resources/org/elasticsearch/entitlement/policy/test-policy.yaml @@ -0,0 +1,9 @@ +policy: + - module: + name: entitlement-test + entitlements: + - file: + path: "test/path/to/file" + actions: + - "read" + - "write" From 8aa4a5cc8dba84fa532c7a480341937045273d7e Mon Sep 17 00:00:00 2001 From: Jack Conradson Date: Mon, 7 Oct 2024 15:32:38 -0700 Subject: [PATCH 2/9] initial parser for module scope --- .../entitlement/policy/FileEntitlement.java | 32 ------- .../entitlement/policy/Policy.java | 15 ---- .../entitlement/policy/PolicyBuilder.java | 90 +++++++++++++++---- 3 files changed, 72 insertions(+), 65 deletions(-) delete mode 100644 libs/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/policy/FileEntitlement.java delete mode 100644 libs/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/policy/Policy.java diff --git a/libs/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/policy/FileEntitlement.java b/libs/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/policy/FileEntitlement.java deleted file mode 100644 index 75c8b569b6c26..0000000000000 --- a/libs/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/policy/FileEntitlement.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the "Elastic License - * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side - * Public License v 1"; you may not use this file except in compliance with, at - * your election, the "Elastic License 2.0", the "GNU Affero General Public - * License v3.0 only", or the "Server Side Public License, v 1". - */ - -package org.elasticsearch.entitlement.policy; - -public class FileEntitlement { - - public static final int READ_OPERATION = 0x1; - public static final int WRITE_OPERATION = 0x2; - - private final String path; - private final int operations; - - public FileEntitlement(String path, int operations) { - this.path = path; - this.operations = operations; - } - - public String getPath() { - return path; - } - - public int getOperations() { - return operations; - } -} diff --git a/libs/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/policy/Policy.java b/libs/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/policy/Policy.java deleted file mode 100644 index 4351f0ac9a3aa..0000000000000 --- a/libs/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/policy/Policy.java +++ /dev/null @@ -1,15 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the "Elastic License - * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side - * Public License v 1"; you may not use this file except in compliance with, at - * your election, the "Elastic License 2.0", the "GNU Affero General Public - * License v3.0 only", or the "Server Side Public License, v 1". - */ - -package org.elasticsearch.entitlement.policy; - -public class Policy { - - -} diff --git a/libs/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/policy/PolicyBuilder.java b/libs/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/policy/PolicyBuilder.java index 7c880de364418..ab465062ef6bb 100644 --- a/libs/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/policy/PolicyBuilder.java +++ b/libs/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/policy/PolicyBuilder.java @@ -24,6 +24,10 @@ public class PolicyBuilder { public static ParseField NAME_PARSEFIELD = new ParseField("name"); public static ParseField ENTITLEMENTS_PARSEFIELD = new ParseField("entitlements"); + public static ParseField FILE_PARSEFIELD = new ParseField("file"); + public static ParseField PATH_PARSEFIELD = new ParseField("path"); + public static ParseField ACTIONS_PARSEFIELD = new ParseField("actions"); + /* START_OBJECT FIELD_NAME: policy @@ -55,31 +59,34 @@ public class PolicyBuilder { public static void parsePolicy(String name, InputStream is) { try { XContentParser parser = YamlXContent.yamlXContent.createParser(XContentParserConfiguration.EMPTY, is); - parser.nextToken(); - if (parser.currentToken() != XContentParser.Token.START_OBJECT) { + if (parser.nextToken() != XContentParser.Token.START_OBJECT) { throw new IllegalArgumentException("expected policy [" + name + "] to specify 'policy: ...'"); } - parser.nextToken(); - if (parser.currentToken() != XContentParser.Token.FIELD_NAME || parser.currentName().equals(POLICY_PARSEFIELD.getPreferredName()) == false) { + if (parser.nextToken() != XContentParser.Token.FIELD_NAME + || parser.currentName().equals(POLICY_PARSEFIELD.getPreferredName()) == false) { throw new IllegalArgumentException("expected policy [" + name + "] to specify 'policy: ...'"); } - parser.nextToken(); - if (parser.currentToken() != XContentParser.Token.START_ARRAY) { + if (parser.nextToken() != XContentParser.Token.START_ARRAY) { throw new IllegalArgumentException("expected policy [" + name + "] to specify 'module: ...'"); } while (parser.nextToken() != XContentParser.Token.END_ARRAY) { if (parser.currentToken() != XContentParser.Token.START_OBJECT) { throw new IllegalArgumentException("expected policy [" + name + "] to specify 'module: ...'"); } - parser.nextToken(); - if (parser.currentToken() != XContentParser.Token.FIELD_NAME || parser.currentName().equals(MODULE_PARSEFIELD.getPreferredName()) == false) { + if (parser.nextToken() != XContentParser.Token.FIELD_NAME + || parser.currentName().equals(MODULE_PARSEFIELD.getPreferredName()) == false) { throw new IllegalArgumentException("expected policy [" + name + "] to specify 'module: ...'"); } - parser.nextToken(); - if (parser.currentToken() != XContentParser.Token.START_OBJECT) { + if (parser.nextToken() != XContentParser.Token.START_OBJECT) { throw new IllegalArgumentException("expected policy [" + name + "] to specify 'module: ...'"); } parseModulePolicy(name, parser); + if (parser.nextToken() != XContentParser.Token.END_OBJECT) { + throw new IllegalArgumentException("expected policy [" + name + "] to specify closing brace '}'"); + } + } + if (parser.nextToken() != XContentParser.Token.END_OBJECT) { + throw new IllegalArgumentException("expected policy [" + name + "] to specify closing brace '}'"); } } catch (IOException ioe) { throw new RuntimeException(ioe); @@ -93,14 +100,12 @@ private static void parseModulePolicy(String name, XContentParser parser) throws } String currentFieldName = parser.currentName(); if (currentFieldName.equals(NAME_PARSEFIELD.getPreferredName())) { - parser.nextToken(); - if (parser.currentToken() != XContentParser.Token.VALUE_STRING) { + if (parser.nextToken() != XContentParser.Token.VALUE_STRING) { throw new IllegalArgumentException("expected policy [" + name + "] to specify value for 'name: '"); } - // TODO: store module name + // TODO: store module name and check for duplicates } else if (currentFieldName.equals(ENTITLEMENTS_PARSEFIELD.getPreferredName())) { - parser.nextToken(); - if (parser.currentToken() != XContentParser.Token.START_ARRAY) { + if (parser.nextToken() != XContentParser.Token.START_ARRAY) { throw new IllegalArgumentException("expected policy [" + name + "] to specify 'entitlements: [...]'"); } parseEntitlements(name, parser); @@ -119,14 +124,63 @@ private static void parseEntitlements(String name, XContentParser parser) throws throw new IllegalArgumentException("expected policy [" + name + "] to specify '- : ...'"); } String currentFieldName = parser.currentName(); - if (currentFieldName.equals("file")) { + if (currentFieldName.equals(FILE_PARSEFIELD.getPreferredName())) { parseFileEntitlement(name, parser); + } else { + throw new IllegalArgumentException("unexpected entitlement type [" + currentFieldName + "] for policy [" + name + "]"); + } + if (parser.nextToken() != XContentParser.Token.END_OBJECT) { + throw new IllegalArgumentException("expected policy [" + name + "] to specify closing brace '}'"); } } } - private static void parseFileEntitlement(String name, XContentParser parser) { - + private static void parseFileEntitlement(String name, XContentParser parser) throws IOException { + if (parser.nextToken() != XContentParser.Token.START_OBJECT) { + throw new IllegalArgumentException("expected policy [" + name + "] to specify '- file: ...'"); + } + while (parser.nextToken() != XContentParser.Token.END_OBJECT) { + if (parser.currentToken() != XContentParser.Token.FIELD_NAME) { + throw new IllegalArgumentException("expected policy [" + name + "] to specify 'file: '"); + } + String currentFieldName = parser.currentName(); + if (currentFieldName.equals(PATH_PARSEFIELD.getPreferredName())) { + if (parser.nextToken() != XContentParser.Token.VALUE_STRING) { + throw new IllegalArgumentException( + "expected field [" + + PATH_PARSEFIELD.getPreferredName() + + "] to have a value for entitlement type [" + + FILE_PARSEFIELD.getPreferredName() + + "] for policy [" + + name + + "]" + ); + } + // TODO: store path name and check for duplicates + } else if (currentFieldName.equals(ACTIONS_PARSEFIELD.getPreferredName())) { + if (parser.nextToken() != XContentParser.Token.START_ARRAY) { + throw new IllegalArgumentException( + "expected field [" + + ACTIONS_PARSEFIELD.getPreferredName() + + "] to have an array for entitlement type [" + + FILE_PARSEFIELD.getPreferredName() + + "] for policy [" + + name + + "]" + ); + } + while (parser.nextToken() != XContentParser.Token.END_ARRAY) { + if (parser.currentToken() != XContentParser.Token.VALUE_STRING) { + throw new IllegalArgumentException("expected value for file action in for entitlement type [" + currentFieldName + "] for policy [" + name + "]"); + } + // TODO: store actions and check for duplicates + } + } else { + throw new IllegalArgumentException( + "unexpected field [" + currentFieldName + "] for entitlement type [" + currentFieldName + "] for policy [" + name + "]" + ); + } + } } private PolicyBuilder() { From 8d40a4df0dc21d23ea6517836d6f0be03f3ceddb Mon Sep 17 00:00:00 2001 From: Jack Conradson Date: Wed, 9 Oct 2024 10:49:44 -0700 Subject: [PATCH 3/9] split builders into pieces and clean up error messaging --- .../policy/EntitlementBuilder.java | 29 +++ .../policy/FileEntitlementBuilder.java | 120 ++++++++++ .../policy/ModuleScopeBuilder.java | 34 +++ .../entitlement/policy/PolicyBuilder.java | 209 ++++++------------ .../policy/PolicyParserException.java | 89 ++++++++ .../entitlement/policy/ScopeBuilder.java | 131 +++++++++++ .../policy/PolicyBuilderTests.java | 2 +- 7 files changed, 467 insertions(+), 147 deletions(-) create mode 100644 libs/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/policy/EntitlementBuilder.java create mode 100644 libs/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/policy/FileEntitlementBuilder.java create mode 100644 libs/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/policy/ModuleScopeBuilder.java create mode 100644 libs/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/policy/PolicyParserException.java create mode 100644 libs/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/policy/ScopeBuilder.java diff --git a/libs/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/policy/EntitlementBuilder.java b/libs/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/policy/EntitlementBuilder.java new file mode 100644 index 0000000000000..f1f0b21e7708b --- /dev/null +++ b/libs/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/policy/EntitlementBuilder.java @@ -0,0 +1,29 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +package org.elasticsearch.entitlement.policy; + +import org.elasticsearch.xcontent.XContentParser; + +import java.io.IOException; + +public abstract class EntitlementBuilder { + + protected final String policyName; + protected final String scopeName; + protected final XContentParser policyParser; + + protected EntitlementBuilder(String policyName, String scopeName, XContentParser policyParser) { + this.policyName = policyName; + this.scopeName = scopeName; + this.policyParser = policyParser; + } + + protected abstract void parseEntitlement() throws IOException; +} diff --git a/libs/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/policy/FileEntitlementBuilder.java b/libs/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/policy/FileEntitlementBuilder.java new file mode 100644 index 0000000000000..bda9fd693cad7 --- /dev/null +++ b/libs/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/policy/FileEntitlementBuilder.java @@ -0,0 +1,120 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +package org.elasticsearch.entitlement.policy; + +import org.elasticsearch.xcontent.ParseField; +import org.elasticsearch.xcontent.XContentParser; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import static org.elasticsearch.entitlement.policy.PolicyParserException.newPolicyParserException; + +public class FileEntitlementBuilder extends EntitlementBuilder { + + public static ParseField FILE_PARSEFIELD = new ParseField("file"); + public static ParseField PATH_PARSEFIELD = new ParseField("path"); + public static ParseField ACTIONS_PARSEFIELD = new ParseField("actions"); + + protected String path; + protected final List actions = new ArrayList<>(); + + protected FileEntitlementBuilder(String policyName, String scopeName, XContentParser policyParser) { + super(policyName, scopeName, policyParser); + } + + protected void parseEntitlement() throws IOException { + if (policyParser.nextToken() != XContentParser.Token.START_OBJECT) { + throw newPolicyParserException( + policyParser.getTokenLocation(), + policyName, + scopeName, + "expected object [" + FILE_PARSEFIELD.getPreferredName() + "]" + ); + } + while (policyParser.nextToken() != XContentParser.Token.END_OBJECT) { + if (policyParser.currentToken() != XContentParser.Token.FIELD_NAME) { + throw newPolicyParserException( + policyParser.getTokenLocation(), + policyName, + scopeName, + "expected object [" + FILE_PARSEFIELD.getPreferredName() + "]" + ); + } + String currentFieldName = policyParser.currentName(); + if (currentFieldName.equals(PATH_PARSEFIELD.getPreferredName())) { + if (policyParser.nextToken() != XContentParser.Token.VALUE_STRING) { + throw newPolicyParserException( + policyParser.getTokenLocation(), + policyName, + scopeName, + FILE_PARSEFIELD.getPreferredName(), + "expected value for field [" + PATH_PARSEFIELD.getPreferredName() + "]" + ); + } + if (path == null) { + path = policyParser.text(); + } else { + throw newPolicyParserException( + policyParser.getTokenLocation(), + policyName, + "field [" + + PATH_PARSEFIELD.getPreferredName() + + "] has already been set; found multiple values" + + "['" + + path + + "', '" + + policyParser.text() + + "']" + ); + } + } else if (currentFieldName.equals(ACTIONS_PARSEFIELD.getPreferredName())) { + if (policyParser.nextToken() != XContentParser.Token.START_ARRAY) { + throw newPolicyParserException( + policyParser.getTokenLocation(), + policyName, + scopeName, + FILE_PARSEFIELD.getPreferredName(), + "expected array for field [" + ACTIONS_PARSEFIELD.getPreferredName() + "]" + ); + } + while (policyParser.nextToken() != XContentParser.Token.END_ARRAY) { + if (policyParser.currentToken() != XContentParser.Token.VALUE_STRING) { + throw newPolicyParserException( + policyParser.getTokenLocation(), + policyName, + scopeName, + FILE_PARSEFIELD.getPreferredName(), + "expected value(s) for array [" + ACTIONS_PARSEFIELD.getPreferredName() + "]" + ); + } + String action = policyParser.text(); + if (actions.contains(action) == false) { + actions.add(action); + } else { + throw newPolicyParserException( + policyParser.getTokenLocation(), + policyName, + "field [" + ACTIONS_PARSEFIELD.getPreferredName() + "] already has value [" + policyParser.text() + "]" + ); + } + } + } else { + throw newPolicyParserException( + policyParser.getTokenLocation(), + policyName, + scopeName, + "unexpected field [" + currentFieldName + "]" + ); + } + } + } +} diff --git a/libs/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/policy/ModuleScopeBuilder.java b/libs/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/policy/ModuleScopeBuilder.java new file mode 100644 index 0000000000000..22f07fe37da29 --- /dev/null +++ b/libs/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/policy/ModuleScopeBuilder.java @@ -0,0 +1,34 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +package org.elasticsearch.entitlement.policy; + +import org.elasticsearch.xcontent.ParseField; +import org.elasticsearch.xcontent.XContentParser; + +public class ModuleScopeBuilder extends ScopeBuilder { + + public static ParseField MODULE_PARSEFIELD = new ParseField("module"); + + protected String moduleScopeName; + + public ModuleScopeBuilder(String policyName, XContentParser policyParser) { + super(policyName, policyParser); + } + + @Override + protected void setScopeName(String scopeName) { + this.moduleScopeName = scopeName; + } + + @Override + protected String getScopeName() { + return moduleScopeName; + } +} diff --git a/libs/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/policy/PolicyBuilder.java b/libs/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/policy/PolicyBuilder.java index ab465062ef6bb..47a257b0168c6 100644 --- a/libs/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/policy/PolicyBuilder.java +++ b/libs/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/policy/PolicyBuilder.java @@ -16,174 +16,91 @@ import java.io.IOException; import java.io.InputStream; +import java.io.UncheckedIOException; +import java.util.ArrayList; +import java.util.List; + +import static org.elasticsearch.entitlement.policy.PolicyParserException.newPolicyParserException; public class PolicyBuilder { public static ParseField POLICY_PARSEFIELD = new ParseField("policy"); - public static ParseField MODULE_PARSEFIELD = new ParseField("module"); - public static ParseField NAME_PARSEFIELD = new ParseField("name"); - public static ParseField ENTITLEMENTS_PARSEFIELD = new ParseField("entitlements"); - public static ParseField FILE_PARSEFIELD = new ParseField("file"); - public static ParseField PATH_PARSEFIELD = new ParseField("path"); - public static ParseField ACTIONS_PARSEFIELD = new ParseField("actions"); + public final String policyName; + public final XContentParser policyParser; + + public final List policyModules = new ArrayList<>(); - /* - START_OBJECT - FIELD_NAME: policy - START_OBJECT - FIELD_NAME: module - START_OBJECT - FIELD_NAME: name - VALUE_STRING: entitlement-test - FIELD_NAME: entitlements - START_ARRAY - START_OBJECT - FIELD_NAME: file - START_OBJECT - FIELD_NAME: path - VALUE_STRING: test/path/to/file - FIELD_NAME: actions - START_ARRAY - VALUE_STRING: read - VALUE_STRING: write - END_ARRAY - END_OBJECT - END_OBJECT - END_ARRAY - END_OBJECT - END_OBJECT - END_OBJECT - */ + public PolicyBuilder(String policyName, InputStream inputStream) { + this.policyName = policyName; - public static void parsePolicy(String name, InputStream is) { try { - XContentParser parser = YamlXContent.yamlXContent.createParser(XContentParserConfiguration.EMPTY, is); - if (parser.nextToken() != XContentParser.Token.START_OBJECT) { - throw new IllegalArgumentException("expected policy [" + name + "] to specify 'policy: ...'"); - } - if (parser.nextToken() != XContentParser.Token.FIELD_NAME - || parser.currentName().equals(POLICY_PARSEFIELD.getPreferredName()) == false) { - throw new IllegalArgumentException("expected policy [" + name + "] to specify 'policy: ...'"); - } - if (parser.nextToken() != XContentParser.Token.START_ARRAY) { - throw new IllegalArgumentException("expected policy [" + name + "] to specify 'module: ...'"); - } - while (parser.nextToken() != XContentParser.Token.END_ARRAY) { - if (parser.currentToken() != XContentParser.Token.START_OBJECT) { - throw new IllegalArgumentException("expected policy [" + name + "] to specify 'module: ...'"); - } - if (parser.nextToken() != XContentParser.Token.FIELD_NAME - || parser.currentName().equals(MODULE_PARSEFIELD.getPreferredName()) == false) { - throw new IllegalArgumentException("expected policy [" + name + "] to specify 'module: ...'"); - } - if (parser.nextToken() != XContentParser.Token.START_OBJECT) { - throw new IllegalArgumentException("expected policy [" + name + "] to specify 'module: ...'"); - } - parseModulePolicy(name, parser); - if (parser.nextToken() != XContentParser.Token.END_OBJECT) { - throw new IllegalArgumentException("expected policy [" + name + "] to specify closing brace '}'"); - } - } - if (parser.nextToken() != XContentParser.Token.END_OBJECT) { - throw new IllegalArgumentException("expected policy [" + name + "] to specify closing brace '}'"); - } + this.policyParser = YamlXContent.yamlXContent.createParser(XContentParserConfiguration.EMPTY, inputStream); } catch (IOException ioe) { - throw new RuntimeException(ioe); - } - } - - private static void parseModulePolicy(String name, XContentParser parser) throws IOException { - while (parser.nextToken() != XContentParser.Token.END_OBJECT) { - if (parser.currentToken() != XContentParser.Token.FIELD_NAME) { - throw new IllegalArgumentException("expected policy [" + name + "] to specify 'module: ...'"); - } - String currentFieldName = parser.currentName(); - if (currentFieldName.equals(NAME_PARSEFIELD.getPreferredName())) { - if (parser.nextToken() != XContentParser.Token.VALUE_STRING) { - throw new IllegalArgumentException("expected policy [" + name + "] to specify value for 'name: '"); - } - // TODO: store module name and check for duplicates - } else if (currentFieldName.equals(ENTITLEMENTS_PARSEFIELD.getPreferredName())) { - if (parser.nextToken() != XContentParser.Token.START_ARRAY) { - throw new IllegalArgumentException("expected policy [" + name + "] to specify 'entitlements: [...]'"); - } - parseEntitlements(name, parser); - } else { - throw new IllegalArgumentException("unexpected field name [" + currentFieldName + "] for policy [" + name + "]"); - } + throw new UncheckedIOException(ioe); } } - private static void parseEntitlements(String name, XContentParser parser) throws IOException { - while (parser.nextToken() != XContentParser.Token.END_ARRAY) { - if (parser.currentToken() != XContentParser.Token.START_OBJECT) { - throw new IllegalArgumentException("expected policy [" + name + "] to specify '- : ...'"); - } - if (parser.nextToken() != XContentParser.Token.FIELD_NAME) { - throw new IllegalArgumentException("expected policy [" + name + "] to specify '- : ...'"); - } - String currentFieldName = parser.currentName(); - if (currentFieldName.equals(FILE_PARSEFIELD.getPreferredName())) { - parseFileEntitlement(name, parser); - } else { - throw new IllegalArgumentException("unexpected entitlement type [" + currentFieldName + "] for policy [" + name + "]"); + public void buildPolicy() { + try { + if (policyParser.nextToken() != XContentParser.Token.START_OBJECT) { + throw newPolicyParserException( + policyParser.getTokenLocation(), + policyName, + "expected object [" + POLICY_PARSEFIELD.getPreferredName() + "]" + ); } - if (parser.nextToken() != XContentParser.Token.END_OBJECT) { - throw new IllegalArgumentException("expected policy [" + name + "] to specify closing brace '}'"); + if (policyParser.nextToken() != XContentParser.Token.FIELD_NAME + || policyParser.currentName().equals(POLICY_PARSEFIELD.getPreferredName()) == false) { + throw newPolicyParserException( + policyParser.getTokenLocation(), + policyName, + "expected object [" + POLICY_PARSEFIELD.getPreferredName() + "]" + ); } - } - } - - private static void parseFileEntitlement(String name, XContentParser parser) throws IOException { - if (parser.nextToken() != XContentParser.Token.START_OBJECT) { - throw new IllegalArgumentException("expected policy [" + name + "] to specify '- file: ...'"); - } - while (parser.nextToken() != XContentParser.Token.END_OBJECT) { - if (parser.currentToken() != XContentParser.Token.FIELD_NAME) { - throw new IllegalArgumentException("expected policy [" + name + "] to specify 'file: '"); + if (policyParser.nextToken() != XContentParser.Token.START_ARRAY) { + throw newPolicyParserException( + policyParser.getTokenLocation(), + policyName, + "expected array of [" + ModuleScopeBuilder.MODULE_PARSEFIELD.getPreferredName() + "]" + ); } - String currentFieldName = parser.currentName(); - if (currentFieldName.equals(PATH_PARSEFIELD.getPreferredName())) { - if (parser.nextToken() != XContentParser.Token.VALUE_STRING) { - throw new IllegalArgumentException( - "expected field [" - + PATH_PARSEFIELD.getPreferredName() - + "] to have a value for entitlement type [" - + FILE_PARSEFIELD.getPreferredName() - + "] for policy [" - + name - + "]" + while (policyParser.nextToken() != XContentParser.Token.END_ARRAY) { + if (policyParser.currentToken() != XContentParser.Token.START_OBJECT) { + throw newPolicyParserException( + policyParser.getTokenLocation(), + policyName, + "expected object [" + ModuleScopeBuilder.MODULE_PARSEFIELD.getPreferredName() + "]" ); } - // TODO: store path name and check for duplicates - } else if (currentFieldName.equals(ACTIONS_PARSEFIELD.getPreferredName())) { - if (parser.nextToken() != XContentParser.Token.START_ARRAY) { - throw new IllegalArgumentException( - "expected field [" - + ACTIONS_PARSEFIELD.getPreferredName() - + "] to have an array for entitlement type [" - + FILE_PARSEFIELD.getPreferredName() - + "] for policy [" - + name - + "]" + if (policyParser.nextToken() != XContentParser.Token.FIELD_NAME + || policyParser.currentName().equals(ModuleScopeBuilder.MODULE_PARSEFIELD.getPreferredName()) == false) { + throw newPolicyParserException( + policyParser.getTokenLocation(), + policyName, + "expected object [" + ModuleScopeBuilder.MODULE_PARSEFIELD.getPreferredName() + "]" ); } - while (parser.nextToken() != XContentParser.Token.END_ARRAY) { - if (parser.currentToken() != XContentParser.Token.VALUE_STRING) { - throw new IllegalArgumentException("expected value for file action in for entitlement type [" + currentFieldName + "] for policy [" + name + "]"); - } - // TODO: store actions and check for duplicates + if (policyParser.nextToken() != XContentParser.Token.START_OBJECT) { + throw newPolicyParserException( + policyParser.getTokenLocation(), + policyName, + "expected object [" + ModuleScopeBuilder.MODULE_PARSEFIELD.getPreferredName() + "]" + ); } - } else { - throw new IllegalArgumentException( - "unexpected field [" + currentFieldName + "] for entitlement type [" + currentFieldName + "] for policy [" + name + "]" - ); + ModuleScopeBuilder moduleScopeBuilder = new ModuleScopeBuilder(policyName, policyParser); + moduleScopeBuilder.parseScope(); + policyModules.add(moduleScopeBuilder); + if (policyParser.nextToken() != XContentParser.Token.END_OBJECT) { + throw newPolicyParserException(policyParser.getTokenLocation(), policyName, "expected closing object"); + } + } + if (policyParser.nextToken() != XContentParser.Token.END_OBJECT) { + throw newPolicyParserException(policyParser.getTokenLocation(), policyName, "expected closing object"); } + } catch (IOException ioe) { + throw new UncheckedIOException(ioe); } } - private PolicyBuilder() { - // do nothing - } } diff --git a/libs/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/policy/PolicyParserException.java b/libs/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/policy/PolicyParserException.java new file mode 100644 index 0000000000000..fc3e201777cc8 --- /dev/null +++ b/libs/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/policy/PolicyParserException.java @@ -0,0 +1,89 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +package org.elasticsearch.entitlement.policy; + +import org.elasticsearch.xcontent.XContentLocation; + +public class PolicyParserException extends RuntimeException { + + public static PolicyParserException newPolicyParserException(XContentLocation location, String policyName, String message) { + return new PolicyParserException( + "[" + location.lineNumber() + ":" + location.columnNumber() + "] policy parsing error for [" + policyName + "]: " + message + ); + } + + public static PolicyParserException newPolicyParserException( + XContentLocation location, + String policyName, + String scopeName, + String message + ) { + if (scopeName == null) { + return new PolicyParserException( + "[" + location.lineNumber() + ":" + location.columnNumber() + "] policy parsing error for [" + policyName + "]: " + message + ); + } else { + return new PolicyParserException( + "[" + + location.lineNumber() + + ":" + + location.columnNumber() + + "] policy parsing error for [" + + policyName + + "] in scope [" + + scopeName + + "]: " + + message + ); + } + } + + public static PolicyParserException newPolicyParserException( + XContentLocation location, + String policyName, + String scopeName, + String entitlementType, + String message + ) { + if (scopeName == null) { + return new PolicyParserException( + "[" + + location.lineNumber() + + ":" + + location.columnNumber() + + "] policy parsing error for [" + + policyName + + "] for entitlement type [" + + entitlementType + + "]: " + + message + ); + } else { + return new PolicyParserException( + "[" + + location.lineNumber() + + ":" + + location.columnNumber() + + "] policy parsing error for [" + + policyName + + "] in scope [" + + scopeName + + "] for entitlement type [" + + entitlementType + + "]: " + + message + ); + } + } + + private PolicyParserException(String message) { + super(message); + } +} diff --git a/libs/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/policy/ScopeBuilder.java b/libs/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/policy/ScopeBuilder.java new file mode 100644 index 0000000000000..de569291d8b37 --- /dev/null +++ b/libs/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/policy/ScopeBuilder.java @@ -0,0 +1,131 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +package org.elasticsearch.entitlement.policy; + +import org.elasticsearch.xcontent.ParseField; +import org.elasticsearch.xcontent.XContentParser; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import static org.elasticsearch.entitlement.policy.PolicyParserException.newPolicyParserException; + +public abstract class ScopeBuilder { + + public static ParseField NAME_PARSEFIELD = new ParseField("name"); + public static ParseField ENTITLEMENTS_PARSEFIELD = new ParseField("entitlements"); + + protected final String policyName; + protected final XContentParser policyParser; + + protected final List fileEntitlementBuilders = new ArrayList<>(); + + protected ScopeBuilder(String policyName, XContentParser policyParser) { + this.policyName = policyName; + this.policyParser = policyParser; + } + + protected void parseScope() throws IOException { + while (policyParser.nextToken() != XContentParser.Token.END_OBJECT) { + if (policyParser.currentToken() != XContentParser.Token.FIELD_NAME) { + throw newPolicyParserException( + policyParser.getTokenLocation(), + policyName, + "expected object [" + ModuleScopeBuilder.MODULE_PARSEFIELD.getPreferredName() + "]" + ); + } + String currentFieldName = policyParser.currentName(); + if (currentFieldName.equals(NAME_PARSEFIELD.getPreferredName())) { + if (policyParser.nextToken() != XContentParser.Token.VALUE_STRING) { + throw newPolicyParserException( + policyParser.getTokenLocation(), + policyName, + "expected value for field [" + NAME_PARSEFIELD.getPreferredName() + "]" + ); + } + if (getScopeName() == null) { + setScopeName(policyParser.text()); + } else { + throw newPolicyParserException( + policyParser.getTokenLocation(), + policyName, + "field [" + + NAME_PARSEFIELD.getPreferredName() + + "] has already been set; found multiple values" + + "['" + + getScopeName() + + "', '" + + policyParser.text() + + "']" + ); + } + } else if (currentFieldName.equals(ENTITLEMENTS_PARSEFIELD.getPreferredName())) { + if (policyParser.nextToken() != XContentParser.Token.START_ARRAY) { + throw newPolicyParserException( + policyParser.getTokenLocation(), + policyName, + getScopeName(), + "expected array of " + ); + } + parseEntitlements(); + } else { + throw newPolicyParserException( + policyParser.getTokenLocation(), + policyName, + getScopeName(), + "unexpected field [" + currentFieldName + "]" + ); + } + } + } + + protected void parseEntitlements() throws IOException { + while (policyParser.nextToken() != XContentParser.Token.END_ARRAY) { + if (policyParser.currentToken() != XContentParser.Token.START_OBJECT) { + throw newPolicyParserException( + policyParser.getTokenLocation(), + policyName, + getScopeName(), + "expected object " + ); + } + if (policyParser.nextToken() != XContentParser.Token.FIELD_NAME) { + throw newPolicyParserException( + policyParser.getTokenLocation(), + policyName, + getScopeName(), + "expected object " + ); + } + String currentFieldName = policyParser.currentName(); + if (currentFieldName.equals(FileEntitlementBuilder.FILE_PARSEFIELD.getPreferredName())) { + FileEntitlementBuilder fileEntitlementBuilder = new FileEntitlementBuilder(policyName, getScopeName(), policyParser); + fileEntitlementBuilder.parseEntitlement(); + fileEntitlementBuilders.add(fileEntitlementBuilder); + } else { + throw newPolicyParserException( + policyParser.getTokenLocation(), + policyName, + getScopeName(), + "unexpected field [" + currentFieldName + "]" + ); + } + if (policyParser.nextToken() != XContentParser.Token.END_OBJECT) { + throw newPolicyParserException(policyParser.getTokenLocation(), policyName, getScopeName(), "expected closing object"); + } + } + } + + protected abstract void setScopeName(String scopeName); + + protected abstract String getScopeName(); +} diff --git a/libs/entitlement-runtime/src/test/java/org/elasticsearch/entitlement/policy/PolicyBuilderTests.java b/libs/entitlement-runtime/src/test/java/org/elasticsearch/entitlement/policy/PolicyBuilderTests.java index 22dd24b59eea8..40baf025bc9f5 100644 --- a/libs/entitlement-runtime/src/test/java/org/elasticsearch/entitlement/policy/PolicyBuilderTests.java +++ b/libs/entitlement-runtime/src/test/java/org/elasticsearch/entitlement/policy/PolicyBuilderTests.java @@ -14,6 +14,6 @@ public class PolicyBuilderTests extends ESTestCase { public void testPolicyBuilder() { - PolicyBuilder.parsePolicy("test-policy.yaml", PolicyBuilderTests.class.getResourceAsStream("test-policy.yaml")); + new PolicyBuilder("test-policy.yaml", PolicyBuilderTests.class.getResourceAsStream("test-policy.yaml")).buildPolicy(); } } From 70a1e47782f49682e94df22df56fbc84025cb71c Mon Sep 17 00:00:00 2001 From: Jack Conradson Date: Fri, 11 Oct 2024 13:41:10 -0700 Subject: [PATCH 4/9] update based on pr comments --- ...entBuilder.java => EntitlementParser.java} | 4 +- ...uilder.java => FileEntitlementParser.java} | 4 +- ...opeBuilder.java => ModuleScopeParser.java} | 4 +- .../{PolicyBuilder.java => PolicyParser.java} | 37 ++++++++----------- .../{ScopeBuilder.java => ScopeParser.java} | 16 ++++---- ...ilderTests.java => PolicyParserTests.java} | 8 ++-- 6 files changed, 35 insertions(+), 38 deletions(-) rename distribution/tools/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/runtime/policy/{EntitlementBuilder.java => EntitlementParser.java} (86%) rename distribution/tools/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/runtime/policy/{FileEntitlementBuilder.java => FileEntitlementParser.java} (96%) rename distribution/tools/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/runtime/policy/{ModuleScopeBuilder.java => ModuleScopeParser.java} (88%) rename distribution/tools/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/runtime/policy/{PolicyBuilder.java => PolicyParser.java} (75%) rename distribution/tools/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/runtime/policy/{ScopeBuilder.java => ScopeParser.java} (88%) rename distribution/tools/entitlement-runtime/src/test/java/org/elasticsearch/entitlement/runtime/policy/{PolicyBuilderTests.java => PolicyParserTests.java} (68%) diff --git a/distribution/tools/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/runtime/policy/EntitlementBuilder.java b/distribution/tools/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/runtime/policy/EntitlementParser.java similarity index 86% rename from distribution/tools/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/runtime/policy/EntitlementBuilder.java rename to distribution/tools/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/runtime/policy/EntitlementParser.java index c7aa7956956b2..ff6e8488df46a 100644 --- a/distribution/tools/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/runtime/policy/EntitlementBuilder.java +++ b/distribution/tools/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/runtime/policy/EntitlementParser.java @@ -13,13 +13,13 @@ import java.io.IOException; -public abstract class EntitlementBuilder { +public abstract class EntitlementParser { protected final String policyName; protected final String scopeName; protected final XContentParser policyParser; - protected EntitlementBuilder(String policyName, String scopeName, XContentParser policyParser) { + protected EntitlementParser(String policyName, String scopeName, XContentParser policyParser) { this.policyName = policyName; this.scopeName = scopeName; this.policyParser = policyParser; diff --git a/distribution/tools/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/runtime/policy/FileEntitlementBuilder.java b/distribution/tools/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/runtime/policy/FileEntitlementParser.java similarity index 96% rename from distribution/tools/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/runtime/policy/FileEntitlementBuilder.java rename to distribution/tools/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/runtime/policy/FileEntitlementParser.java index 99abb15ab4da1..6ae8bca5c11d9 100644 --- a/distribution/tools/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/runtime/policy/FileEntitlementBuilder.java +++ b/distribution/tools/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/runtime/policy/FileEntitlementParser.java @@ -18,7 +18,7 @@ import static org.elasticsearch.entitlement.runtime.policy.PolicyParserException.newPolicyParserException; -public class FileEntitlementBuilder extends EntitlementBuilder { +public class FileEntitlementParser extends EntitlementParser { public static ParseField FILE_PARSEFIELD = new ParseField("file"); public static ParseField PATH_PARSEFIELD = new ParseField("path"); @@ -27,7 +27,7 @@ public class FileEntitlementBuilder extends EntitlementBuilder { protected String path; protected final List actions = new ArrayList<>(); - protected FileEntitlementBuilder(String policyName, String scopeName, XContentParser policyParser) { + protected FileEntitlementParser(String policyName, String scopeName, XContentParser policyParser) { super(policyName, scopeName, policyParser); } diff --git a/distribution/tools/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/runtime/policy/ModuleScopeBuilder.java b/distribution/tools/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/runtime/policy/ModuleScopeParser.java similarity index 88% rename from distribution/tools/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/runtime/policy/ModuleScopeBuilder.java rename to distribution/tools/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/runtime/policy/ModuleScopeParser.java index eb2ed2e8af5a7..de5a263c5f484 100644 --- a/distribution/tools/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/runtime/policy/ModuleScopeBuilder.java +++ b/distribution/tools/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/runtime/policy/ModuleScopeParser.java @@ -12,13 +12,13 @@ import org.elasticsearch.xcontent.ParseField; import org.elasticsearch.xcontent.XContentParser; -public class ModuleScopeBuilder extends ScopeBuilder { +public class ModuleScopeParser extends ScopeParser { public static ParseField MODULE_PARSEFIELD = new ParseField("module"); protected String moduleScopeName; - public ModuleScopeBuilder(String policyName, XContentParser policyParser) { + public ModuleScopeParser(String policyName, XContentParser policyParser) { super(policyName, policyParser); } diff --git a/distribution/tools/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/runtime/policy/PolicyBuilder.java b/distribution/tools/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/runtime/policy/PolicyParser.java similarity index 75% rename from distribution/tools/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/runtime/policy/PolicyBuilder.java rename to distribution/tools/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/runtime/policy/PolicyParser.java index e9ece34ec0e0a..9248fb621d7d1 100644 --- a/distribution/tools/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/runtime/policy/PolicyBuilder.java +++ b/distribution/tools/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/runtime/policy/PolicyParser.java @@ -23,29 +23,24 @@ import static org.elasticsearch.entitlement.runtime.policy.PolicyParserException.newPolicyParserException; -public class PolicyBuilder { +public class PolicyParser { public static ParseField POLICY_PARSEFIELD = new ParseField("policy"); public final String policyName; public final XContentParser policyParser; - public final List policyModules = new ArrayList<>(); + public final List policyModuleParsers = new ArrayList<>(); - public PolicyBuilder(String policyName, InputStream inputStream) { + public PolicyParser(String policyName, InputStream inputStream) throws IOException { this.policyName = policyName; - - try { - this.policyParser = YamlXContent.yamlXContent.createParser( - XContentParserConfiguration.EMPTY, - Objects.requireNonNull(inputStream) - ); - } catch (IOException ioe) { - throw new UncheckedIOException(ioe); - } + this.policyParser = YamlXContent.yamlXContent.createParser( + XContentParserConfiguration.EMPTY, + Objects.requireNonNull(inputStream) + ); } - public void buildPolicy() { + public void parsePolicy() { try { if (policyParser.nextToken() != XContentParser.Token.START_OBJECT) { throw newPolicyParserException( @@ -66,7 +61,7 @@ public void buildPolicy() { throw newPolicyParserException( policyParser.getTokenLocation(), policyName, - "expected array of [" + ModuleScopeBuilder.MODULE_PARSEFIELD.getPreferredName() + "]" + "expected array of [" + ModuleScopeParser.MODULE_PARSEFIELD.getPreferredName() + "]" ); } while (policyParser.nextToken() != XContentParser.Token.END_ARRAY) { @@ -74,27 +69,27 @@ public void buildPolicy() { throw newPolicyParserException( policyParser.getTokenLocation(), policyName, - "expected object [" + ModuleScopeBuilder.MODULE_PARSEFIELD.getPreferredName() + "]" + "expected object [" + ModuleScopeParser.MODULE_PARSEFIELD.getPreferredName() + "]" ); } if (policyParser.nextToken() != XContentParser.Token.FIELD_NAME - || policyParser.currentName().equals(ModuleScopeBuilder.MODULE_PARSEFIELD.getPreferredName()) == false) { + || policyParser.currentName().equals(ModuleScopeParser.MODULE_PARSEFIELD.getPreferredName()) == false) { throw newPolicyParserException( policyParser.getTokenLocation(), policyName, - "expected object [" + ModuleScopeBuilder.MODULE_PARSEFIELD.getPreferredName() + "]" + "expected object [" + ModuleScopeParser.MODULE_PARSEFIELD.getPreferredName() + "]" ); } if (policyParser.nextToken() != XContentParser.Token.START_OBJECT) { throw newPolicyParserException( policyParser.getTokenLocation(), policyName, - "expected object [" + ModuleScopeBuilder.MODULE_PARSEFIELD.getPreferredName() + "]" + "expected object [" + ModuleScopeParser.MODULE_PARSEFIELD.getPreferredName() + "]" ); } - ModuleScopeBuilder moduleScopeBuilder = new ModuleScopeBuilder(policyName, policyParser); - moduleScopeBuilder.parseScope(); - policyModules.add(moduleScopeBuilder); + ModuleScopeParser moduleScopeParser = new ModuleScopeParser(policyName, policyParser); + moduleScopeParser.parseScope(); + policyModuleParsers.add(moduleScopeParser); if (policyParser.nextToken() != XContentParser.Token.END_OBJECT) { throw newPolicyParserException(policyParser.getTokenLocation(), policyName, "expected closing object"); } diff --git a/distribution/tools/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/runtime/policy/ScopeBuilder.java b/distribution/tools/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/runtime/policy/ScopeParser.java similarity index 88% rename from distribution/tools/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/runtime/policy/ScopeBuilder.java rename to distribution/tools/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/runtime/policy/ScopeParser.java index eb02d723323d4..f84b4b5d5791f 100644 --- a/distribution/tools/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/runtime/policy/ScopeBuilder.java +++ b/distribution/tools/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/runtime/policy/ScopeParser.java @@ -18,7 +18,7 @@ import static org.elasticsearch.entitlement.runtime.policy.PolicyParserException.newPolicyParserException; -public abstract class ScopeBuilder { +public abstract class ScopeParser { public static ParseField NAME_PARSEFIELD = new ParseField("name"); public static ParseField ENTITLEMENTS_PARSEFIELD = new ParseField("entitlements"); @@ -26,9 +26,9 @@ public abstract class ScopeBuilder { protected final String policyName; protected final XContentParser policyParser; - protected final List fileEntitlementBuilders = new ArrayList<>(); + protected final List fileEntitlementParsers = new ArrayList<>(); - protected ScopeBuilder(String policyName, XContentParser policyParser) { + protected ScopeParser(String policyName, XContentParser policyParser) { this.policyName = policyName; this.policyParser = policyParser; } @@ -39,7 +39,7 @@ protected void parseScope() throws IOException { throw newPolicyParserException( policyParser.getTokenLocation(), policyName, - "expected object [" + ModuleScopeBuilder.MODULE_PARSEFIELD.getPreferredName() + "]" + "expected object [" + ModuleScopeParser.MODULE_PARSEFIELD.getPreferredName() + "]" ); } String currentFieldName = policyParser.currentName(); @@ -107,10 +107,10 @@ protected void parseEntitlements() throws IOException { ); } String currentFieldName = policyParser.currentName(); - if (currentFieldName.equals(FileEntitlementBuilder.FILE_PARSEFIELD.getPreferredName())) { - FileEntitlementBuilder fileEntitlementBuilder = new FileEntitlementBuilder(policyName, getScopeName(), policyParser); - fileEntitlementBuilder.parseEntitlement(); - fileEntitlementBuilders.add(fileEntitlementBuilder); + if (currentFieldName.equals(FileEntitlementParser.FILE_PARSEFIELD.getPreferredName())) { + FileEntitlementParser fileEntitlementParser = new FileEntitlementParser(policyName, getScopeName(), policyParser); + fileEntitlementParser.parseEntitlement(); + fileEntitlementParsers.add(fileEntitlementParser); } else { throw newPolicyParserException( policyParser.getTokenLocation(), diff --git a/distribution/tools/entitlement-runtime/src/test/java/org/elasticsearch/entitlement/runtime/policy/PolicyBuilderTests.java b/distribution/tools/entitlement-runtime/src/test/java/org/elasticsearch/entitlement/runtime/policy/PolicyParserTests.java similarity index 68% rename from distribution/tools/entitlement-runtime/src/test/java/org/elasticsearch/entitlement/runtime/policy/PolicyBuilderTests.java rename to distribution/tools/entitlement-runtime/src/test/java/org/elasticsearch/entitlement/runtime/policy/PolicyParserTests.java index 9f8b87c51b8b9..9f7194beb8c5b 100644 --- a/distribution/tools/entitlement-runtime/src/test/java/org/elasticsearch/entitlement/runtime/policy/PolicyBuilderTests.java +++ b/distribution/tools/entitlement-runtime/src/test/java/org/elasticsearch/entitlement/runtime/policy/PolicyParserTests.java @@ -11,9 +11,11 @@ import org.elasticsearch.test.ESTestCase; -public class PolicyBuilderTests extends ESTestCase { +import java.io.IOException; - public void testPolicyBuilder() { - new PolicyBuilder("test-policy.yaml", PolicyBuilderTests.class.getResourceAsStream("test-policy.yaml")).buildPolicy(); +public class PolicyParserTests extends ESTestCase { + + public void testPolicyBuilder() throws IOException { + new PolicyParser("test-policy.yaml", PolicyParserTests.class.getResourceAsStream("test-policy.yaml")).parsePolicy(); } } From 3ea102069b7301eb71c19a9bd420ea57a755fed7 Mon Sep 17 00:00:00 2001 From: Jack Conradson Date: Fri, 11 Oct 2024 13:50:34 -0700 Subject: [PATCH 5/9] spotless --- .../entitlement/runtime/policy/PolicyParser.java | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/distribution/tools/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/runtime/policy/PolicyParser.java b/distribution/tools/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/runtime/policy/PolicyParser.java index 9248fb621d7d1..5265bda496711 100644 --- a/distribution/tools/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/runtime/policy/PolicyParser.java +++ b/distribution/tools/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/runtime/policy/PolicyParser.java @@ -34,10 +34,7 @@ public class PolicyParser { public PolicyParser(String policyName, InputStream inputStream) throws IOException { this.policyName = policyName; - this.policyParser = YamlXContent.yamlXContent.createParser( - XContentParserConfiguration.EMPTY, - Objects.requireNonNull(inputStream) - ); + this.policyParser = YamlXContent.yamlXContent.createParser(XContentParserConfiguration.EMPTY, Objects.requireNonNull(inputStream)); } public void parsePolicy() { From de4ce60aa115029fb2740a6a91ebf24b9900d021 Mon Sep 17 00:00:00 2001 From: Jack Conradson Date: Mon, 14 Oct 2024 13:10:57 -0700 Subject: [PATCH 6/9] add failure tests class --- .../policy/PolicyParserFailureTests.java | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 distribution/tools/entitlement-runtime/src/test/java/org/elasticsearch/entitlement/runtime/policy/PolicyParserFailureTests.java diff --git a/distribution/tools/entitlement-runtime/src/test/java/org/elasticsearch/entitlement/runtime/policy/PolicyParserFailureTests.java b/distribution/tools/entitlement-runtime/src/test/java/org/elasticsearch/entitlement/runtime/policy/PolicyParserFailureTests.java new file mode 100644 index 0000000000000..ed7ade1e78002 --- /dev/null +++ b/distribution/tools/entitlement-runtime/src/test/java/org/elasticsearch/entitlement/runtime/policy/PolicyParserFailureTests.java @@ -0,0 +1,18 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +package org.elasticsearch.entitlement.runtime.policy; + +public class PolicyParserFailureTests { + + public void testParserSyntaxFailures() { + + + } +} From 821dc3f57f7bc2cc2e8433603e75c2cf64a63121 Mon Sep 17 00:00:00 2001 From: Jack Conradson Date: Wed, 16 Oct 2024 12:04:51 -0700 Subject: [PATCH 7/9] move packages --- .../runtime/policy/impl/FileEntitlement.java | 24 +++++++++++++ .../runtime/policy/impl/Policy.java} | 12 ++++--- .../runtime/policy/impl/Scope.java | 22 ++++++++++++ .../{ => parser}/EntitlementParser.java | 4 +-- .../{ => parser}/FileEntitlementParser.java | 36 ++++++++++++++----- .../{ => parser}/ModuleScopeParser.java | 2 +- .../policy/{ => parser}/PolicyParser.java | 4 +-- .../{ => parser}/PolicyParserException.java | 2 +- .../policy/{ => parser}/ScopeParser.java | 9 +++-- .../parser/PolicyParserFailureTests.java | 35 ++++++++++++++++++ .../{ => parser}/PolicyParserTests.java | 2 +- .../policy/{ => parser}/test-policy.yaml | 0 12 files changed, 127 insertions(+), 25 deletions(-) create mode 100644 distribution/tools/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/runtime/policy/impl/FileEntitlement.java rename distribution/tools/entitlement-runtime/src/{test/java/org/elasticsearch/entitlement/runtime/policy/PolicyParserFailureTests.java => main/java/org/elasticsearch/entitlement/runtime/policy/impl/Policy.java} (60%) create mode 100644 distribution/tools/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/runtime/policy/impl/Scope.java rename distribution/tools/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/runtime/policy/{ => parser}/EntitlementParser.java (88%) rename distribution/tools/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/runtime/policy/{ => parser}/FileEntitlementParser.java (74%) rename distribution/tools/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/runtime/policy/{ => parser}/ModuleScopeParser.java (94%) rename distribution/tools/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/runtime/policy/{ => parser}/PolicyParser.java (96%) rename distribution/tools/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/runtime/policy/{ => parser}/PolicyParserException.java (98%) rename distribution/tools/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/runtime/policy/{ => parser}/ScopeParser.java (94%) create mode 100644 distribution/tools/entitlement-runtime/src/test/java/org/elasticsearch/entitlement/runtime/policy/parser/PolicyParserFailureTests.java rename distribution/tools/entitlement-runtime/src/test/java/org/elasticsearch/entitlement/runtime/policy/{ => parser}/PolicyParserTests.java (92%) rename distribution/tools/entitlement-runtime/src/test/resources/org/elasticsearch/entitlement/runtime/policy/{ => parser}/test-policy.yaml (100%) diff --git a/distribution/tools/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/runtime/policy/impl/FileEntitlement.java b/distribution/tools/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/runtime/policy/impl/FileEntitlement.java new file mode 100644 index 0000000000000..a33ef4dd6d2a4 --- /dev/null +++ b/distribution/tools/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/runtime/policy/impl/FileEntitlement.java @@ -0,0 +1,24 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +package org.elasticsearch.entitlement.runtime.policy.impl; + +public class FileEntitlement { + + public static final int READ_ACTION = 0x1; + public static final int WRITE_ACTION = 0x2; + + private final String path; + private final int actions; + + public FileEntitlement(String path, int actions) { + this.path = path; + this.actions = actions; + } +} diff --git a/distribution/tools/entitlement-runtime/src/test/java/org/elasticsearch/entitlement/runtime/policy/PolicyParserFailureTests.java b/distribution/tools/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/runtime/policy/impl/Policy.java similarity index 60% rename from distribution/tools/entitlement-runtime/src/test/java/org/elasticsearch/entitlement/runtime/policy/PolicyParserFailureTests.java rename to distribution/tools/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/runtime/policy/impl/Policy.java index ed7ade1e78002..72c9c2b510996 100644 --- a/distribution/tools/entitlement-runtime/src/test/java/org/elasticsearch/entitlement/runtime/policy/PolicyParserFailureTests.java +++ b/distribution/tools/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/runtime/policy/impl/Policy.java @@ -7,12 +7,16 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -package org.elasticsearch.entitlement.runtime.policy; +package org.elasticsearch.entitlement.runtime.policy.impl; -public class PolicyParserFailureTests { +import java.util.Collections; +import java.util.Map; - public void testParserSyntaxFailures() { +public class Policy { - + public final Map moduleScopes; + + public Policy(Map moduleScopes) { + this.moduleScopes = Collections.unmodifiableMap(moduleScopes); } } diff --git a/distribution/tools/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/runtime/policy/impl/Scope.java b/distribution/tools/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/runtime/policy/impl/Scope.java new file mode 100644 index 0000000000000..5986b7adcf3da --- /dev/null +++ b/distribution/tools/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/runtime/policy/impl/Scope.java @@ -0,0 +1,22 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +package org.elasticsearch.entitlement.runtime.policy.impl; + +import java.util.Collections; +import java.util.List; + +public class Scope { + + public final List fileEntitlements; + + public Scope(List fileEntitlements) { + this.fileEntitlements = Collections.unmodifiableList(fileEntitlements); + } +} diff --git a/distribution/tools/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/runtime/policy/EntitlementParser.java b/distribution/tools/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/runtime/policy/parser/EntitlementParser.java similarity index 88% rename from distribution/tools/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/runtime/policy/EntitlementParser.java rename to distribution/tools/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/runtime/policy/parser/EntitlementParser.java index ff6e8488df46a..0a6bbc3a3303e 100644 --- a/distribution/tools/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/runtime/policy/EntitlementParser.java +++ b/distribution/tools/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/runtime/policy/parser/EntitlementParser.java @@ -7,7 +7,7 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -package org.elasticsearch.entitlement.runtime.policy; +package org.elasticsearch.entitlement.runtime.policy.parser; import org.elasticsearch.xcontent.XContentParser; @@ -24,6 +24,4 @@ protected EntitlementParser(String policyName, String scopeName, XContentParser this.scopeName = scopeName; this.policyParser = policyParser; } - - protected abstract void parseEntitlement() throws IOException; } diff --git a/distribution/tools/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/runtime/policy/FileEntitlementParser.java b/distribution/tools/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/runtime/policy/parser/FileEntitlementParser.java similarity index 74% rename from distribution/tools/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/runtime/policy/FileEntitlementParser.java rename to distribution/tools/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/runtime/policy/parser/FileEntitlementParser.java index 6ae8bca5c11d9..b15aa7acc9cea 100644 --- a/distribution/tools/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/runtime/policy/FileEntitlementParser.java +++ b/distribution/tools/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/runtime/policy/parser/FileEntitlementParser.java @@ -7,8 +7,9 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -package org.elasticsearch.entitlement.runtime.policy; +package org.elasticsearch.entitlement.runtime.policy.parser; +import org.elasticsearch.entitlement.runtime.policy.impl.FileEntitlement; import org.elasticsearch.xcontent.ParseField; import org.elasticsearch.xcontent.XContentParser; @@ -16,7 +17,7 @@ import java.util.ArrayList; import java.util.List; -import static org.elasticsearch.entitlement.runtime.policy.PolicyParserException.newPolicyParserException; +import static org.elasticsearch.entitlement.runtime.policy.parser.PolicyParserException.newPolicyParserException; public class FileEntitlementParser extends EntitlementParser { @@ -24,14 +25,14 @@ public class FileEntitlementParser extends EntitlementParser { public static ParseField PATH_PARSEFIELD = new ParseField("path"); public static ParseField ACTIONS_PARSEFIELD = new ParseField("actions"); - protected String path; - protected final List actions = new ArrayList<>(); + public static String READ_ACTION = "read"; + public static String WRITE_ACTION = "write"; protected FileEntitlementParser(String policyName, String scopeName, XContentParser policyParser) { super(policyName, scopeName, policyParser); } - protected void parseEntitlement() throws IOException { + protected FileEntitlement parseEntitlement() throws IOException { if (policyParser.nextToken() != XContentParser.Token.START_OBJECT) { throw newPolicyParserException( policyParser.getTokenLocation(), @@ -40,6 +41,8 @@ protected void parseEntitlement() throws IOException { "expected object [" + FILE_PARSEFIELD.getPreferredName() + "]" ); } + String path = null; + int actions = 0; while (policyParser.nextToken() != XContentParser.Token.END_OBJECT) { if (policyParser.currentToken() != XContentParser.Token.FIELD_NAME) { throw newPolicyParserException( @@ -97,13 +100,29 @@ protected void parseEntitlement() throws IOException { ); } String action = policyParser.text(); - if (actions.contains(action) == false) { - actions.add(action); + if (READ_ACTION.equals(action)) { + if ((actions & FileEntitlement.READ_ACTION) == FileEntitlement.READ_ACTION) { + throw newPolicyParserException( + policyParser.getTokenLocation(), + policyName, + "field [" + ACTIONS_PARSEFIELD.getPreferredName() + "] already has value [" + action + "]" + ); + } + actions |= FileEntitlement.READ_ACTION; + } else if (WRITE_ACTION.equals(action)) { + if ((actions & FileEntitlement.WRITE_ACTION) == FileEntitlement.WRITE_ACTION) { + throw newPolicyParserException( + policyParser.getTokenLocation(), + policyName, + "field [" + ACTIONS_PARSEFIELD.getPreferredName() + "] already has value [" + action + "]" + ); + } + actions |= FileEntitlement.WRITE_ACTION; } else { throw newPolicyParserException( policyParser.getTokenLocation(), policyName, - "field [" + ACTIONS_PARSEFIELD.getPreferredName() + "] already has value [" + policyParser.text() + "]" + "field [" + ACTIONS_PARSEFIELD.getPreferredName() + "] has unknown value [" + action + "]" ); } } @@ -116,5 +135,6 @@ protected void parseEntitlement() throws IOException { ); } } + return new FileEntitlement(path, actions); } } diff --git a/distribution/tools/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/runtime/policy/ModuleScopeParser.java b/distribution/tools/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/runtime/policy/parser/ModuleScopeParser.java similarity index 94% rename from distribution/tools/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/runtime/policy/ModuleScopeParser.java rename to distribution/tools/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/runtime/policy/parser/ModuleScopeParser.java index de5a263c5f484..e9f31ade69c91 100644 --- a/distribution/tools/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/runtime/policy/ModuleScopeParser.java +++ b/distribution/tools/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/runtime/policy/parser/ModuleScopeParser.java @@ -7,7 +7,7 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -package org.elasticsearch.entitlement.runtime.policy; +package org.elasticsearch.entitlement.runtime.policy.parser; import org.elasticsearch.xcontent.ParseField; import org.elasticsearch.xcontent.XContentParser; diff --git a/distribution/tools/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/runtime/policy/PolicyParser.java b/distribution/tools/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/runtime/policy/parser/PolicyParser.java similarity index 96% rename from distribution/tools/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/runtime/policy/PolicyParser.java rename to distribution/tools/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/runtime/policy/parser/PolicyParser.java index 5265bda496711..cd5a42d770b9e 100644 --- a/distribution/tools/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/runtime/policy/PolicyParser.java +++ b/distribution/tools/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/runtime/policy/parser/PolicyParser.java @@ -7,7 +7,7 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -package org.elasticsearch.entitlement.runtime.policy; +package org.elasticsearch.entitlement.runtime.policy.parser; import org.elasticsearch.xcontent.ParseField; import org.elasticsearch.xcontent.XContentParser; @@ -21,7 +21,7 @@ import java.util.List; import java.util.Objects; -import static org.elasticsearch.entitlement.runtime.policy.PolicyParserException.newPolicyParserException; +import static org.elasticsearch.entitlement.runtime.policy.parser.PolicyParserException.newPolicyParserException; public class PolicyParser { diff --git a/distribution/tools/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/runtime/policy/PolicyParserException.java b/distribution/tools/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/runtime/policy/parser/PolicyParserException.java similarity index 98% rename from distribution/tools/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/runtime/policy/PolicyParserException.java rename to distribution/tools/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/runtime/policy/parser/PolicyParserException.java index 2282f90a93d54..b1ee9813ed888 100644 --- a/distribution/tools/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/runtime/policy/PolicyParserException.java +++ b/distribution/tools/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/runtime/policy/parser/PolicyParserException.java @@ -7,7 +7,7 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -package org.elasticsearch.entitlement.runtime.policy; +package org.elasticsearch.entitlement.runtime.policy.parser; import org.elasticsearch.xcontent.XContentLocation; diff --git a/distribution/tools/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/runtime/policy/ScopeParser.java b/distribution/tools/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/runtime/policy/parser/ScopeParser.java similarity index 94% rename from distribution/tools/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/runtime/policy/ScopeParser.java rename to distribution/tools/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/runtime/policy/parser/ScopeParser.java index f84b4b5d5791f..d4556cf172780 100644 --- a/distribution/tools/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/runtime/policy/ScopeParser.java +++ b/distribution/tools/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/runtime/policy/parser/ScopeParser.java @@ -7,8 +7,9 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -package org.elasticsearch.entitlement.runtime.policy; +package org.elasticsearch.entitlement.runtime.policy.parser; +import org.elasticsearch.entitlement.runtime.policy.impl.Scope; import org.elasticsearch.xcontent.ParseField; import org.elasticsearch.xcontent.XContentParser; @@ -16,7 +17,7 @@ import java.util.ArrayList; import java.util.List; -import static org.elasticsearch.entitlement.runtime.policy.PolicyParserException.newPolicyParserException; +import static org.elasticsearch.entitlement.runtime.policy.parser.PolicyParserException.newPolicyParserException; public abstract class ScopeParser { @@ -26,14 +27,12 @@ public abstract class ScopeParser { protected final String policyName; protected final XContentParser policyParser; - protected final List fileEntitlementParsers = new ArrayList<>(); - protected ScopeParser(String policyName, XContentParser policyParser) { this.policyName = policyName; this.policyParser = policyParser; } - protected void parseScope() throws IOException { + protected Scope parseScope() throws IOException { while (policyParser.nextToken() != XContentParser.Token.END_OBJECT) { if (policyParser.currentToken() != XContentParser.Token.FIELD_NAME) { throw newPolicyParserException( diff --git a/distribution/tools/entitlement-runtime/src/test/java/org/elasticsearch/entitlement/runtime/policy/parser/PolicyParserFailureTests.java b/distribution/tools/entitlement-runtime/src/test/java/org/elasticsearch/entitlement/runtime/policy/parser/PolicyParserFailureTests.java new file mode 100644 index 0000000000000..938ac21300822 --- /dev/null +++ b/distribution/tools/entitlement-runtime/src/test/java/org/elasticsearch/entitlement/runtime/policy/parser/PolicyParserFailureTests.java @@ -0,0 +1,35 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +package org.elasticsearch.entitlement.runtime.policy.parser; + +import org.elasticsearch.common.Strings; +import org.elasticsearch.test.ESTestCase; +import org.elasticsearch.xcontent.XContentBuilder; +import org.elasticsearch.xcontent.yaml.YamlXContent; + +import java.io.ByteArrayInputStream; +import java.io.IOException; + +public class PolicyParserFailureTests extends ESTestCase { + + public void testParserSyntaxFailures() throws IOException { + + try (XContentBuilder builder = YamlXContent.contentBuilder()) { + builder.startArray(); + builder.endArray(); + PolicyParserException ppe = expectThrows(PolicyParserException.class, () -> + new PolicyParser("test-failure-policy.yaml", + new ByteArrayInputStream(Strings.toString(builder).getBytes()) + ).parsePolicy() + ); + assertEquals("[1:5] policy parsing error for [test-failure-policy.yaml]: expected object [policy]", ppe.getMessage()); + } + } +} diff --git a/distribution/tools/entitlement-runtime/src/test/java/org/elasticsearch/entitlement/runtime/policy/PolicyParserTests.java b/distribution/tools/entitlement-runtime/src/test/java/org/elasticsearch/entitlement/runtime/policy/parser/PolicyParserTests.java similarity index 92% rename from distribution/tools/entitlement-runtime/src/test/java/org/elasticsearch/entitlement/runtime/policy/PolicyParserTests.java rename to distribution/tools/entitlement-runtime/src/test/java/org/elasticsearch/entitlement/runtime/policy/parser/PolicyParserTests.java index 9f7194beb8c5b..1f8c516c1aba7 100644 --- a/distribution/tools/entitlement-runtime/src/test/java/org/elasticsearch/entitlement/runtime/policy/PolicyParserTests.java +++ b/distribution/tools/entitlement-runtime/src/test/java/org/elasticsearch/entitlement/runtime/policy/parser/PolicyParserTests.java @@ -7,7 +7,7 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -package org.elasticsearch.entitlement.runtime.policy; +package org.elasticsearch.entitlement.runtime.policy.parser; import org.elasticsearch.test.ESTestCase; diff --git a/distribution/tools/entitlement-runtime/src/test/resources/org/elasticsearch/entitlement/runtime/policy/test-policy.yaml b/distribution/tools/entitlement-runtime/src/test/resources/org/elasticsearch/entitlement/runtime/policy/parser/test-policy.yaml similarity index 100% rename from distribution/tools/entitlement-runtime/src/test/resources/org/elasticsearch/entitlement/runtime/policy/test-policy.yaml rename to distribution/tools/entitlement-runtime/src/test/resources/org/elasticsearch/entitlement/runtime/policy/parser/test-policy.yaml From 2c0d2c1df7ac6e3f151728cae905086850e8b349 Mon Sep 17 00:00:00 2001 From: Jack Conradson Date: Wed, 16 Oct 2024 16:14:31 -0700 Subject: [PATCH 8/9] update parser to remove boilerplate --- .../{impl/Scope.java => Entitlement.java} | 12 +- .../Policy.java => ExternalEntitlement.java} | 15 +- .../runtime/policy/FileEntitlement.java | 64 ++++++ .../entitlement/runtime/policy/Policy.java | 43 ++++ .../runtime/policy/PolicyParser.java | 215 ++++++++++++++++++ .../{parser => }/PolicyParserException.java | 2 +- .../entitlement/runtime/policy/Scope.java | 43 ++++ .../runtime/policy/impl/FileEntitlement.java | 24 -- .../policy/parser/EntitlementParser.java | 27 --- .../policy/parser/FileEntitlementParser.java | 140 ------------ .../policy/parser/ModuleScopeParser.java | 34 --- .../runtime/policy/parser/PolicyParser.java | 102 --------- .../runtime/policy/parser/ScopeParser.java | 130 ----------- .../policy/PolicyParserFailureTests.java | 149 ++++++++++++ .../{parser => }/PolicyParserTests.java | 11 +- .../parser/PolicyParserFailureTests.java | 35 --- .../runtime/policy/parser/test-policy.yaml | 9 - .../runtime/policy/test-policy.yaml | 7 + 18 files changed, 539 insertions(+), 523 deletions(-) rename distribution/tools/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/runtime/policy/{impl/Scope.java => Entitlement.java} (58%) rename distribution/tools/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/runtime/policy/{impl/Policy.java => ExternalEntitlement.java} (60%) create mode 100644 distribution/tools/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/runtime/policy/FileEntitlement.java create mode 100644 distribution/tools/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/runtime/policy/Policy.java create mode 100644 distribution/tools/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/runtime/policy/PolicyParser.java rename distribution/tools/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/runtime/policy/{parser => }/PolicyParserException.java (98%) create mode 100644 distribution/tools/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/runtime/policy/Scope.java delete mode 100644 distribution/tools/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/runtime/policy/impl/FileEntitlement.java delete mode 100644 distribution/tools/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/runtime/policy/parser/EntitlementParser.java delete mode 100644 distribution/tools/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/runtime/policy/parser/FileEntitlementParser.java delete mode 100644 distribution/tools/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/runtime/policy/parser/ModuleScopeParser.java delete mode 100644 distribution/tools/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/runtime/policy/parser/PolicyParser.java delete mode 100644 distribution/tools/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/runtime/policy/parser/ScopeParser.java create mode 100644 distribution/tools/entitlement-runtime/src/test/java/org/elasticsearch/entitlement/runtime/policy/PolicyParserFailureTests.java rename distribution/tools/entitlement-runtime/src/test/java/org/elasticsearch/entitlement/runtime/policy/{parser => }/PolicyParserTests.java (56%) delete mode 100644 distribution/tools/entitlement-runtime/src/test/java/org/elasticsearch/entitlement/runtime/policy/parser/PolicyParserFailureTests.java delete mode 100644 distribution/tools/entitlement-runtime/src/test/resources/org/elasticsearch/entitlement/runtime/policy/parser/test-policy.yaml create mode 100644 distribution/tools/entitlement-runtime/src/test/resources/org/elasticsearch/entitlement/runtime/policy/test-policy.yaml diff --git a/distribution/tools/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/runtime/policy/impl/Scope.java b/distribution/tools/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/runtime/policy/Entitlement.java similarity index 58% rename from distribution/tools/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/runtime/policy/impl/Scope.java rename to distribution/tools/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/runtime/policy/Entitlement.java index 5986b7adcf3da..12804ea7e960f 100644 --- a/distribution/tools/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/runtime/policy/impl/Scope.java +++ b/distribution/tools/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/runtime/policy/Entitlement.java @@ -7,16 +7,8 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -package org.elasticsearch.entitlement.runtime.policy.impl; +package org.elasticsearch.entitlement.runtime.policy; -import java.util.Collections; -import java.util.List; +public interface Entitlement { -public class Scope { - - public final List fileEntitlements; - - public Scope(List fileEntitlements) { - this.fileEntitlements = Collections.unmodifiableList(fileEntitlements); - } } diff --git a/distribution/tools/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/runtime/policy/impl/Policy.java b/distribution/tools/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/runtime/policy/ExternalEntitlement.java similarity index 60% rename from distribution/tools/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/runtime/policy/impl/Policy.java rename to distribution/tools/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/runtime/policy/ExternalEntitlement.java index 72c9c2b510996..c6066af961a53 100644 --- a/distribution/tools/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/runtime/policy/impl/Policy.java +++ b/distribution/tools/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/runtime/policy/ExternalEntitlement.java @@ -7,16 +7,13 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -package org.elasticsearch.entitlement.runtime.policy.impl; +package org.elasticsearch.entitlement.runtime.policy; -import java.util.Collections; -import java.util.Map; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; -public class Policy { +@Retention(RetentionPolicy.RUNTIME) +public @interface ExternalEntitlement { - public final Map moduleScopes; - - public Policy(Map moduleScopes) { - this.moduleScopes = Collections.unmodifiableMap(moduleScopes); - } + String[] parameterNames() default {}; } diff --git a/distribution/tools/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/runtime/policy/FileEntitlement.java b/distribution/tools/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/runtime/policy/FileEntitlement.java new file mode 100644 index 0000000000000..9b2587fcd62df --- /dev/null +++ b/distribution/tools/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/runtime/policy/FileEntitlement.java @@ -0,0 +1,64 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +package org.elasticsearch.entitlement.runtime.policy; + +import java.util.List; +import java.util.Objects; + +public class FileEntitlement implements Entitlement { + + public static final int READ_ACTION = 0x1; + public static final int WRITE_ACTION = 0x2; + + private final String path; + private final int actions; + + @ExternalEntitlement(parameterNames = { "path", "actions" }) + public FileEntitlement(String path, List actionsList) { + this.path = path; + int actionsInt = 0; + + for (String actionString : actionsList) { + if ("read".equals(actionString)) { + if ((actionsInt & READ_ACTION) == READ_ACTION) { + throw new IllegalArgumentException("file action [read] specified multiple times"); + } + actionsInt |= READ_ACTION; + } else if ("write".equals(actionString)) { + if ((actionsInt & WRITE_ACTION) == WRITE_ACTION) { + throw new IllegalArgumentException("file action [write] specified multiple times"); + } + actionsInt |= WRITE_ACTION; + } else { + throw new IllegalArgumentException("unknown file action [" + actionString + "]"); + } + } + + this.actions = actionsInt; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + FileEntitlement that = (FileEntitlement) o; + return actions == that.actions && Objects.equals(path, that.path); + } + + @Override + public int hashCode() { + return Objects.hash(path, actions); + } + + @Override + public String toString() { + return "FileEntitlement{" + "path='" + path + '\'' + ", actions=" + actions + '}'; + } +} diff --git a/distribution/tools/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/runtime/policy/Policy.java b/distribution/tools/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/runtime/policy/Policy.java new file mode 100644 index 0000000000000..b8494b14be156 --- /dev/null +++ b/distribution/tools/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/runtime/policy/Policy.java @@ -0,0 +1,43 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +package org.elasticsearch.entitlement.runtime.policy; + +import java.util.Collections; +import java.util.List; +import java.util.Objects; + +public class Policy { + + public final String name; + public final List scopes; + + public Policy(String name, List scopes) { + this.name = Objects.requireNonNull(name); + this.scopes = Collections.unmodifiableList(Objects.requireNonNull(scopes)); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Policy policy = (Policy) o; + return Objects.equals(name, policy.name) && Objects.equals(scopes, policy.scopes); + } + + @Override + public int hashCode() { + return Objects.hash(name, scopes); + } + + @Override + public String toString() { + return "Policy{" + "name='" + name + '\'' + ", scopes=" + scopes + '}'; + } +} diff --git a/distribution/tools/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/runtime/policy/PolicyParser.java b/distribution/tools/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/runtime/policy/PolicyParser.java new file mode 100644 index 0000000000000..b0f1f164ff64f --- /dev/null +++ b/distribution/tools/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/runtime/policy/PolicyParser.java @@ -0,0 +1,215 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +package org.elasticsearch.entitlement.runtime.policy; + +import org.elasticsearch.xcontent.ParseField; +import org.elasticsearch.xcontent.XContentParser; +import org.elasticsearch.xcontent.XContentParserConfiguration; +import org.elasticsearch.xcontent.yaml.YamlXContent; + +import java.io.IOException; +import java.io.InputStream; +import java.io.UncheckedIOException; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +import static org.elasticsearch.entitlement.runtime.policy.PolicyParserException.newPolicyParserException; + +public class PolicyParser { + + protected static final ParseField ENTITLEMENTS_PARSEFIELD = new ParseField("entitlements"); + + protected static final String entitlementPackageName = Entitlement.class.getPackage().getName(); + + protected final XContentParser policyParser; + protected final String policyName; + + public PolicyParser(InputStream inputStream, String policyName) throws IOException { + this.policyParser = YamlXContent.yamlXContent.createParser(XContentParserConfiguration.EMPTY, Objects.requireNonNull(inputStream)); + this.policyName = policyName; + } + + public Policy parsePolicy() { + try { + if (policyParser.nextToken() != XContentParser.Token.START_OBJECT) { + throw newPolicyParserException(policyParser.getTokenLocation(), policyName, "expected object "); + } + List scopes = new ArrayList<>(); + while (policyParser.nextToken() != XContentParser.Token.END_OBJECT) { + if (policyParser.currentToken() != XContentParser.Token.FIELD_NAME) { + throw newPolicyParserException(policyParser.getTokenLocation(), policyName, "expected object "); + } + String scopeName = policyParser.currentName(); + Scope scope = parseScope(scopeName); + scopes.add(scope); + } + return new Policy(policyName, scopes); + } catch (IOException ioe) { + throw new UncheckedIOException(ioe); + } + } + + protected Scope parseScope(String scopeName) throws IOException { + try { + if (policyParser.nextToken() != XContentParser.Token.START_OBJECT) { + throw newPolicyParserException( + policyParser.getTokenLocation(), + policyName, + scopeName, + "expected object [" + ENTITLEMENTS_PARSEFIELD.getPreferredName() + "]" + ); + } + if (policyParser.nextToken() != XContentParser.Token.FIELD_NAME + || policyParser.currentName().equals(ENTITLEMENTS_PARSEFIELD.getPreferredName()) == false) { + throw newPolicyParserException( + policyParser.getTokenLocation(), + policyName, + scopeName, + "expected object [" + ENTITLEMENTS_PARSEFIELD.getPreferredName() + "]" + ); + } + if (policyParser.nextToken() != XContentParser.Token.START_ARRAY) { + throw newPolicyParserException( + policyParser.getTokenLocation(), + policyName, + scopeName, + "expected array of " + ); + } + List entitlements = new ArrayList<>(); + while (policyParser.nextToken() != XContentParser.Token.END_ARRAY) { + if (policyParser.currentToken() != XContentParser.Token.START_OBJECT) { + throw newPolicyParserException( + policyParser.getTokenLocation(), + policyName, + scopeName, + "expected object " + ); + } + if (policyParser.nextToken() != XContentParser.Token.FIELD_NAME) { + throw newPolicyParserException( + policyParser.getTokenLocation(), + policyName, + scopeName, + "expected object " + ); + } + String entitlementType = policyParser.currentName(); + Entitlement entitlement = parseEntitlement(scopeName, entitlementType); + entitlements.add(entitlement); + if (policyParser.nextToken() != XContentParser.Token.END_OBJECT) { + throw newPolicyParserException(policyParser.getTokenLocation(), policyName, scopeName, "expected closing object"); + } + } + if (policyParser.nextToken() != XContentParser.Token.END_OBJECT) { + throw newPolicyParserException(policyParser.getTokenLocation(), policyName, scopeName, "expected closing object"); + } + return new Scope(scopeName, entitlements); + } catch (IOException ioe) { + throw new UncheckedIOException(ioe); + } + } + + protected Entitlement parseEntitlement(String scopeName, String entitlementType) throws IOException { + Class entitlementClass; + try { + entitlementClass = Class.forName( + entitlementPackageName + + "." + + Character.toUpperCase(entitlementType.charAt(0)) + + entitlementType.substring(1) + + "Entitlement" + ); + } catch (ClassNotFoundException cnfe) { + throw newPolicyParserException( + policyParser.getTokenLocation(), + policyName, + scopeName, + "unknown entitlement type [" + entitlementType + "]" + ); + } + if (Entitlement.class.isAssignableFrom(entitlementClass) == false) { + throw newPolicyParserException( + policyParser.getTokenLocation(), + policyName, + scopeName, + "unknown entitlement type [" + entitlementType + "]" + ); + } + Constructor entitlementConstructor = entitlementClass.getConstructors()[0]; + ExternalEntitlement entitlementMetadata = entitlementConstructor.getAnnotation(ExternalEntitlement.class); + if (entitlementMetadata == null) { + throw newPolicyParserException( + policyParser.getTokenLocation(), + policyName, + scopeName, + "unknown entitlement type [" + entitlementType + "]" + ); + } + + if (policyParser.nextToken() != XContentParser.Token.START_OBJECT) { + throw newPolicyParserException( + policyParser.getTokenLocation(), + policyName, + scopeName, + entitlementType, + "expected entitlement parameters" + ); + } + Map parsedValues = policyParser.map(); + + Class[] parameterTypes = entitlementConstructor.getParameterTypes(); + String[] parametersNames = entitlementMetadata.parameterNames(); + Object[] parameterValues = new Object[parameterTypes.length]; + for (int parameterIndex = 0; parameterIndex < parameterTypes.length; ++parameterIndex) { + String parameterName = parametersNames[parameterIndex]; + Object parameterValue = parsedValues.remove(parameterName); + if (parameterValue == null) { + throw newPolicyParserException( + policyParser.getTokenLocation(), + policyName, + scopeName, + entitlementType, + "missing entitlement parameter [" + parameterName + "]" + ); + } + Class parameterType = parameterTypes[parameterIndex]; + if (parameterType.isAssignableFrom(parameterValue.getClass()) == false) { + throw newPolicyParserException( + policyParser.getTokenLocation(), + policyName, + scopeName, + entitlementType, + "unexpected parameter type [" + parameterType.getSimpleName() + "] for entitlement parameter [" + parameterName + "]" + ); + } + parameterValues[parameterIndex] = parameterValue; + } + if (parsedValues.isEmpty() == false) { + throw newPolicyParserException( + policyParser.getTokenLocation(), + policyName, + scopeName, + entitlementType, + "extraneous entitlement parameter(s) " + parsedValues + ); + } + + try { + return (Entitlement) entitlementConstructor.newInstance(parameterValues); + } catch (InvocationTargetException | InstantiationException | IllegalAccessException e) { + throw new IllegalStateException("internal error"); + } + } +} diff --git a/distribution/tools/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/runtime/policy/parser/PolicyParserException.java b/distribution/tools/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/runtime/policy/PolicyParserException.java similarity index 98% rename from distribution/tools/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/runtime/policy/parser/PolicyParserException.java rename to distribution/tools/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/runtime/policy/PolicyParserException.java index b1ee9813ed888..2282f90a93d54 100644 --- a/distribution/tools/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/runtime/policy/parser/PolicyParserException.java +++ b/distribution/tools/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/runtime/policy/PolicyParserException.java @@ -7,7 +7,7 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -package org.elasticsearch.entitlement.runtime.policy.parser; +package org.elasticsearch.entitlement.runtime.policy; import org.elasticsearch.xcontent.XContentLocation; diff --git a/distribution/tools/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/runtime/policy/Scope.java b/distribution/tools/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/runtime/policy/Scope.java new file mode 100644 index 0000000000000..424aeb557e97b --- /dev/null +++ b/distribution/tools/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/runtime/policy/Scope.java @@ -0,0 +1,43 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +package org.elasticsearch.entitlement.runtime.policy; + +import java.util.Collections; +import java.util.List; +import java.util.Objects; + +public class Scope { + + public final String name; + public final List entitlements; + + public Scope(String name, List entitlements) { + this.name = Objects.requireNonNull(name); + this.entitlements = Collections.unmodifiableList(Objects.requireNonNull(entitlements)); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Scope scope = (Scope) o; + return Objects.equals(name, scope.name) && Objects.equals(entitlements, scope.entitlements); + } + + @Override + public int hashCode() { + return Objects.hash(name, entitlements); + } + + @Override + public String toString() { + return "Scope{" + "name='" + name + '\'' + ", entitlements=" + entitlements + '}'; + } +} diff --git a/distribution/tools/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/runtime/policy/impl/FileEntitlement.java b/distribution/tools/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/runtime/policy/impl/FileEntitlement.java deleted file mode 100644 index a33ef4dd6d2a4..0000000000000 --- a/distribution/tools/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/runtime/policy/impl/FileEntitlement.java +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the "Elastic License - * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side - * Public License v 1"; you may not use this file except in compliance with, at - * your election, the "Elastic License 2.0", the "GNU Affero General Public - * License v3.0 only", or the "Server Side Public License, v 1". - */ - -package org.elasticsearch.entitlement.runtime.policy.impl; - -public class FileEntitlement { - - public static final int READ_ACTION = 0x1; - public static final int WRITE_ACTION = 0x2; - - private final String path; - private final int actions; - - public FileEntitlement(String path, int actions) { - this.path = path; - this.actions = actions; - } -} diff --git a/distribution/tools/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/runtime/policy/parser/EntitlementParser.java b/distribution/tools/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/runtime/policy/parser/EntitlementParser.java deleted file mode 100644 index 0a6bbc3a3303e..0000000000000 --- a/distribution/tools/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/runtime/policy/parser/EntitlementParser.java +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the "Elastic License - * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side - * Public License v 1"; you may not use this file except in compliance with, at - * your election, the "Elastic License 2.0", the "GNU Affero General Public - * License v3.0 only", or the "Server Side Public License, v 1". - */ - -package org.elasticsearch.entitlement.runtime.policy.parser; - -import org.elasticsearch.xcontent.XContentParser; - -import java.io.IOException; - -public abstract class EntitlementParser { - - protected final String policyName; - protected final String scopeName; - protected final XContentParser policyParser; - - protected EntitlementParser(String policyName, String scopeName, XContentParser policyParser) { - this.policyName = policyName; - this.scopeName = scopeName; - this.policyParser = policyParser; - } -} diff --git a/distribution/tools/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/runtime/policy/parser/FileEntitlementParser.java b/distribution/tools/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/runtime/policy/parser/FileEntitlementParser.java deleted file mode 100644 index b15aa7acc9cea..0000000000000 --- a/distribution/tools/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/runtime/policy/parser/FileEntitlementParser.java +++ /dev/null @@ -1,140 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the "Elastic License - * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side - * Public License v 1"; you may not use this file except in compliance with, at - * your election, the "Elastic License 2.0", the "GNU Affero General Public - * License v3.0 only", or the "Server Side Public License, v 1". - */ - -package org.elasticsearch.entitlement.runtime.policy.parser; - -import org.elasticsearch.entitlement.runtime.policy.impl.FileEntitlement; -import org.elasticsearch.xcontent.ParseField; -import org.elasticsearch.xcontent.XContentParser; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; - -import static org.elasticsearch.entitlement.runtime.policy.parser.PolicyParserException.newPolicyParserException; - -public class FileEntitlementParser extends EntitlementParser { - - public static ParseField FILE_PARSEFIELD = new ParseField("file"); - public static ParseField PATH_PARSEFIELD = new ParseField("path"); - public static ParseField ACTIONS_PARSEFIELD = new ParseField("actions"); - - public static String READ_ACTION = "read"; - public static String WRITE_ACTION = "write"; - - protected FileEntitlementParser(String policyName, String scopeName, XContentParser policyParser) { - super(policyName, scopeName, policyParser); - } - - protected FileEntitlement parseEntitlement() throws IOException { - if (policyParser.nextToken() != XContentParser.Token.START_OBJECT) { - throw newPolicyParserException( - policyParser.getTokenLocation(), - policyName, - scopeName, - "expected object [" + FILE_PARSEFIELD.getPreferredName() + "]" - ); - } - String path = null; - int actions = 0; - while (policyParser.nextToken() != XContentParser.Token.END_OBJECT) { - if (policyParser.currentToken() != XContentParser.Token.FIELD_NAME) { - throw newPolicyParserException( - policyParser.getTokenLocation(), - policyName, - scopeName, - "expected object [" + FILE_PARSEFIELD.getPreferredName() + "]" - ); - } - String currentFieldName = policyParser.currentName(); - if (currentFieldName.equals(PATH_PARSEFIELD.getPreferredName())) { - if (policyParser.nextToken() != XContentParser.Token.VALUE_STRING) { - throw newPolicyParserException( - policyParser.getTokenLocation(), - policyName, - scopeName, - FILE_PARSEFIELD.getPreferredName(), - "expected value for field [" + PATH_PARSEFIELD.getPreferredName() + "]" - ); - } - if (path == null) { - path = policyParser.text(); - } else { - throw newPolicyParserException( - policyParser.getTokenLocation(), - policyName, - "field [" - + PATH_PARSEFIELD.getPreferredName() - + "] has already been set; found multiple values" - + "['" - + path - + "', '" - + policyParser.text() - + "']" - ); - } - } else if (currentFieldName.equals(ACTIONS_PARSEFIELD.getPreferredName())) { - if (policyParser.nextToken() != XContentParser.Token.START_ARRAY) { - throw newPolicyParserException( - policyParser.getTokenLocation(), - policyName, - scopeName, - FILE_PARSEFIELD.getPreferredName(), - "expected array for field [" + ACTIONS_PARSEFIELD.getPreferredName() + "]" - ); - } - while (policyParser.nextToken() != XContentParser.Token.END_ARRAY) { - if (policyParser.currentToken() != XContentParser.Token.VALUE_STRING) { - throw newPolicyParserException( - policyParser.getTokenLocation(), - policyName, - scopeName, - FILE_PARSEFIELD.getPreferredName(), - "expected value(s) for array [" + ACTIONS_PARSEFIELD.getPreferredName() + "]" - ); - } - String action = policyParser.text(); - if (READ_ACTION.equals(action)) { - if ((actions & FileEntitlement.READ_ACTION) == FileEntitlement.READ_ACTION) { - throw newPolicyParserException( - policyParser.getTokenLocation(), - policyName, - "field [" + ACTIONS_PARSEFIELD.getPreferredName() + "] already has value [" + action + "]" - ); - } - actions |= FileEntitlement.READ_ACTION; - } else if (WRITE_ACTION.equals(action)) { - if ((actions & FileEntitlement.WRITE_ACTION) == FileEntitlement.WRITE_ACTION) { - throw newPolicyParserException( - policyParser.getTokenLocation(), - policyName, - "field [" + ACTIONS_PARSEFIELD.getPreferredName() + "] already has value [" + action + "]" - ); - } - actions |= FileEntitlement.WRITE_ACTION; - } else { - throw newPolicyParserException( - policyParser.getTokenLocation(), - policyName, - "field [" + ACTIONS_PARSEFIELD.getPreferredName() + "] has unknown value [" + action + "]" - ); - } - } - } else { - throw newPolicyParserException( - policyParser.getTokenLocation(), - policyName, - scopeName, - "unexpected field [" + currentFieldName + "]" - ); - } - } - return new FileEntitlement(path, actions); - } -} diff --git a/distribution/tools/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/runtime/policy/parser/ModuleScopeParser.java b/distribution/tools/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/runtime/policy/parser/ModuleScopeParser.java deleted file mode 100644 index e9f31ade69c91..0000000000000 --- a/distribution/tools/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/runtime/policy/parser/ModuleScopeParser.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the "Elastic License - * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side - * Public License v 1"; you may not use this file except in compliance with, at - * your election, the "Elastic License 2.0", the "GNU Affero General Public - * License v3.0 only", or the "Server Side Public License, v 1". - */ - -package org.elasticsearch.entitlement.runtime.policy.parser; - -import org.elasticsearch.xcontent.ParseField; -import org.elasticsearch.xcontent.XContentParser; - -public class ModuleScopeParser extends ScopeParser { - - public static ParseField MODULE_PARSEFIELD = new ParseField("module"); - - protected String moduleScopeName; - - public ModuleScopeParser(String policyName, XContentParser policyParser) { - super(policyName, policyParser); - } - - @Override - protected void setScopeName(String scopeName) { - this.moduleScopeName = scopeName; - } - - @Override - protected String getScopeName() { - return moduleScopeName; - } -} diff --git a/distribution/tools/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/runtime/policy/parser/PolicyParser.java b/distribution/tools/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/runtime/policy/parser/PolicyParser.java deleted file mode 100644 index cd5a42d770b9e..0000000000000 --- a/distribution/tools/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/runtime/policy/parser/PolicyParser.java +++ /dev/null @@ -1,102 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the "Elastic License - * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side - * Public License v 1"; you may not use this file except in compliance with, at - * your election, the "Elastic License 2.0", the "GNU Affero General Public - * License v3.0 only", or the "Server Side Public License, v 1". - */ - -package org.elasticsearch.entitlement.runtime.policy.parser; - -import org.elasticsearch.xcontent.ParseField; -import org.elasticsearch.xcontent.XContentParser; -import org.elasticsearch.xcontent.XContentParserConfiguration; -import org.elasticsearch.xcontent.yaml.YamlXContent; - -import java.io.IOException; -import java.io.InputStream; -import java.io.UncheckedIOException; -import java.util.ArrayList; -import java.util.List; -import java.util.Objects; - -import static org.elasticsearch.entitlement.runtime.policy.parser.PolicyParserException.newPolicyParserException; - -public class PolicyParser { - - public static ParseField POLICY_PARSEFIELD = new ParseField("policy"); - - public final String policyName; - public final XContentParser policyParser; - - public final List policyModuleParsers = new ArrayList<>(); - - public PolicyParser(String policyName, InputStream inputStream) throws IOException { - this.policyName = policyName; - this.policyParser = YamlXContent.yamlXContent.createParser(XContentParserConfiguration.EMPTY, Objects.requireNonNull(inputStream)); - } - - public void parsePolicy() { - try { - if (policyParser.nextToken() != XContentParser.Token.START_OBJECT) { - throw newPolicyParserException( - policyParser.getTokenLocation(), - policyName, - "expected object [" + POLICY_PARSEFIELD.getPreferredName() + "]" - ); - } - if (policyParser.nextToken() != XContentParser.Token.FIELD_NAME - || policyParser.currentName().equals(POLICY_PARSEFIELD.getPreferredName()) == false) { - throw newPolicyParserException( - policyParser.getTokenLocation(), - policyName, - "expected object [" + POLICY_PARSEFIELD.getPreferredName() + "]" - ); - } - if (policyParser.nextToken() != XContentParser.Token.START_ARRAY) { - throw newPolicyParserException( - policyParser.getTokenLocation(), - policyName, - "expected array of [" + ModuleScopeParser.MODULE_PARSEFIELD.getPreferredName() + "]" - ); - } - while (policyParser.nextToken() != XContentParser.Token.END_ARRAY) { - if (policyParser.currentToken() != XContentParser.Token.START_OBJECT) { - throw newPolicyParserException( - policyParser.getTokenLocation(), - policyName, - "expected object [" + ModuleScopeParser.MODULE_PARSEFIELD.getPreferredName() + "]" - ); - } - if (policyParser.nextToken() != XContentParser.Token.FIELD_NAME - || policyParser.currentName().equals(ModuleScopeParser.MODULE_PARSEFIELD.getPreferredName()) == false) { - throw newPolicyParserException( - policyParser.getTokenLocation(), - policyName, - "expected object [" + ModuleScopeParser.MODULE_PARSEFIELD.getPreferredName() + "]" - ); - } - if (policyParser.nextToken() != XContentParser.Token.START_OBJECT) { - throw newPolicyParserException( - policyParser.getTokenLocation(), - policyName, - "expected object [" + ModuleScopeParser.MODULE_PARSEFIELD.getPreferredName() + "]" - ); - } - ModuleScopeParser moduleScopeParser = new ModuleScopeParser(policyName, policyParser); - moduleScopeParser.parseScope(); - policyModuleParsers.add(moduleScopeParser); - if (policyParser.nextToken() != XContentParser.Token.END_OBJECT) { - throw newPolicyParserException(policyParser.getTokenLocation(), policyName, "expected closing object"); - } - } - if (policyParser.nextToken() != XContentParser.Token.END_OBJECT) { - throw newPolicyParserException(policyParser.getTokenLocation(), policyName, "expected closing object"); - } - } catch (IOException ioe) { - throw new UncheckedIOException(ioe); - } - } - -} diff --git a/distribution/tools/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/runtime/policy/parser/ScopeParser.java b/distribution/tools/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/runtime/policy/parser/ScopeParser.java deleted file mode 100644 index d4556cf172780..0000000000000 --- a/distribution/tools/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/runtime/policy/parser/ScopeParser.java +++ /dev/null @@ -1,130 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the "Elastic License - * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side - * Public License v 1"; you may not use this file except in compliance with, at - * your election, the "Elastic License 2.0", the "GNU Affero General Public - * License v3.0 only", or the "Server Side Public License, v 1". - */ - -package org.elasticsearch.entitlement.runtime.policy.parser; - -import org.elasticsearch.entitlement.runtime.policy.impl.Scope; -import org.elasticsearch.xcontent.ParseField; -import org.elasticsearch.xcontent.XContentParser; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; - -import static org.elasticsearch.entitlement.runtime.policy.parser.PolicyParserException.newPolicyParserException; - -public abstract class ScopeParser { - - public static ParseField NAME_PARSEFIELD = new ParseField("name"); - public static ParseField ENTITLEMENTS_PARSEFIELD = new ParseField("entitlements"); - - protected final String policyName; - protected final XContentParser policyParser; - - protected ScopeParser(String policyName, XContentParser policyParser) { - this.policyName = policyName; - this.policyParser = policyParser; - } - - protected Scope parseScope() throws IOException { - while (policyParser.nextToken() != XContentParser.Token.END_OBJECT) { - if (policyParser.currentToken() != XContentParser.Token.FIELD_NAME) { - throw newPolicyParserException( - policyParser.getTokenLocation(), - policyName, - "expected object [" + ModuleScopeParser.MODULE_PARSEFIELD.getPreferredName() + "]" - ); - } - String currentFieldName = policyParser.currentName(); - if (currentFieldName.equals(NAME_PARSEFIELD.getPreferredName())) { - if (policyParser.nextToken() != XContentParser.Token.VALUE_STRING) { - throw newPolicyParserException( - policyParser.getTokenLocation(), - policyName, - "expected value for field [" + NAME_PARSEFIELD.getPreferredName() + "]" - ); - } - if (getScopeName() == null) { - setScopeName(policyParser.text()); - } else { - throw newPolicyParserException( - policyParser.getTokenLocation(), - policyName, - "field [" - + NAME_PARSEFIELD.getPreferredName() - + "] has already been set; found multiple values" - + "['" - + getScopeName() - + "', '" - + policyParser.text() - + "']" - ); - } - } else if (currentFieldName.equals(ENTITLEMENTS_PARSEFIELD.getPreferredName())) { - if (policyParser.nextToken() != XContentParser.Token.START_ARRAY) { - throw newPolicyParserException( - policyParser.getTokenLocation(), - policyName, - getScopeName(), - "expected array of " - ); - } - parseEntitlements(); - } else { - throw newPolicyParserException( - policyParser.getTokenLocation(), - policyName, - getScopeName(), - "unexpected field [" + currentFieldName + "]" - ); - } - } - } - - protected void parseEntitlements() throws IOException { - while (policyParser.nextToken() != XContentParser.Token.END_ARRAY) { - if (policyParser.currentToken() != XContentParser.Token.START_OBJECT) { - throw newPolicyParserException( - policyParser.getTokenLocation(), - policyName, - getScopeName(), - "expected object " - ); - } - if (policyParser.nextToken() != XContentParser.Token.FIELD_NAME) { - throw newPolicyParserException( - policyParser.getTokenLocation(), - policyName, - getScopeName(), - "expected object " - ); - } - String currentFieldName = policyParser.currentName(); - if (currentFieldName.equals(FileEntitlementParser.FILE_PARSEFIELD.getPreferredName())) { - FileEntitlementParser fileEntitlementParser = new FileEntitlementParser(policyName, getScopeName(), policyParser); - fileEntitlementParser.parseEntitlement(); - fileEntitlementParsers.add(fileEntitlementParser); - } else { - throw newPolicyParserException( - policyParser.getTokenLocation(), - policyName, - getScopeName(), - "unexpected field [" + currentFieldName + "]" - ); - } - if (policyParser.nextToken() != XContentParser.Token.END_OBJECT) { - throw newPolicyParserException(policyParser.getTokenLocation(), policyName, getScopeName(), "expected closing object"); - } - } - } - - protected abstract void setScopeName(String scopeName); - - protected abstract String getScopeName(); -} diff --git a/distribution/tools/entitlement-runtime/src/test/java/org/elasticsearch/entitlement/runtime/policy/PolicyParserFailureTests.java b/distribution/tools/entitlement-runtime/src/test/java/org/elasticsearch/entitlement/runtime/policy/PolicyParserFailureTests.java new file mode 100644 index 0000000000000..e1002415edc72 --- /dev/null +++ b/distribution/tools/entitlement-runtime/src/test/java/org/elasticsearch/entitlement/runtime/policy/PolicyParserFailureTests.java @@ -0,0 +1,149 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +package org.elasticsearch.entitlement.runtime.policy; + +import org.elasticsearch.common.Strings; +import org.elasticsearch.test.ESTestCase; +import org.elasticsearch.xcontent.XContentBuilder; +import org.elasticsearch.xcontent.yaml.YamlXContent; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.nio.charset.StandardCharsets; + +public class PolicyParserFailureTests extends ESTestCase { + + public void testParserSyntaxFailures() throws IOException { + try (XContentBuilder builder = YamlXContent.contentBuilder()) { + builder.startArray(); + builder.endArray(); + PolicyParserException ppe = expectThrows( + PolicyParserException.class, + () -> new PolicyParser( + new ByteArrayInputStream(Strings.toString(builder).getBytes(StandardCharsets.UTF_8)), + "test-failure-policy.yaml" + ).parsePolicy() + ); + assertEquals("[1:5] policy parsing error for [test-failure-policy.yaml]: expected object ", ppe.getMessage()); + } + } + + public void testEntitlementDoesNotExist() throws IOException { + try (XContentBuilder builder = YamlXContent.contentBuilder()) { + builder.startObject(); + builder.startObject("entitlement-module-1"); + builder.startArray("entitlements"); + builder.startObject(); + builder.startObject("does_not_exist"); + builder.endObject(); + builder.endObject(); + builder.endArray(); + builder.endObject(); + builder.endObject(); + PolicyParserException ppe = expectThrows( + PolicyParserException.class, + () -> new PolicyParser( + new ByteArrayInputStream(Strings.toString(builder).getBytes(StandardCharsets.UTF_8)), + "test-failure-policy.yaml" + ).parsePolicy() + ); + assertEquals( + "[4:5] policy parsing error for [test-failure-policy.yaml] in scope [entitlement-module-1]: " + + "unknown entitlement type [does_not_exist]", + ppe.getMessage() + ); + } + } + + public void testEntitlementMissingParameter() throws IOException { + try (XContentBuilder builder = YamlXContent.contentBuilder()) { + builder.startObject(); + builder.startObject("entitlement-module-1"); + builder.startArray("entitlements"); + builder.startObject(); + builder.startObject("file"); + builder.endObject(); + builder.endObject(); + builder.endArray(); + builder.endObject(); + builder.endObject(); + PolicyParserException ppe = expectThrows( + PolicyParserException.class, + () -> new PolicyParser( + new ByteArrayInputStream(Strings.toString(builder).getBytes(StandardCharsets.UTF_8)), + "test-failure-policy.yaml" + ).parsePolicy() + ); + assertEquals( + "[4:12] policy parsing error for [test-failure-policy.yaml] in scope [entitlement-module-1] " + + "for entitlement type [file]: missing entitlement parameter [path]", + ppe.getMessage() + ); + } + + try (XContentBuilder builder = YamlXContent.contentBuilder()) { + builder.startObject(); + builder.startObject("entitlement-module-1"); + builder.startArray("entitlements"); + builder.startObject(); + builder.startObject("file"); + builder.field("path", "test-path"); + builder.endObject(); + builder.endObject(); + builder.endArray(); + builder.endObject(); + builder.endObject(); + PolicyParserException ppe = expectThrows( + PolicyParserException.class, + () -> new PolicyParser( + new ByteArrayInputStream(Strings.toString(builder).getBytes(StandardCharsets.UTF_8)), + "test-failure-policy.yaml" + ).parsePolicy() + ); + assertEquals( + "[6:1] policy parsing error for [test-failure-policy.yaml] in scope [entitlement-module-1] " + + "for entitlement type [file]: missing entitlement parameter [actions]", + ppe.getMessage() + ); + } + } + + public void testEntitlementExtraneousParameter() throws IOException { + try (XContentBuilder builder = YamlXContent.contentBuilder()) { + builder.startObject(); + builder.startObject("entitlement-module-1"); + builder.startArray("entitlements"); + builder.startObject(); + builder.startObject("file"); + builder.field("path", "test-path"); + builder.startArray("actions"); + builder.value("read"); + builder.endArray(); + builder.field("extra", "test"); + builder.endObject(); + builder.endObject(); + builder.endArray(); + builder.endObject(); + builder.endObject(); + PolicyParserException ppe = expectThrows( + PolicyParserException.class, + () -> new PolicyParser( + new ByteArrayInputStream(Strings.toString(builder).getBytes(StandardCharsets.UTF_8)), + "test-failure-policy.yaml" + ).parsePolicy() + ); + assertEquals( + "[9:1] policy parsing error for [test-failure-policy.yaml] in scope [entitlement-module-1] " + + "for entitlement type [file]: extraneous entitlement parameter(s) {extra=test}", + ppe.getMessage() + ); + } + } +} diff --git a/distribution/tools/entitlement-runtime/src/test/java/org/elasticsearch/entitlement/runtime/policy/parser/PolicyParserTests.java b/distribution/tools/entitlement-runtime/src/test/java/org/elasticsearch/entitlement/runtime/policy/PolicyParserTests.java similarity index 56% rename from distribution/tools/entitlement-runtime/src/test/java/org/elasticsearch/entitlement/runtime/policy/parser/PolicyParserTests.java rename to distribution/tools/entitlement-runtime/src/test/java/org/elasticsearch/entitlement/runtime/policy/PolicyParserTests.java index 1f8c516c1aba7..90dd2f625dfcb 100644 --- a/distribution/tools/entitlement-runtime/src/test/java/org/elasticsearch/entitlement/runtime/policy/parser/PolicyParserTests.java +++ b/distribution/tools/entitlement-runtime/src/test/java/org/elasticsearch/entitlement/runtime/policy/PolicyParserTests.java @@ -7,15 +7,22 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -package org.elasticsearch.entitlement.runtime.policy.parser; +package org.elasticsearch.entitlement.runtime.policy; import org.elasticsearch.test.ESTestCase; import java.io.IOException; +import java.util.List; public class PolicyParserTests extends ESTestCase { public void testPolicyBuilder() throws IOException { - new PolicyParser("test-policy.yaml", PolicyParserTests.class.getResourceAsStream("test-policy.yaml")).parsePolicy(); + Policy parsedPolicy = new PolicyParser(PolicyParserTests.class.getResourceAsStream("test-policy.yaml"), "test-policy.yaml") + .parsePolicy(); + Policy builtPolicy = new Policy( + "test-policy.yaml", + List.of(new Scope("entitlement-module-1", List.of(new FileEntitlement("test/path/to/file", List.of("read", "write"))))) + ); + assertEquals(parsedPolicy, builtPolicy); } } diff --git a/distribution/tools/entitlement-runtime/src/test/java/org/elasticsearch/entitlement/runtime/policy/parser/PolicyParserFailureTests.java b/distribution/tools/entitlement-runtime/src/test/java/org/elasticsearch/entitlement/runtime/policy/parser/PolicyParserFailureTests.java deleted file mode 100644 index 938ac21300822..0000000000000 --- a/distribution/tools/entitlement-runtime/src/test/java/org/elasticsearch/entitlement/runtime/policy/parser/PolicyParserFailureTests.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the "Elastic License - * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side - * Public License v 1"; you may not use this file except in compliance with, at - * your election, the "Elastic License 2.0", the "GNU Affero General Public - * License v3.0 only", or the "Server Side Public License, v 1". - */ - -package org.elasticsearch.entitlement.runtime.policy.parser; - -import org.elasticsearch.common.Strings; -import org.elasticsearch.test.ESTestCase; -import org.elasticsearch.xcontent.XContentBuilder; -import org.elasticsearch.xcontent.yaml.YamlXContent; - -import java.io.ByteArrayInputStream; -import java.io.IOException; - -public class PolicyParserFailureTests extends ESTestCase { - - public void testParserSyntaxFailures() throws IOException { - - try (XContentBuilder builder = YamlXContent.contentBuilder()) { - builder.startArray(); - builder.endArray(); - PolicyParserException ppe = expectThrows(PolicyParserException.class, () -> - new PolicyParser("test-failure-policy.yaml", - new ByteArrayInputStream(Strings.toString(builder).getBytes()) - ).parsePolicy() - ); - assertEquals("[1:5] policy parsing error for [test-failure-policy.yaml]: expected object [policy]", ppe.getMessage()); - } - } -} diff --git a/distribution/tools/entitlement-runtime/src/test/resources/org/elasticsearch/entitlement/runtime/policy/parser/test-policy.yaml b/distribution/tools/entitlement-runtime/src/test/resources/org/elasticsearch/entitlement/runtime/policy/parser/test-policy.yaml deleted file mode 100644 index b4c0e671c3a54..0000000000000 --- a/distribution/tools/entitlement-runtime/src/test/resources/org/elasticsearch/entitlement/runtime/policy/parser/test-policy.yaml +++ /dev/null @@ -1,9 +0,0 @@ -policy: - - module: - name: entitlement-test - entitlements: - - file: - path: "test/path/to/file" - actions: - - "read" - - "write" diff --git a/distribution/tools/entitlement-runtime/src/test/resources/org/elasticsearch/entitlement/runtime/policy/test-policy.yaml b/distribution/tools/entitlement-runtime/src/test/resources/org/elasticsearch/entitlement/runtime/policy/test-policy.yaml new file mode 100644 index 0000000000000..93b24c3e60e70 --- /dev/null +++ b/distribution/tools/entitlement-runtime/src/test/resources/org/elasticsearch/entitlement/runtime/policy/test-policy.yaml @@ -0,0 +1,7 @@ +entitlement-module-1: + entitlements: + - file: + path: "test/path/to/file" + actions: + - "read" + - "write" From 4ed58a116381de672fe66853913d34c6c1e42cdd Mon Sep 17 00:00:00 2001 From: Jack Conradson Date: Fri, 18 Oct 2024 14:52:48 -0700 Subject: [PATCH 9/9] changes based on pr feedback --- .../runtime/policy/Entitlement.java | 5 + .../runtime/policy/ExternalEntitlement.java | 17 ++ .../runtime/policy/FileEntitlement.java | 3 + .../entitlement/runtime/policy/Policy.java | 3 + .../runtime/policy/PolicyParser.java | 111 ++++-------- .../runtime/policy/PolicyParserException.java | 3 + .../entitlement/runtime/policy/Scope.java | 3 + .../policy/PolicyParserFailureTests.java | 170 ++++++------------ .../runtime/policy/PolicyParserTests.java | 2 +- .../runtime/policy/test-policy.yaml | 2 +- 10 files changed, 124 insertions(+), 195 deletions(-) diff --git a/distribution/tools/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/runtime/policy/Entitlement.java b/distribution/tools/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/runtime/policy/Entitlement.java index 12804ea7e960f..5b53c399cc1b7 100644 --- a/distribution/tools/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/runtime/policy/Entitlement.java +++ b/distribution/tools/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/runtime/policy/Entitlement.java @@ -9,6 +9,11 @@ package org.elasticsearch.entitlement.runtime.policy; +/** + * Marker interface to ensure that only {@link Entitlement} are + * part of a {@link Policy}. All entitlement classes should implement + * this. + */ public interface Entitlement { } diff --git a/distribution/tools/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/runtime/policy/ExternalEntitlement.java b/distribution/tools/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/runtime/policy/ExternalEntitlement.java index c6066af961a53..bb1205696b49e 100644 --- a/distribution/tools/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/runtime/policy/ExternalEntitlement.java +++ b/distribution/tools/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/runtime/policy/ExternalEntitlement.java @@ -9,11 +9,28 @@ package org.elasticsearch.entitlement.runtime.policy; +import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +/** + * This annotation indicates an {@link Entitlement} is available + * to "external" classes such as those used in plugins. Any {@link Entitlement} + * using this annotation is considered parseable as part of a policy file + * for entitlements. + */ +@Target(ElementType.CONSTRUCTOR) @Retention(RetentionPolicy.RUNTIME) public @interface ExternalEntitlement { + /** + * This is the list of parameter names that are + * parseable in {@link PolicyParser#parseEntitlement(String, String)}. + * The number and order of parameter names much match the number and order + * of constructor parameters as this is how the parser will pass in the + * parsed values from a policy file. However, the names themselves do NOT + * have to match the parameter names of the constructor. + */ String[] parameterNames() default {}; } diff --git a/distribution/tools/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/runtime/policy/FileEntitlement.java b/distribution/tools/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/runtime/policy/FileEntitlement.java index 9b2587fcd62df..8df199591d3e4 100644 --- a/distribution/tools/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/runtime/policy/FileEntitlement.java +++ b/distribution/tools/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/runtime/policy/FileEntitlement.java @@ -12,6 +12,9 @@ import java.util.List; import java.util.Objects; +/** + * Describes a file entitlement with a path and actions. + */ public class FileEntitlement implements Entitlement { public static final int READ_ACTION = 0x1; diff --git a/distribution/tools/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/runtime/policy/Policy.java b/distribution/tools/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/runtime/policy/Policy.java index b8494b14be156..e8bd7a3fff357 100644 --- a/distribution/tools/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/runtime/policy/Policy.java +++ b/distribution/tools/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/runtime/policy/Policy.java @@ -13,6 +13,9 @@ import java.util.List; import java.util.Objects; +/** + * A holder for scoped entitlements. + */ public class Policy { public final String name; diff --git a/distribution/tools/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/runtime/policy/PolicyParser.java b/distribution/tools/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/runtime/policy/PolicyParser.java index b0f1f164ff64f..229ccec3b8b2c 100644 --- a/distribution/tools/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/runtime/policy/PolicyParser.java +++ b/distribution/tools/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/runtime/policy/PolicyParser.java @@ -26,6 +26,9 @@ import static org.elasticsearch.entitlement.runtime.policy.PolicyParserException.newPolicyParserException; +/** + * A parser to parse policy files for entitlements. + */ public class PolicyParser { protected static final ParseField ENTITLEMENTS_PARSEFIELD = new ParseField("entitlements"); @@ -43,12 +46,12 @@ public PolicyParser(InputStream inputStream, String policyName) throws IOExcepti public Policy parsePolicy() { try { if (policyParser.nextToken() != XContentParser.Token.START_OBJECT) { - throw newPolicyParserException(policyParser.getTokenLocation(), policyName, "expected object "); + throw newPolicyParserException("expected object "); } List scopes = new ArrayList<>(); while (policyParser.nextToken() != XContentParser.Token.END_OBJECT) { if (policyParser.currentToken() != XContentParser.Token.FIELD_NAME) { - throw newPolicyParserException(policyParser.getTokenLocation(), policyName, "expected object "); + throw newPolicyParserException("expected object "); } String scopeName = policyParser.currentName(); Scope scope = parseScope(scopeName); @@ -63,57 +66,32 @@ public Policy parsePolicy() { protected Scope parseScope(String scopeName) throws IOException { try { if (policyParser.nextToken() != XContentParser.Token.START_OBJECT) { - throw newPolicyParserException( - policyParser.getTokenLocation(), - policyName, - scopeName, - "expected object [" + ENTITLEMENTS_PARSEFIELD.getPreferredName() + "]" - ); + throw newPolicyParserException(scopeName, "expected object [" + ENTITLEMENTS_PARSEFIELD.getPreferredName() + "]"); } if (policyParser.nextToken() != XContentParser.Token.FIELD_NAME || policyParser.currentName().equals(ENTITLEMENTS_PARSEFIELD.getPreferredName()) == false) { - throw newPolicyParserException( - policyParser.getTokenLocation(), - policyName, - scopeName, - "expected object [" + ENTITLEMENTS_PARSEFIELD.getPreferredName() + "]" - ); + throw newPolicyParserException(scopeName, "expected object [" + ENTITLEMENTS_PARSEFIELD.getPreferredName() + "]"); } if (policyParser.nextToken() != XContentParser.Token.START_ARRAY) { - throw newPolicyParserException( - policyParser.getTokenLocation(), - policyName, - scopeName, - "expected array of " - ); + throw newPolicyParserException(scopeName, "expected array of "); } List entitlements = new ArrayList<>(); while (policyParser.nextToken() != XContentParser.Token.END_ARRAY) { if (policyParser.currentToken() != XContentParser.Token.START_OBJECT) { - throw newPolicyParserException( - policyParser.getTokenLocation(), - policyName, - scopeName, - "expected object " - ); + throw newPolicyParserException(scopeName, "expected object "); } if (policyParser.nextToken() != XContentParser.Token.FIELD_NAME) { - throw newPolicyParserException( - policyParser.getTokenLocation(), - policyName, - scopeName, - "expected object " - ); + throw newPolicyParserException(scopeName, "expected object "); } String entitlementType = policyParser.currentName(); Entitlement entitlement = parseEntitlement(scopeName, entitlementType); entitlements.add(entitlement); if (policyParser.nextToken() != XContentParser.Token.END_OBJECT) { - throw newPolicyParserException(policyParser.getTokenLocation(), policyName, scopeName, "expected closing object"); + throw newPolicyParserException(scopeName, "expected closing object"); } } if (policyParser.nextToken() != XContentParser.Token.END_OBJECT) { - throw newPolicyParserException(policyParser.getTokenLocation(), policyName, scopeName, "expected closing object"); + throw newPolicyParserException(scopeName, "expected closing object"); } return new Scope(scopeName, entitlements); } catch (IOException ioe) { @@ -132,40 +110,19 @@ protected Entitlement parseEntitlement(String scopeName, String entitlementType) + "Entitlement" ); } catch (ClassNotFoundException cnfe) { - throw newPolicyParserException( - policyParser.getTokenLocation(), - policyName, - scopeName, - "unknown entitlement type [" + entitlementType + "]" - ); + throw newPolicyParserException(scopeName, "unknown entitlement type [" + entitlementType + "]"); } if (Entitlement.class.isAssignableFrom(entitlementClass) == false) { - throw newPolicyParserException( - policyParser.getTokenLocation(), - policyName, - scopeName, - "unknown entitlement type [" + entitlementType + "]" - ); + throw newPolicyParserException(scopeName, "unknown entitlement type [" + entitlementType + "]"); } Constructor entitlementConstructor = entitlementClass.getConstructors()[0]; ExternalEntitlement entitlementMetadata = entitlementConstructor.getAnnotation(ExternalEntitlement.class); if (entitlementMetadata == null) { - throw newPolicyParserException( - policyParser.getTokenLocation(), - policyName, - scopeName, - "unknown entitlement type [" + entitlementType + "]" - ); + throw newPolicyParserException(scopeName, "unknown entitlement type [" + entitlementType + "]"); } if (policyParser.nextToken() != XContentParser.Token.START_OBJECT) { - throw newPolicyParserException( - policyParser.getTokenLocation(), - policyName, - scopeName, - entitlementType, - "expected entitlement parameters" - ); + throw newPolicyParserException(scopeName, entitlementType, "expected entitlement parameters"); } Map parsedValues = policyParser.map(); @@ -176,19 +133,11 @@ protected Entitlement parseEntitlement(String scopeName, String entitlementType) String parameterName = parametersNames[parameterIndex]; Object parameterValue = parsedValues.remove(parameterName); if (parameterValue == null) { - throw newPolicyParserException( - policyParser.getTokenLocation(), - policyName, - scopeName, - entitlementType, - "missing entitlement parameter [" + parameterName + "]" - ); + throw newPolicyParserException(scopeName, entitlementType, "missing entitlement parameter [" + parameterName + "]"); } Class parameterType = parameterTypes[parameterIndex]; if (parameterType.isAssignableFrom(parameterValue.getClass()) == false) { throw newPolicyParserException( - policyParser.getTokenLocation(), - policyName, scopeName, entitlementType, "unexpected parameter type [" + parameterType.getSimpleName() + "] for entitlement parameter [" + parameterName + "]" @@ -197,13 +146,7 @@ protected Entitlement parseEntitlement(String scopeName, String entitlementType) parameterValues[parameterIndex] = parameterValue; } if (parsedValues.isEmpty() == false) { - throw newPolicyParserException( - policyParser.getTokenLocation(), - policyName, - scopeName, - entitlementType, - "extraneous entitlement parameter(s) " + parsedValues - ); + throw newPolicyParserException(scopeName, entitlementType, "extraneous entitlement parameter(s) " + parsedValues); } try { @@ -212,4 +155,22 @@ protected Entitlement parseEntitlement(String scopeName, String entitlementType) throw new IllegalStateException("internal error"); } } + + protected PolicyParserException newPolicyParserException(String message) { + return PolicyParserException.newPolicyParserException(policyParser.getTokenLocation(), policyName, message); + } + + protected PolicyParserException newPolicyParserException(String scopeName, String message) { + return PolicyParserException.newPolicyParserException(policyParser.getTokenLocation(), policyName, scopeName, message); + } + + protected PolicyParserException newPolicyParserException(String scopeName, String entitlementType, String message) { + return PolicyParserException.newPolicyParserException( + policyParser.getTokenLocation(), + policyName, + scopeName, + entitlementType, + message + ); + } } diff --git a/distribution/tools/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/runtime/policy/PolicyParserException.java b/distribution/tools/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/runtime/policy/PolicyParserException.java index 2282f90a93d54..5dfa12f11d0be 100644 --- a/distribution/tools/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/runtime/policy/PolicyParserException.java +++ b/distribution/tools/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/runtime/policy/PolicyParserException.java @@ -11,6 +11,9 @@ import org.elasticsearch.xcontent.XContentLocation; +/** + * An exception specifically for policy parsing errors. + */ public class PolicyParserException extends RuntimeException { public static PolicyParserException newPolicyParserException(XContentLocation location, String policyName, String message) { diff --git a/distribution/tools/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/runtime/policy/Scope.java b/distribution/tools/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/runtime/policy/Scope.java index 424aeb557e97b..0fe63eb8da1b7 100644 --- a/distribution/tools/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/runtime/policy/Scope.java +++ b/distribution/tools/entitlement-runtime/src/main/java/org/elasticsearch/entitlement/runtime/policy/Scope.java @@ -13,6 +13,9 @@ import java.util.List; import java.util.Objects; +/** + * A holder for entitlements within a single scope. + */ public class Scope { public final String name; diff --git a/distribution/tools/entitlement-runtime/src/test/java/org/elasticsearch/entitlement/runtime/policy/PolicyParserFailureTests.java b/distribution/tools/entitlement-runtime/src/test/java/org/elasticsearch/entitlement/runtime/policy/PolicyParserFailureTests.java index e1002415edc72..b21d206f3eb6a 100644 --- a/distribution/tools/entitlement-runtime/src/test/java/org/elasticsearch/entitlement/runtime/policy/PolicyParserFailureTests.java +++ b/distribution/tools/entitlement-runtime/src/test/java/org/elasticsearch/entitlement/runtime/policy/PolicyParserFailureTests.java @@ -9,10 +9,7 @@ package org.elasticsearch.entitlement.runtime.policy; -import org.elasticsearch.common.Strings; import org.elasticsearch.test.ESTestCase; -import org.elasticsearch.xcontent.XContentBuilder; -import org.elasticsearch.xcontent.yaml.YamlXContent; import java.io.ByteArrayInputStream; import java.io.IOException; @@ -20,130 +17,67 @@ public class PolicyParserFailureTests extends ESTestCase { - public void testParserSyntaxFailures() throws IOException { - try (XContentBuilder builder = YamlXContent.contentBuilder()) { - builder.startArray(); - builder.endArray(); - PolicyParserException ppe = expectThrows( - PolicyParserException.class, - () -> new PolicyParser( - new ByteArrayInputStream(Strings.toString(builder).getBytes(StandardCharsets.UTF_8)), - "test-failure-policy.yaml" - ).parsePolicy() - ); - assertEquals("[1:5] policy parsing error for [test-failure-policy.yaml]: expected object ", ppe.getMessage()); - } + public void testParserSyntaxFailures() { + PolicyParserException ppe = expectThrows( + PolicyParserException.class, + () -> new PolicyParser(new ByteArrayInputStream("[]".getBytes(StandardCharsets.UTF_8)), "test-failure-policy.yaml") + .parsePolicy() + ); + assertEquals("[1:1] policy parsing error for [test-failure-policy.yaml]: expected object ", ppe.getMessage()); } public void testEntitlementDoesNotExist() throws IOException { - try (XContentBuilder builder = YamlXContent.contentBuilder()) { - builder.startObject(); - builder.startObject("entitlement-module-1"); - builder.startArray("entitlements"); - builder.startObject(); - builder.startObject("does_not_exist"); - builder.endObject(); - builder.endObject(); - builder.endArray(); - builder.endObject(); - builder.endObject(); - PolicyParserException ppe = expectThrows( - PolicyParserException.class, - () -> new PolicyParser( - new ByteArrayInputStream(Strings.toString(builder).getBytes(StandardCharsets.UTF_8)), - "test-failure-policy.yaml" - ).parsePolicy() - ); - assertEquals( - "[4:5] policy parsing error for [test-failure-policy.yaml] in scope [entitlement-module-1]: " - + "unknown entitlement type [does_not_exist]", - ppe.getMessage() - ); - } + PolicyParserException ppe = expectThrows(PolicyParserException.class, () -> new PolicyParser(new ByteArrayInputStream(""" + entitlement-module-name: + entitlements: + - does_not_exist: {} + """.getBytes(StandardCharsets.UTF_8)), "test-failure-policy.yaml").parsePolicy()); + assertEquals( + "[3:7] policy parsing error for [test-failure-policy.yaml] in scope [entitlement-module-name]: " + + "unknown entitlement type [does_not_exist]", + ppe.getMessage() + ); } public void testEntitlementMissingParameter() throws IOException { - try (XContentBuilder builder = YamlXContent.contentBuilder()) { - builder.startObject(); - builder.startObject("entitlement-module-1"); - builder.startArray("entitlements"); - builder.startObject(); - builder.startObject("file"); - builder.endObject(); - builder.endObject(); - builder.endArray(); - builder.endObject(); - builder.endObject(); - PolicyParserException ppe = expectThrows( - PolicyParserException.class, - () -> new PolicyParser( - new ByteArrayInputStream(Strings.toString(builder).getBytes(StandardCharsets.UTF_8)), - "test-failure-policy.yaml" - ).parsePolicy() - ); - assertEquals( - "[4:12] policy parsing error for [test-failure-policy.yaml] in scope [entitlement-module-1] " - + "for entitlement type [file]: missing entitlement parameter [path]", - ppe.getMessage() - ); - } + PolicyParserException ppe = expectThrows(PolicyParserException.class, () -> new PolicyParser(new ByteArrayInputStream(""" + entitlement-module-name: + entitlements: + - file: {} + """.getBytes(StandardCharsets.UTF_8)), "test-failure-policy.yaml").parsePolicy()); + assertEquals( + "[3:14] policy parsing error for [test-failure-policy.yaml] in scope [entitlement-module-name] " + + "for entitlement type [file]: missing entitlement parameter [path]", + ppe.getMessage() + ); - try (XContentBuilder builder = YamlXContent.contentBuilder()) { - builder.startObject(); - builder.startObject("entitlement-module-1"); - builder.startArray("entitlements"); - builder.startObject(); - builder.startObject("file"); - builder.field("path", "test-path"); - builder.endObject(); - builder.endObject(); - builder.endArray(); - builder.endObject(); - builder.endObject(); - PolicyParserException ppe = expectThrows( - PolicyParserException.class, - () -> new PolicyParser( - new ByteArrayInputStream(Strings.toString(builder).getBytes(StandardCharsets.UTF_8)), - "test-failure-policy.yaml" - ).parsePolicy() - ); - assertEquals( - "[6:1] policy parsing error for [test-failure-policy.yaml] in scope [entitlement-module-1] " - + "for entitlement type [file]: missing entitlement parameter [actions]", - ppe.getMessage() - ); - } + ppe = expectThrows(PolicyParserException.class, () -> new PolicyParser(new ByteArrayInputStream(""" + entitlement-module-name: + entitlements: + - file: + path: test-path + """.getBytes(StandardCharsets.UTF_8)), "test-failure-policy.yaml").parsePolicy()); + assertEquals( + "[5:1] policy parsing error for [test-failure-policy.yaml] in scope [entitlement-module-name] " + + "for entitlement type [file]: missing entitlement parameter [actions]", + ppe.getMessage() + ); } public void testEntitlementExtraneousParameter() throws IOException { - try (XContentBuilder builder = YamlXContent.contentBuilder()) { - builder.startObject(); - builder.startObject("entitlement-module-1"); - builder.startArray("entitlements"); - builder.startObject(); - builder.startObject("file"); - builder.field("path", "test-path"); - builder.startArray("actions"); - builder.value("read"); - builder.endArray(); - builder.field("extra", "test"); - builder.endObject(); - builder.endObject(); - builder.endArray(); - builder.endObject(); - builder.endObject(); - PolicyParserException ppe = expectThrows( - PolicyParserException.class, - () -> new PolicyParser( - new ByteArrayInputStream(Strings.toString(builder).getBytes(StandardCharsets.UTF_8)), - "test-failure-policy.yaml" - ).parsePolicy() - ); - assertEquals( - "[9:1] policy parsing error for [test-failure-policy.yaml] in scope [entitlement-module-1] " - + "for entitlement type [file]: extraneous entitlement parameter(s) {extra=test}", - ppe.getMessage() - ); - } + PolicyParserException ppe = expectThrows(PolicyParserException.class, () -> new PolicyParser(new ByteArrayInputStream(""" + entitlement-module-name: + entitlements: + - file: + path: test-path + actions: + - read + extra: test + """.getBytes(StandardCharsets.UTF_8)), "test-failure-policy.yaml").parsePolicy()); + assertEquals( + "[8:1] policy parsing error for [test-failure-policy.yaml] in scope [entitlement-module-name] " + + "for entitlement type [file]: extraneous entitlement parameter(s) {extra=test}", + ppe.getMessage() + ); } } diff --git a/distribution/tools/entitlement-runtime/src/test/java/org/elasticsearch/entitlement/runtime/policy/PolicyParserTests.java b/distribution/tools/entitlement-runtime/src/test/java/org/elasticsearch/entitlement/runtime/policy/PolicyParserTests.java index 90dd2f625dfcb..40016b2e3027e 100644 --- a/distribution/tools/entitlement-runtime/src/test/java/org/elasticsearch/entitlement/runtime/policy/PolicyParserTests.java +++ b/distribution/tools/entitlement-runtime/src/test/java/org/elasticsearch/entitlement/runtime/policy/PolicyParserTests.java @@ -21,7 +21,7 @@ public void testPolicyBuilder() throws IOException { .parsePolicy(); Policy builtPolicy = new Policy( "test-policy.yaml", - List.of(new Scope("entitlement-module-1", List.of(new FileEntitlement("test/path/to/file", List.of("read", "write"))))) + List.of(new Scope("entitlement-module-name", List.of(new FileEntitlement("test/path/to/file", List.of("read", "write"))))) ); assertEquals(parsedPolicy, builtPolicy); } diff --git a/distribution/tools/entitlement-runtime/src/test/resources/org/elasticsearch/entitlement/runtime/policy/test-policy.yaml b/distribution/tools/entitlement-runtime/src/test/resources/org/elasticsearch/entitlement/runtime/policy/test-policy.yaml index 93b24c3e60e70..b58287cfc83b7 100644 --- a/distribution/tools/entitlement-runtime/src/test/resources/org/elasticsearch/entitlement/runtime/policy/test-policy.yaml +++ b/distribution/tools/entitlement-runtime/src/test/resources/org/elasticsearch/entitlement/runtime/policy/test-policy.yaml @@ -1,4 +1,4 @@ -entitlement-module-1: +entitlement-module-name: entitlements: - file: path: "test/path/to/file"