This is an automated email from the ASF dual-hosted git repository. dlmarion pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/accumulo-access.git
The following commit(s) were added to refs/heads/main by this push: new 8ef5fb5 Add ANTLR grammar and tests to contrib directory 8ef5fb5 is described below commit 8ef5fb5246edd70ae7ff9c95893f62c0f83478d6 Author: Dave Marion <dlmar...@apache.org> AuthorDate: Fri Sep 22 16:11:54 2023 -0400 Add ANTLR grammar and tests to contrib directory Added contrib/antlr4 directory which contains an ANTLRv4 grammar file for parsing AccessExpressions. I also pulled in the ANTLRv4 ABNF grammar file[1] and used that to parse the ABNF specification that we created to validate that the specification is syntactically correct. I created tests to validate that the AccessExpression grammar is correct. The ANTLRv4 grammar file (AccessExpression.g4) can be used by other projects that are written in a language supported by ANTLR4 (c++, c#, python, etc.) to validate that the access expressions that they are creating are valid. [1] https://github.com/antlr/grammars-v4/blob/master/abnf/Abnf.g4 --- README.md | 17 +- SPECIFICATION.md | 25 ++- contrib/.gitignore | 33 ++++ contrib/antlr4/README.md | 23 +++ pom.xml => contrib/antlr4/pom.xml | 77 ++++---- contrib/antlr4/src/main/antlr4/Abnf.g4 | 163 +++++++++++++++++ contrib/antlr4/src/main/antlr4/AccessExpression.g4 | 36 ++++ .../org/apache/accumulo/access/TestDataLoader.java | 77 ++++++++ .../accumulo/access/grammar/SpecificationTest.java | 66 +++++++ .../antlr/AccessExpressionAntlrBenchmark.java | 165 +++++++++++++++++ .../antlr/AccessExpressionAntlrEvaluator.java | 117 ++++++++++++ .../grammar/antlr/AccessExpressionAntlrParser.java | 96 ++++++++++ .../accumulo/access/grammar/antlr/Antlr4Tests.java | 202 +++++++++++++++++++++ .../antlr4/src/test/resources/specification.abnf | 17 ++ pom.xml | 1 + .../apache/accumulo/access/AccessEvaluator.java | 1 - .../accumulo/access/AccessEvaluatorTest.java | 1 - src/test/resources/testdata.json | 4 +- 18 files changed, 1056 insertions(+), 65 deletions(-) diff --git a/README.md b/README.md index 30f313d..2b86199 100644 --- a/README.md +++ b/README.md @@ -19,10 +19,17 @@ --> -# accumulo-access -Accumulo Access Control Library +# Accumulo Access Control Library -This project is a work in progress with the following goals. +This library is a stand-alone java library that provides the functionality of the Apache Accumulo ColumnVisibility for use outside of Accumulo. + +## Concepts + + * AccessExpression - a boolean expression of attributes required to access an object (e.g. Key/Value pair in Accumulo). See SPECIFICATION.md. + * Authorizations - a set of attributes, typically attributed to the entity trying to access an object. + * AccessEvaluator - An object that determines if the entity can access the object using the entity's Authorizations and the objects AccessExpression. + +## Goals * Create a standalone java library that offers the Accumulo visibility functionality * Support the same syntax and semantics as ColumnVisibility and VisibilityEvaluator initially. This will allow ColumnVisibility and VisibilityEvaluator to adapt to use this new library for their implementation. @@ -30,7 +37,9 @@ This project is a work in progress with the following goals. * Use no external types (like Hadoop types) in its API. * Use semantic versioning. -The following types constitute the public API of this library. All other types are package private and are not part of the public API. +## Public API + +The following types constitute the public API of this library. All other types are package private and are not part of the public API. * [IllegalAccessExpressionException](src/main/java/org/apache/accumulo/access/IllegalAccessExpressionException.java). * [AccessEvaluator](src/main/java/org/apache/accumulo/access/AccessEvaluator.java). diff --git a/SPECIFICATION.md b/SPECIFICATION.md index e733c13..51d99db 100644 --- a/SPECIFICATION.md +++ b/SPECIFICATION.md @@ -22,7 +22,7 @@ # AccessExpression Specification This document specifies the format of an Apache Accumulo AccessExpression. An AccessExpression -is an encoding of a boolean expression of the attributes that a subject is required to have to +is an encoding of a boolean expression of the attributes that an entity is required to have to access a particular piece of data. ## Syntax @@ -44,13 +44,12 @@ or-expression = "|" (access-token / paren-expression) [or-expression] access-token = 1*( ALPHA / DIGIT / "_" / "-" / "." / ":" / slash ) access-token =/ DQUOTE 1*(utf8-subset / escaped) DQUOTE -utf8-subset = %x20-21 / %x23-5B / %5D-7E / UVCHARBEYONDASCII ; utf8 minus '"' and '\' +utf8-subset = %x20-21 / %x23-5B / %x5D-7E / unicode-beyond-ascii ; utf8 minus '"' and '\' +unicode-beyond-ascii = %x0080-D7FF / %xE000-10FFFF escaped = "\" DQUOTE / "\\" slash = "/" ``` -The definition of utf8 was borrowed from this [ietf document][2]. TODO that doc defines unicode and not utf8 - ## Serialization An AccessExpression is a UTF-8 string. It can be serialized using a byte array as long as it @@ -58,13 +57,13 @@ can be deserialized back into the same UTF-8 string. ## Evaluation -Evaluation of access expressions performs a combination of [set][3] existence -checks and [boolean algebra][4]. Access expression use the following from +Evaluation of access expressions performs a combination of [set][2] existence +checks and [boolean algebra][3]. Access expression use the following from boolean algebra. - * The symbol `&` in an access expression represents [logical conjunction][5] + * The symbol `&` in an access expression represents [logical conjunction][4] which is represented in a boolean algebra as `∧`. - * The symbol `|` in an access expression represents [logical disjunction][6] + * The symbol `|` in an access expression represents [logical disjunction][5] which is represented in a boolean algebra as `∨`. When evaluating an access expression set existence checks are done against a @@ -119,9 +118,7 @@ Notice above when checking if `"abc\\xyz"` exist in the set that it is unquoted and the `\` character is unescaped. [1]: https://www.rfc-editor.org/rfc/rfc5234 -[2]: https://datatracker.ietf.org/doc/html/draft-seantek-unicode-in-abnf-03#section-4.2 -[3]: https://en.wikipedia.org/wiki/Set_(mathematics) -[4]: https://en.wikipedia.org/wiki/Boolean_algebra -[5]: https://en.wikipedia.org/wiki/Logical_conjunction -[6]: https://en.wikipedia.org/wiki/Logical_disjunction - +[2]: https://en.wikipedia.org/wiki/Set_(mathematics) +[3]: https://en.wikipedia.org/wiki/Boolean_algebra +[4]: https://en.wikipedia.org/wiki/Logical_conjunction +[5]: https://en.wikipedia.org/wiki/Logical_disjunction diff --git a/contrib/.gitignore b/contrib/.gitignore new file mode 100644 index 0000000..7cd1fdc --- /dev/null +++ b/contrib/.gitignore @@ -0,0 +1,33 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# +/antlr4/target/ + +/antlr4/.settings/ +/antlr4/.project +/antlr4/.classpath +/antlr4/.pydevproject +/antlr4/.idea +/antlr4/*.iml +/antlr4/*.ipr +/antlr4/*.iws +/antlr4/nbproject/ +/antlr4/nbactions.xml +/antlr4/nb-configuration.xml +/antlr4/.vscode/ +/antlr4/.factorypath diff --git a/contrib/antlr4/README.md b/contrib/antlr4/README.md new file mode 100644 index 0000000..1b90603 --- /dev/null +++ b/contrib/antlr4/README.md @@ -0,0 +1,23 @@ +<!-- + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + https://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. +--> +# ANTLR Example + +This contrib example contains an [ANTLRv4](https://www.antlr.org/) grammar file (see [AccessExpression.g4](src/main/antlr4/AccessExpression.g4)) that can be used to create AccessExpression parsers in languages supported by ANTLRv4. For example, a project could use this to validate that AccessExpression's are valid before sending them to Accumulo in the ColumnVisibility field of the Key. + +An example [parser](src/test/java/org/apache/accumulo/access/grammar/antlr/AccessExpressionAntlrParser.java) and [evaluator](src/test/java/org/apache/accumulo/access/grammar/antlr/AccessExpressionAntlrEvaluator.java) are used when building this project to confirm that the parsing and evaluation are consistent with the reference Java implementation. + +ANTLR was evaluated as a replacement for the existing custom Java parser, but it doesn't parse as fast as the custom implementation. You can view the performance differences by running the JMH benchmark in this contrib project and the one in the main project. diff --git a/pom.xml b/contrib/antlr4/pom.xml similarity index 72% copy from pom.xml copy to contrib/antlr4/pom.xml index e2147ce..ca30fbf 100644 --- a/pom.xml +++ b/contrib/antlr4/pom.xml @@ -1,24 +1,4 @@ <?xml version="1.0" encoding="UTF-8"?> -<!-- - - Licensed to the Apache Software Foundation (ASF) under one - or more contributor license agreements. See the NOTICE file - distributed with this work for additional information - regarding copyright ownership. The ASF licenses this file - to you under the Apache License, Version 2.0 (the - "License"); you may not use this file except in compliance - with the License. You may obtain a copy of the License at - - https://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, - software distributed under the License is distributed on an - "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - KIND, either express or implied. See the License for the - specific language governing permissions and limitations - under the License. - ---> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> @@ -27,16 +7,28 @@ <version>30</version> </parent> <groupId>org.apache.accumulo</groupId> - <artifactId>accumulo-access</artifactId> + <artifactId>accumulo-access-antlr4</artifactId> <version>1.0.0-SNAPSHOT</version> - <name>Apache Accumulo Access</name> + <name>Apache Accumulo Access Antlr4 Example</name> <properties> <maven.compiler.source>11</maven.compiler.source> <maven.compiler.target>11</maven.compiler.target> + <maven.test.redirectTestOutputToFile>true</maven.test.redirectTestOutputToFile> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <version.jmh>1.36</version.jmh> </properties> <dependencies> + <dependency> + <groupId>org.antlr</groupId> + <artifactId>antlr4-runtime</artifactId> + <version>4.13.1</version> + <scope>compile</scope> + </dependency> + <dependency> + <groupId>org.apache.accumulo</groupId> + <artifactId>accumulo-access</artifactId> + <version>1.0.0-SNAPSHOT</version> + </dependency> <dependency> <groupId>com.github.spotbugs</groupId> <artifactId>spotbugs-annotations</artifactId> @@ -96,6 +88,24 @@ </execution> </executions> </plugin> + <plugin> + <groupId>org.antlr</groupId> + <artifactId>antlr4-maven-plugin</artifactId> + <version>4.13.1</version> + <executions> + <execution> + <goals> + <goal>antlr4</goal> + </goals> + <phase>generate-sources</phase> + <configuration> + <outputDirectory>target/generated-sources/antlr4/org/apache/accumulo/access/grammars</outputDirectory> + <listener>false</listener> + <visitor>false</visitor> + </configuration> + </execution> + </executions> + </plugin> <plugin> <groupId>net.revelc.code</groupId> <artifactId>impsort-maven-plugin</artifactId> @@ -118,7 +128,7 @@ <artifactId>formatter-maven-plugin</artifactId> <version>2.23.0</version> <configuration> - <configFile>src/build/eclipse-codestyle.xml</configFile> + <configFile>../../src/build/eclipse-codestyle.xml</configFile> <lineEnding>LF</lineEnding> <skipCssFormatting>true</skipCssFormatting> <skipHtmlFormatting>true</skipHtmlFormatting> @@ -135,27 +145,6 @@ </execution> </executions> </plugin> - <plugin> - <groupId>net.revelc.code</groupId> - <artifactId>apilyzer-maven-plugin</artifactId> - <version>1.3.0</version> - <executions> - <execution> - <id>apilyzer</id> - <goals> - <goal>analyze</goal> - </goals> - <configuration> - <includes> - <include>org[.]apache[.]accumulo[.]access[.]IllegalAccessExpressionException</include> - <include>org[.]apache[.]accumulo[.]access[.]AccessExpression</include> - <include>org[.]apache[.]accumulo[.]access[.]AccessEvaluator</include> - <include>org[.]apache[.]accumulo[.]access[.]Authorizations</include> - </includes> - </configuration> - </execution> - </executions> - </plugin> </plugins> </build> </project> diff --git a/contrib/antlr4/src/main/antlr4/Abnf.g4 b/contrib/antlr4/src/main/antlr4/Abnf.g4 new file mode 100644 index 0000000..0eb9088 --- /dev/null +++ b/contrib/antlr4/src/main/antlr4/Abnf.g4 @@ -0,0 +1,163 @@ +/* +BSD License + +Copyright (c) 2013, Rainer Schuster +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. +3. Neither the name of Rainer Schuster nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +ABNF grammar derived from: + + http://tools.ietf.org/html/rfc5234 + + Augmented BNF for Syntax Specifications: ABNF + January 2008 + + http://tools.ietf.org/html/rfc7405 + + Case-Sensitive String Support in ABNF + December 2014 + +Terminal rules mainly created by ANTLRWorks 1.5 sample code. + */ + +/* + * This is a copy of https://github.com/antlr/grammars-v4/blob/master/abnf/Abnf.g4 that + * has been modified to specify the package name for the generated java code + */ +grammar Abnf; + + @header { + package org.apache.accumulo.access.grammars; + } + + +// Note: Whitespace handling not as strict as in the specification. + +rulelist + : rule_* EOF + ; + +rule_ + : ID '=' '/'? elements + ; + +elements + : alternation + ; + +alternation + : concatenation ( '/' concatenation )* + ; + +concatenation + : repetition + + ; + +repetition + : repeat_? element + ; + +repeat_ + : INT | INT? '*' INT? + ; + +element + : ID | group | option | STRING | NumberValue | ProseValue + ; + +group + : '(' alternation ')' + ; + +option + : '[' alternation ']' + ; + + +NumberValue + : '%' ( BinaryValue | DecimalValue | HexValue ) + ; + + +fragment BinaryValue + : 'b' BIT+ ( ( '.' BIT+ )+ | '-' BIT+ )? + ; + + +fragment DecimalValue + : 'd' DIGIT+ ( ( '.' DIGIT+ )+ | '-' DIGIT+ )? + ; + + +fragment HexValue + : 'x' HEX_DIGIT+ ( ( '.' HEX_DIGIT+ )+ | '-' HEX_DIGIT+ )? + ; + + +ProseValue + : '<' ~'>'* '>' + ; + +ID + : LETTER ( LETTER | DIGIT | '-' )* + ; + +INT + : '0' .. '9'+ + ; + +COMMENT + : ';' ~ ( '\n' | '\r' )* '\r'? '\n' -> channel ( HIDDEN ) + ; + +WS + : ( ' ' | '\t' | '\r' | '\n' ) -> channel ( HIDDEN ) + ; + + +STRING + : ( '%s' | '%i' )? '"' ~'"'* '"' + ; + +fragment LETTER : 'a' .. 'z' | 'A' .. 'Z'; + +fragment BIT + : '0' .. '1' + ; + + +fragment DIGIT + : '0' .. '9' + ; + + +// Note: from the RFC errata (http://www.rfc-editor.org/errata_search.php?rfc=5234&eid=4040): +// > ABNF strings are case insensitive and the character set for these strings is US-ASCII. +// > So the definition of HEXDIG already allows for both upper and lower case (or a mixture). +fragment HEX_DIGIT + : '0' .. '9' | 'a' .. 'f' | 'A' .. 'F' + ; diff --git a/contrib/antlr4/src/main/antlr4/AccessExpression.g4 b/contrib/antlr4/src/main/antlr4/AccessExpression.g4 new file mode 100644 index 0000000..e945c6d --- /dev/null +++ b/contrib/antlr4/src/main/antlr4/AccessExpression.g4 @@ -0,0 +1,36 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. +*/ +grammar AccessExpression; + +@header { + package org.apache.accumulo.access.grammars; +} + +access_expression : EOF | expression EOF; +expression : ( and_expression | or_expression | '(' expression ')' | access_token); +and_expression : ( access_token | '(' expression ')' ) (and_operator ( access_token | '(' expression ')' ) )+; +or_expression : ( access_token | '(' expression ')' ) (or_operator ( access_token | '(' expression ')' ) )+; +access_token : ACCESS_TOKEN; +and_operator : '&'; +or_operator : '|'; + + +ACCESS_TOKEN : ( [A-Za-z] | [0-9] | '_' | '-' | '.' | ':' | '/' )+ + | '"' ( [\u0020-\u0021] | [\u0023-\u005B] | [\u005D-\u007E] | [\u0080-\uD7FF] | [\uE000-\u{10FFFF}] | ( '\\"' | '\\\\' ) )+ '"' ; +WS : [\r\t\b\u000C]+ -> skip; \ No newline at end of file diff --git a/contrib/antlr4/src/test/java/org/apache/accumulo/access/TestDataLoader.java b/contrib/antlr4/src/test/java/org/apache/accumulo/access/TestDataLoader.java new file mode 100644 index 0000000..bb858d0 --- /dev/null +++ b/contrib/antlr4/src/test/java/org/apache/accumulo/access/TestDataLoader.java @@ -0,0 +1,77 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.accumulo.access; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.lang.reflect.Type; +import java.net.URISyntaxException; +import java.net.URL; +import java.util.ArrayList; +import java.util.List; + +import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; + +public class TestDataLoader { + + public enum ExpectedResult { + ACCESSIBLE, INACCESSIBLE, ERROR + } + + public static class TestExpressions { + public ExpectedResult expectedResult; + public String[] expressions; + } + + public static class TestDataSet { + public String description; + + public String[][] auths; + + public List<TestExpressions> tests; + } + + public static List<TestDataSet> readTestData() throws IOException, URISyntaxException { + + URL url = TestDataLoader.class.getClassLoader().getResource("."); + File testClassesDir = new File(url.toURI()); + File accumuloAccessParentDir = + testClassesDir.getParentFile().getParentFile().getParentFile().getParentFile(); + File accumuloAccessSourceDir = new File(accumuloAccessParentDir, "src"); + assertTrue(accumuloAccessSourceDir.exists()); + File accumuloAccessTestDir = new File(accumuloAccessSourceDir, "test"); + assertTrue(accumuloAccessTestDir.exists()); + File accumuloAccessTestResourcesDir = new File(accumuloAccessTestDir, "resources"); + assertTrue(accumuloAccessTestResourcesDir.exists()); + File testDataFile = new File(accumuloAccessTestResourcesDir, "testdata.json"); + assertTrue(testDataFile.exists()); + + try (FileInputStream input = new FileInputStream(testDataFile)) { + var json = new String(input.readAllBytes(), UTF_8); + + Type listType = new TypeToken<ArrayList<TestDataSet>>() {}.getType(); + return new Gson().fromJson(json, listType); + } + } +} diff --git a/contrib/antlr4/src/test/java/org/apache/accumulo/access/grammar/SpecificationTest.java b/contrib/antlr4/src/test/java/org/apache/accumulo/access/grammar/SpecificationTest.java new file mode 100644 index 0000000..d0362db --- /dev/null +++ b/contrib/antlr4/src/test/java/org/apache/accumulo/access/grammar/SpecificationTest.java @@ -0,0 +1,66 @@ +package org.apache.accumulo.access.grammar; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import java.io.InputStream; +import java.util.concurrent.atomic.AtomicLong; + +import org.antlr.v4.runtime.CharStreams; +import org.antlr.v4.runtime.CommonTokenStream; +import org.antlr.v4.runtime.ConsoleErrorListener; +import org.antlr.v4.runtime.LexerNoViableAltException; +import org.antlr.v4.runtime.RecognitionException; +import org.antlr.v4.runtime.Recognizer; +import org.apache.accumulo.access.grammar.antlr.Antlr4Tests; +import org.apache.accumulo.access.grammars.AbnfLexer; +import org.apache.accumulo.access.grammars.AbnfParser; +import org.junit.jupiter.api.Test; + +// This test uses the ANTLR ABNF grammar to parse the +// accumulo-access ANBF specification to validate that +// it is proper ANBF. +public class SpecificationTest { + + @Test + public void testAbnfSpecificationParses() throws Exception { + + // The test resource specification.abnf is a copy of the ABNF + // from SPECIFICATION.md + + InputStream is = Antlr4Tests.class.getResourceAsStream("/specification.abnf"); + assertNotNull(is); + + final AtomicLong errors = new AtomicLong(0); + + AbnfLexer lexer = new AbnfLexer(CharStreams.fromStream(is)) { + @Override + public void recover(LexerNoViableAltException e) { + super.recover(e); + errors.incrementAndGet(); + } + + @Override + public void recover(RecognitionException re) { + super.recover(re); + errors.incrementAndGet(); + } + }; + + AbnfParser parser = new AbnfParser(new CommonTokenStream(lexer)); + parser.removeErrorListeners(); + parser.addErrorListener(new ConsoleErrorListener() { + @Override + public void syntaxError(Recognizer<?,?> recognizer, Object offendingSymbol, int line, + int charPositionInLine, String msg, RecognitionException e) { + super.syntaxError(recognizer, offendingSymbol, line, charPositionInLine, msg, e); + errors.incrementAndGet(); + } + }); + + parser.rulelist(); + assertEquals(0, errors.get()); + + } + +} diff --git a/contrib/antlr4/src/test/java/org/apache/accumulo/access/grammar/antlr/AccessExpressionAntlrBenchmark.java b/contrib/antlr4/src/test/java/org/apache/accumulo/access/grammar/antlr/AccessExpressionAntlrBenchmark.java new file mode 100644 index 0000000..80ad3a3 --- /dev/null +++ b/contrib/antlr4/src/test/java/org/apache/accumulo/access/grammar/antlr/AccessExpressionAntlrBenchmark.java @@ -0,0 +1,165 @@ +package org.apache.accumulo.access.grammar.antlr; + +import static java.nio.charset.StandardCharsets.UTF_8; + +import java.io.IOException; +import java.net.URISyntaxException; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.apache.accumulo.access.Authorizations; +import org.apache.accumulo.access.TestDataLoader; +import org.apache.accumulo.access.grammars.AccessExpressionParser.Access_expressionContext; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.infra.Blackhole; +import org.openjdk.jmh.runner.Runner; +import org.openjdk.jmh.runner.options.Options; +import org.openjdk.jmh.runner.options.OptionsBuilder; +import org.openjdk.jmh.runner.options.TimeValue; + +/** + * Benchmarks Access Expressions using JMH. To run, use the following commands. + * + * <p> + * <blockquote> + * + * <pre> + * mvn clean package + * mvn exec:exec -Dexec.executable="java" -Dexec.classpathScope=test -Dexec.args="-classpath %classpath org.apache.accumulo.access.grammar.antlr.AccessExpressionAntlrBenchmark" + * </code></blockquote> + * </pre> + */ +public class AccessExpressionAntlrBenchmark { + + public static class EvaluatorTests { + AccessExpressionAntlrEvaluator evaluator; + + List<Access_expressionContext> parsedExpressions; + + List<byte[]> expressions; + } + + @State(Scope.Benchmark) + public static class BenchmarkState { + + private ArrayList<byte[]> allTestExpressions; + + private ArrayList<String> allTestExpressionsStr; + + private ArrayList<EvaluatorTests> evaluatorTests; + + @Setup + public void loadData() throws IOException, URISyntaxException { + List<TestDataLoader.TestDataSet> testData = TestDataLoader.readTestData(); + allTestExpressions = new ArrayList<>(); + allTestExpressionsStr = new ArrayList<>(); + evaluatorTests = new ArrayList<>(); + + for (var testDataSet : testData) { + EvaluatorTests et = new EvaluatorTests(); + et.parsedExpressions = new ArrayList<>(); + et.expressions = new ArrayList<>(); + + et.evaluator = new AccessExpressionAntlrEvaluator( + Stream.of(testDataSet.auths).map(Authorizations::of).collect(Collectors.toList())); + + for (var tests : testDataSet.tests) { + if (tests.expectedResult != TestDataLoader.ExpectedResult.ERROR) { + for (var exp : tests.expressions) { + allTestExpressionsStr.add(exp); + byte[] byteExp = exp.getBytes(UTF_8); + allTestExpressions.add(byteExp); + et.expressions.add(byteExp); + et.parsedExpressions.add(AccessExpressionAntlrParser.parseAccessExpression(exp)); + } + } + } + + evaluatorTests.add(et); + } + } + + List<byte[]> getBytesExpressions() { + return allTestExpressions; + } + + List<String> getStringExpressions() { + return allTestExpressionsStr; + } + + public ArrayList<EvaluatorTests> getEvaluatorTests() { + return evaluatorTests; + } + + } + + /** + * Measures the time it takes to parse an expression stored in byte[] and produce a parse tree. + */ + @Benchmark + public void measureBytesParsing(BenchmarkState state, Blackhole blackhole) { + for (byte[] accessExpression : state.getBytesExpressions()) { + blackhole.consume(AccessExpressionAntlrParser.parseAccessExpression(accessExpression)); + } + } + + /** + * Measures the time it takes to parse an expression stored in a String and produce a parse tree. + */ + @Benchmark + public void measureStringParsing(BenchmarkState state, Blackhole blackhole) { + for (String accessExpression : state.getStringExpressions()) { + blackhole.consume(AccessExpressionAntlrParser.parseAccessExpression(accessExpression)); + } + } + + /** + * Measures the time it takes to evaluate a previously parsed expression. + */ + @Benchmark + public void measureEvaluation(BenchmarkState state, Blackhole blackhole) { + for (EvaluatorTests evaluatorTests : state.getEvaluatorTests()) { + for (Access_expressionContext expression : evaluatorTests.parsedExpressions) { + blackhole.consume(evaluatorTests.evaluator.canAccess(expression)); + } + } + } + + /** + * Measures the time it takes to parse and evaluate an expression. This has to create the parse + * tree an operate on it. + */ + @Benchmark + public void measureEvaluationAndParsing(BenchmarkState state, Blackhole blackhole) { + for (EvaluatorTests evaluatorTests : state.getEvaluatorTests()) { + for (byte[] expression : evaluatorTests.expressions) { + blackhole.consume(evaluatorTests.evaluator.canAccess(expression)); + } + } + } + + public static void main(String[] args) throws Exception { + + var state = new BenchmarkState(); + state.loadData(); + + int numExpressions = state.getBytesExpressions().size(); + + System.out.println("Number of Expressions: " + numExpressions); + + Options opt = new OptionsBuilder().include(AccessExpressionAntlrBenchmark.class.getSimpleName()) + .mode(Mode.Throughput).operationsPerInvocation(numExpressions) + .timeUnit(TimeUnit.MICROSECONDS).warmupTime(TimeValue.seconds(5)).warmupIterations(3) + .measurementIterations(4).forks(3).build(); + + new Runner(opt).run(); + } + +} diff --git a/contrib/antlr4/src/test/java/org/apache/accumulo/access/grammar/antlr/AccessExpressionAntlrEvaluator.java b/contrib/antlr4/src/test/java/org/apache/accumulo/access/grammar/antlr/AccessExpressionAntlrEvaluator.java new file mode 100644 index 0000000..98fe4a8 --- /dev/null +++ b/contrib/antlr4/src/test/java/org/apache/accumulo/access/grammar/antlr/AccessExpressionAntlrEvaluator.java @@ -0,0 +1,117 @@ +package org.apache.accumulo.access.grammar.antlr; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.antlr.v4.runtime.tree.ParseTree; +import org.antlr.v4.runtime.tree.TerminalNode; +import org.apache.accumulo.access.AccessEvaluator; +import org.apache.accumulo.access.AccessExpression; +import org.apache.accumulo.access.Authorizations; +import org.apache.accumulo.access.IllegalAccessExpressionException; +import org.apache.accumulo.access.grammars.AccessExpressionParser.Access_expressionContext; +import org.apache.accumulo.access.grammars.AccessExpressionParser.Access_tokenContext; +import org.apache.accumulo.access.grammars.AccessExpressionParser.And_expressionContext; +import org.apache.accumulo.access.grammars.AccessExpressionParser.And_operatorContext; +import org.apache.accumulo.access.grammars.AccessExpressionParser.Or_expressionContext; +import org.apache.accumulo.access.grammars.AccessExpressionParser.Or_operatorContext; + +public class AccessExpressionAntlrEvaluator implements AccessEvaluator { + + private class Entity { + + private Set<String> authorizations; + + @Override + public String toString() { + return "Entity [authorizations=" + authorizations + "]"; + } + + } + + private final List<Entity> entities; + + public AccessExpressionAntlrEvaluator(List<Authorizations> authSets) { + entities = new ArrayList<>(authSets.size()); + + for (Authorizations a : authSets) { + Set<String> entityAuths = a.asSet(); + Entity e = new Entity(); + entities.add(e); + e.authorizations = new HashSet<>(entityAuths.size() * 2); + a.asSet().stream().forEach(auth -> { + e.authorizations.add(auth); + String quoted = AccessExpression.quote(auth); + if (!quoted.startsWith("\"")) { + quoted = '"' + quoted + '"'; + } + e.authorizations.add(quoted); + }); + } + } + + public boolean canAccess(byte[] accessExpression) throws IllegalAccessExpressionException { + return canAccess(AccessExpression.of(accessExpression)); + } + + public boolean canAccess(AccessExpression accessExpression) { + return canAccess(accessExpression.getExpression()); + } + + public boolean canAccess(String accessExpression) { + if ("".equals(accessExpression)) { + return true; + } + return canAccess(AccessExpressionAntlrParser.parseAccessExpression(accessExpression)); + } + + public boolean canAccess(Access_expressionContext parsedExpression) { + for (Entity e : entities) { + if (!evaluate(e, parsedExpression)) { + return false; + } + } + return true; + } + + private Boolean evaluate(Entity e, ParseTree node) { + Boolean retval; + if (node instanceof Access_tokenContext) { + retval = e.authorizations.contains(node.getText()); + } else if (node instanceof TerminalNode + || node instanceof And_operatorContext | node instanceof Or_operatorContext) { + retval = null; + } else { + int childCount = node.getChildCount(); + int trueCount = 0; + int falseCount = 0; + for (int i = 0; i < childCount; i++) { + Boolean childResult = evaluate(e, node.getChild(i)); + if (childResult == null) { + continue; + } else { + if (childResult == Boolean.TRUE) { + trueCount++; + } else { + falseCount++; + } + } + } + if (childCount == 1) { + retval = falseCount == 0; + } else { + if (node instanceof Or_expressionContext) { + retval = trueCount > 0; + } else if (node instanceof And_expressionContext) { + retval = trueCount > 0 && falseCount == 0; + } else { + retval = trueCount > 0 && falseCount == 0; + } + } + } + return retval; + } + +} diff --git a/contrib/antlr4/src/test/java/org/apache/accumulo/access/grammar/antlr/AccessExpressionAntlrParser.java b/contrib/antlr4/src/test/java/org/apache/accumulo/access/grammar/antlr/AccessExpressionAntlrParser.java new file mode 100644 index 0000000..615e836 --- /dev/null +++ b/contrib/antlr4/src/test/java/org/apache/accumulo/access/grammar/antlr/AccessExpressionAntlrParser.java @@ -0,0 +1,96 @@ +package org.apache.accumulo.access.grammar.antlr; + +import java.nio.charset.StandardCharsets; + +import org.antlr.v4.runtime.BailErrorStrategy; +import org.antlr.v4.runtime.CharStream; +import org.antlr.v4.runtime.CharStreams; +import org.antlr.v4.runtime.CodePointCharStream; +import org.antlr.v4.runtime.CommonTokenStream; +import org.antlr.v4.runtime.ConsoleErrorListener; +import org.antlr.v4.runtime.LexerNoViableAltException; +import org.antlr.v4.runtime.RecognitionException; +import org.antlr.v4.runtime.Recognizer; +import org.apache.accumulo.access.IllegalAccessExpressionException; +import org.apache.accumulo.access.grammars.AccessExpressionLexer; +import org.apache.accumulo.access.grammars.AccessExpressionParser; +import org.apache.accumulo.access.grammars.AccessExpressionParser.Access_expressionContext; + +public class AccessExpressionAntlrParser { + + private static class AccessExpressionLexerWithErrors extends AccessExpressionLexer { + + private int errors = 0; + private final CharStream input; + + public AccessExpressionLexerWithErrors(CharStream input) { + super(input); + this.input = input; + } + + @Override + public void recover(LexerNoViableAltException e) { + System.out.println("Error in lexer. Expression: " + input + ", msg: " + e); + super.recover(e); + errors++; + } + + @Override + public void recover(RecognitionException re) { + System.out.println("Error in lexer. Expression: " + input + ", msg: " + re); + super.recover(re); + errors++; + } + + public int getErrorCount() { + return errors; + } + + } + + private static class ParserErrorListener extends ConsoleErrorListener { + + int errors = 0; + + @Override + public void syntaxError(Recognizer<?,?> recognizer, Object offendingSymbol, int line, + int charPositionInLine, String msg, RecognitionException e) { + super.syntaxError(recognizer, offendingSymbol, line, charPositionInLine, msg, e); + errors++; + } + + public int getErrorCount() { + return errors; + } + + } + + public static Access_expressionContext parseAccessExpression(byte[] accessExpression) + throws IllegalAccessExpressionException { + return parseAccessExpression(new String(accessExpression, StandardCharsets.UTF_8)); + } + + public static Access_expressionContext parseAccessExpression(String accessExpression) + throws IllegalAccessExpressionException { + CodePointCharStream input = CharStreams.fromString(accessExpression); + AccessExpressionLexerWithErrors lexer = new AccessExpressionLexerWithErrors(input); + AccessExpressionParser parser = new AccessExpressionParser(new CommonTokenStream(lexer)); + parser.setErrorHandler(new BailErrorStrategy()); + parser.removeErrorListeners(); + ParserErrorListener errorListener = new ParserErrorListener(); + parser.addErrorListener(errorListener); + try { + int errors = 0; + Access_expressionContext ctx = parser.access_expression(); + errors = lexer.getErrorCount(); + errors += errorListener.getErrorCount(); + if (errors > 0 || parser.getNumberOfSyntaxErrors() > 0 || ctx.exception != null) { + throw new IllegalAccessExpressionException("Parse error", "", 0); + } + return ctx; + } catch (RuntimeException e1) { + throw new IllegalAccessExpressionException(e1.getMessage(), "", 0); + } + } + +} diff --git a/contrib/antlr4/src/test/java/org/apache/accumulo/access/grammar/antlr/Antlr4Tests.java b/contrib/antlr4/src/test/java/org/apache/accumulo/access/grammar/antlr/Antlr4Tests.java new file mode 100644 index 0000000..799d18c --- /dev/null +++ b/contrib/antlr4/src/test/java/org/apache/accumulo/access/grammar/antlr/Antlr4Tests.java @@ -0,0 +1,202 @@ +package org.apache.accumulo.access.grammar.antlr; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.List; +import java.util.concurrent.atomic.AtomicLong; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.antlr.v4.runtime.BailErrorStrategy; +import org.antlr.v4.runtime.CharStreams; +import org.antlr.v4.runtime.CodePointCharStream; +import org.antlr.v4.runtime.CommonTokenStream; +import org.antlr.v4.runtime.ConsoleErrorListener; +import org.antlr.v4.runtime.LexerNoViableAltException; +import org.antlr.v4.runtime.RecognitionException; +import org.antlr.v4.runtime.Recognizer; +import org.apache.accumulo.access.AccessEvaluator; +import org.apache.accumulo.access.AccessExpression; +import org.apache.accumulo.access.Authorizations; +import org.apache.accumulo.access.IllegalAccessExpressionException; +import org.apache.accumulo.access.TestDataLoader; +import org.apache.accumulo.access.TestDataLoader.ExpectedResult; +import org.apache.accumulo.access.TestDataLoader.TestDataSet; +import org.apache.accumulo.access.TestDataLoader.TestExpressions; +import org.apache.accumulo.access.grammars.AccessExpressionLexer; +import org.apache.accumulo.access.grammars.AccessExpressionParser; +import org.apache.accumulo.access.grammars.AccessExpressionParser.Access_expressionContext; +import org.junit.jupiter.api.Test; + +public class Antlr4Tests { + + private void testParse(String input) throws Exception { + CodePointCharStream expression = CharStreams.fromString(input); + final AtomicLong errors = new AtomicLong(0); + AccessExpressionLexer lexer = new AccessExpressionLexer(expression) { + + @Override + public void recover(LexerNoViableAltException e) { + System.out.println("Error in lexer. Expression: " + expression + ", msg: " + e); + super.recover(e); + errors.incrementAndGet(); + } + + @Override + public void recover(RecognitionException re) { + System.out.println("Error in lexer. Expression: " + expression + ", msg: " + re); + super.recover(re); + errors.incrementAndGet(); + } + + }; + AccessExpressionParser parser = new AccessExpressionParser(new CommonTokenStream(lexer)); + parser.setErrorHandler(new BailErrorStrategy()); + parser.removeErrorListeners(); + parser.addErrorListener(new ConsoleErrorListener() { + + @Override + public void syntaxError(Recognizer<?,?> recognizer, Object offendingSymbol, int line, + int charPositionInLine, String msg, RecognitionException e) { + super.syntaxError(recognizer, offendingSymbol, line, charPositionInLine, msg, e); + errors.incrementAndGet(); + } + + }); + try { + Access_expressionContext ctx = parser.access_expression(); + assertNull(ctx.exception); + } catch (RuntimeException e1) { + throw new AssertionError(e1); + } + assertEquals(0, errors.get()); + assertEquals(0, parser.getNumberOfSyntaxErrors()); + } + + @Test + public void testCompareWithAccessExpressionImplParsing() throws Exception { + // This test checks that the parsing of the AccessExpressions in testdata.json + // using ANTLR have the same outcome as AccessExpression.of() + List<TestDataSet> testData = TestDataLoader.readTestData(); + for (TestDataSet testSet : testData) { + for (TestExpressions test : testSet.tests) { + ExpectedResult result = test.expectedResult; + for (String cv : test.expressions) { + if (result == ExpectedResult.ERROR) { + assertThrows(IllegalAccessExpressionException.class, () -> AccessExpression.of(cv)); + assertThrows(AssertionError.class, () -> testParse(cv)); + } else { + AccessExpression.of(cv); + testParse(cv); + } + } + } + } + } + + @Test + public void testSimpleEvaluation() throws Exception { + String accessExpression = "(one&two)|(foo&bar)"; + Authorizations auths = Authorizations.of("four", "three", "one", "two"); + AccessExpressionAntlrEvaluator eval = new AccessExpressionAntlrEvaluator(List.of(auths)); + assertTrue(eval.canAccess(accessExpression)); + } + + @Test + public void testSimpleEvaluationFailure() throws Exception { + String accessExpression = "(A&B&C)"; + Authorizations auths = Authorizations.of("A", "C"); + AccessExpressionAntlrEvaluator eval = new AccessExpressionAntlrEvaluator(List.of(auths)); + assertFalse(eval.canAccess(accessExpression)); + } + + @Test + public void testCompareAntlrEvaluationAgainstAccessEvaluatorImpl() throws Exception { + // This test checks that the evaluation of the AccessExpressions in testdata.json + // using ANTLR have the same outcome as AccessEvaluatorImpl + List<TestDataSet> testData = TestDataLoader.readTestData(); + for (TestDataSet testSet : testData) { + + List<Authorizations> authSets = + Stream.of(testSet.auths).map(Authorizations::of).collect(Collectors.toList()); + AccessEvaluator evaluator = AccessEvaluator.builder().authorizations(authSets).build(); + AccessExpressionAntlrEvaluator antlr = new AccessExpressionAntlrEvaluator(authSets); + + for (TestExpressions test : testSet.tests) { + for (String expression : test.expressions) { + switch (test.expectedResult) { + case ACCESSIBLE: + assertTrue(evaluator.canAccess(expression), expression); + assertTrue(evaluator.canAccess(expression.getBytes(UTF_8)), expression); + assertTrue(evaluator.canAccess(AccessExpression.of(expression)), expression); + assertTrue(evaluator.canAccess(AccessExpression.of(expression.getBytes(UTF_8))), + expression); + assertTrue(evaluator.canAccess(AccessExpression.of(expression).normalize()), + expression); + assertEquals(expression, + AccessExpression.of(expression.getBytes(UTF_8)).getExpression()); + assertEquals(expression, AccessExpression.of(expression).getExpression()); + + assertTrue(antlr.canAccess(expression), expression); + assertTrue(antlr.canAccess(expression.getBytes(UTF_8)), expression); + assertTrue(antlr.canAccess(AccessExpression.of(expression)), expression); + assertTrue(antlr.canAccess(AccessExpression.of(expression.getBytes(UTF_8))), + expression); + assertTrue(antlr.canAccess(AccessExpression.of(expression).normalize()), expression); + + break; + case INACCESSIBLE: + assertFalse(evaluator.canAccess(expression), expression); + assertFalse(evaluator.canAccess(expression.getBytes(UTF_8)), expression); + assertFalse(evaluator.canAccess(AccessExpression.of(expression)), expression); + assertFalse(evaluator.canAccess(AccessExpression.of(expression.getBytes(UTF_8))), + expression); + assertFalse(evaluator.canAccess(AccessExpression.of(expression).normalize()), + expression); + assertEquals(expression, + AccessExpression.of(expression.getBytes(UTF_8)).getExpression()); + assertEquals(expression, AccessExpression.of(expression).getExpression()); + + assertFalse(antlr.canAccess(expression), expression); + assertFalse(antlr.canAccess(expression.getBytes(UTF_8)), expression); + assertFalse(antlr.canAccess(AccessExpression.of(expression)), expression); + assertFalse(antlr.canAccess(AccessExpression.of(expression.getBytes(UTF_8))), + expression); + assertFalse(antlr.canAccess(AccessExpression.of(expression).normalize()), expression); + + break; + case ERROR: + assertThrows(IllegalAccessExpressionException.class, + () -> evaluator.canAccess(expression), expression); + assertThrows(IllegalAccessExpressionException.class, + () -> evaluator.canAccess(expression.getBytes(UTF_8)), expression); + assertThrows(IllegalAccessExpressionException.class, + () -> evaluator.canAccess(AccessExpression.of(expression)), expression); + assertThrows(IllegalAccessExpressionException.class, + () -> evaluator.canAccess(AccessExpression.of(expression.getBytes(UTF_8))), + expression); + + assertThrows(IllegalAccessExpressionException.class, + () -> antlr.canAccess(expression), expression); + assertThrows(IllegalAccessExpressionException.class, + () -> antlr.canAccess(expression.getBytes(UTF_8)), expression); + assertThrows(IllegalAccessExpressionException.class, + () -> antlr.canAccess(AccessExpression.of(expression)), expression); + assertThrows(IllegalAccessExpressionException.class, + () -> antlr.canAccess(AccessExpression.of(expression.getBytes(UTF_8))), + expression); + break; + default: + throw new IllegalArgumentException(); + } + } + } + } + + } +} diff --git a/contrib/antlr4/src/test/resources/specification.abnf b/contrib/antlr4/src/test/resources/specification.abnf new file mode 100644 index 0000000..ccd1c38 --- /dev/null +++ b/contrib/antlr4/src/test/resources/specification.abnf @@ -0,0 +1,17 @@ +access-expression = [expression] ; empty string is a valid access expression + +expression = (access-token / paren-expression) [and-expression / or-expression] + +paren-expression = "(" expression ")" + +and-expression = "&" (access-token / paren-expression) [and-expression] + +or-expression = "|" (access-token / paren-expression) [or-expression] + +access-token = 1*( ALPHA / DIGIT / "_" / "-" / "." / ":" / slash ) +access-token =/ DQUOTE 1*(utf8-subset / escaped) DQUOTE + +utf8-subset = %x20-21 / %x23-5B / %x5D-7E / unicode-beyond-ascii ; utf8 minus '"' and '\' +unicode-beyond-ascii = %x0080-D7FF / %xE000-10FFFF +escaped = "\" DQUOTE / "\\" +slash = "/" diff --git a/pom.xml b/pom.xml index e2147ce..626572f 100644 --- a/pom.xml +++ b/pom.xml @@ -33,6 +33,7 @@ <properties> <maven.compiler.source>11</maven.compiler.source> <maven.compiler.target>11</maven.compiler.target> + <maven.test.redirectTestOutputToFile>true</maven.test.redirectTestOutputToFile> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <version.jmh>1.36</version.jmh> </properties> diff --git a/src/main/java/org/apache/accumulo/access/AccessEvaluator.java b/src/main/java/org/apache/accumulo/access/AccessEvaluator.java index 576ad5b..0f579d0 100644 --- a/src/main/java/org/apache/accumulo/access/AccessEvaluator.java +++ b/src/main/java/org/apache/accumulo/access/AccessEvaluator.java @@ -19,7 +19,6 @@ package org.apache.accumulo.access; import java.util.Collection; -import java.util.List; /** * <p> diff --git a/src/test/java/org/apache/accumulo/access/AccessEvaluatorTest.java b/src/test/java/org/apache/accumulo/access/AccessEvaluatorTest.java index a5b59ec..5b3f62b 100644 --- a/src/test/java/org/apache/accumulo/access/AccessEvaluatorTest.java +++ b/src/test/java/org/apache/accumulo/access/AccessEvaluatorTest.java @@ -119,7 +119,6 @@ public class AccessEvaluatorTest { assertTrue(tests.expressions.length > 0); for (var expression : tests.expressions) { - switch (tests.expectedResult) { case ACCESSIBLE: assertTrue(evaluator.canAccess(expression), expression); diff --git a/src/test/resources/testdata.json b/src/test/resources/testdata.json index be9c0d9..3654e49 100644 --- a/src/test/resources/testdata.json +++ b/src/test/resources/testdata.json @@ -198,7 +198,9 @@ "(|a)", "&a&b(a&b)", "#A", - "#&" + "#&", + "(A&B)D&C", + "A&B(D&C)" ] } ]