Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
5624e36
WIP
keith-turner Dec 19, 2025
1cf49e3
WIP
keith-turner Dec 19, 2025
a39ad28
Merge remote-tracking branch 'upstream/main' into aa-88
keith-turner Dec 19, 2025
91a0e35
WIP
keith-turner Dec 19, 2025
8da01cf
WIP
keith-turner Dec 22, 2025
bc73738
WIP
keith-turner Dec 22, 2025
0446fd8
WIP
keith-turner Dec 23, 2025
57d4b74
WIP
keith-turner Dec 23, 2025
9ff4d77
WIP
keith-turner Dec 23, 2025
77e44ea
WIP
keith-turner Dec 23, 2025
b27aba6
WIP
keith-turner Dec 23, 2025
a4d99d8
Merge branch 'main' into aa-88
keith-turner Dec 23, 2025
bdce1e6
remove unused code
keith-turner Dec 23, 2025
48d96a7
fix build
keith-turner Dec 23, 2025
540dc9b
remove unintended change to module
keith-turner Dec 23, 2025
8ae95db
exclude replacement char by default
keith-turner Dec 23, 2025
a75d79e
adds test for replacement char
keith-turner Dec 23, 2025
6190098
pass information about quoting for optimization
keith-turner Dec 23, 2025
9b7d827
fix comment
keith-turner Dec 23, 2025
dd2ef2d
fix comment
keith-turner Dec 23, 2025
ba917f3
doc update
keith-turner Jan 6, 2026
22af52d
optimize by avoiding String.charAt()
keith-turner Jan 6, 2026
12770d5
revert benchmark change
keith-turner Jan 6, 2026
eccd51d
Merge remote-tracking branch 'upstream/main' into aa-88
keith-turner Jan 8, 2026
778c9e4
rename AccumuloAccess to Access
keith-turner Jan 8, 2026
7a37593
rename validate to validateExpression
keith-turner Jan 8, 2026
8331f63
remove newAuthorizations()
keith-turner Jan 9, 2026
4134d65
renamed AccumuloAccessImpl to AccessImpl
keith-turner Jan 9, 2026
f8492ab
improve API names
keith-turner Jan 9, 2026
e69ecf9
improve performance and test
keith-turner Jan 9, 2026
6752d65
add null checks and refactor code
keith-turner Jan 9, 2026
55a0c26
use record and to todo
keith-turner Jan 9, 2026
0ccaa8a
remove todo
keith-turner Jan 9, 2026
f7b0aa9
remove todo
keith-turner Jan 9, 2026
346e322
do todo
keith-turner Jan 9, 2026
7707204
fix specification test
keith-turner Jan 12, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 9 additions & 5 deletions SPECIFICATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,14 +49,17 @@ 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
access-token =/ DQUOTE 1*(unicode-subset / escaped) DQUOTE

utf8-subset = %x20-21 / %x23-5B / %x5D-7E / unicode-beyond-ascii ; utf8 minus '"' and '\'
unicode-beyond-ascii = %x0080-D7FF / %xE000-10FFFF
unicode-subset = %x00-21 / %x23-5B / %x5D-7F / unicode-beyond-ascii ; unicode minus '"' and '\'
escaped = "\" DQUOTE / "\\"
slash = "/"
```

Authorizations must be Unicode characters. Not all Unicode characters are human readable or even visible
(see [Unicode control characters][6]), implementations should provide a way to limit valid authorizations to
a subset of unicode characters (like human-readable characters).

### Examples of Proper Expressions

* `BLUE`
Expand All @@ -73,8 +76,8 @@ slash = "/"

## Serialization

An AccessExpression is a UTF-8 string. It can be serialized using a byte array as long as it
can be deserialized back into the same UTF-8 string.
An access expression or authorization must be a Unicode string. Serialization of an access expression or authorization
should use UTF-8.

## Evaluation

Expand Down Expand Up @@ -140,3 +143,4 @@ within the Authorizations object, the token is unquoted, and the `\` character i
[3]: https://en.wikipedia.org/wiki/Boolean_algebra
[4]: https://en.wikipedia.org/wiki/Logical_conjunction
[5]: https://en.wikipedia.org/wiki/Logical_disjunction
[6]: https://en.wikipedia.org/wiki/Unicode_control_characters
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,8 @@

import org.antlr.v4.runtime.tree.ParseTree;
import org.antlr.v4.runtime.tree.TerminalNode;
import org.apache.accumulo.access.AccessExpression;
import org.apache.accumulo.access.Access;
import org.apache.accumulo.access.Authorizations;
import org.apache.accumulo.access.InvalidAccessExpressionException;
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;
Expand All @@ -37,6 +36,8 @@

public class AccessExpressionAntlrEvaluator {

public static final Access ACCESS = Access.builder().build();

private class Entity {

private Set<String> authorizations;
Expand All @@ -60,7 +61,7 @@ public AccessExpressionAntlrEvaluator(List<Authorizations> authSets) {
e.authorizations = new HashSet<>(entityAuths.size() * 2);
a.asSet().stream().forEach(auth -> {
e.authorizations.add(auth);
String quoted = AccessExpression.quote(auth);
String quoted = ACCESS.quote(auth);
if (!quoted.startsWith("\"")) {
quoted = '"' + quoted + '"';
}
Expand All @@ -69,14 +70,6 @@ public AccessExpressionAntlrEvaluator(List<Authorizations> authSets) {
}
}

public boolean canAccess(byte[] accessExpression) throws InvalidAccessExpressionException {
return canAccess(AccessExpression.of(accessExpression));
}

public boolean canAccess(AccessExpression accessExpression) {
return canAccess(accessExpression.getExpression());
}

public boolean canAccess(String accessExpression) {
if ("".equals(accessExpression)) {
return true;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
package org.apache.accumulo.access.grammar.antlr;

import static java.nio.charset.StandardCharsets.UTF_8;
import static org.apache.accumulo.access.grammar.antlr.Antlr4Tests.ACCESS;

import java.io.IOException;
import java.net.URISyntaxException;
Expand All @@ -29,7 +30,6 @@
import java.util.stream.Collectors;
import java.util.stream.Stream;

import org.apache.accumulo.access.Authorizations;
import org.apache.accumulo.access.antlr.TestDataLoader;
import org.apache.accumulo.access.antlr4.AccessExpressionAntlrEvaluator;
import org.apache.accumulo.access.antlr4.AccessExpressionAntlrParser;
Expand Down Expand Up @@ -64,7 +64,7 @@ public static class EvaluatorTests {

List<Access_expressionContext> parsedExpressions;

List<byte[]> expressions;
List<String> expressions;
}

@State(Scope.Benchmark)
Expand All @@ -89,15 +89,15 @@ public void loadData() throws IOException, URISyntaxException {
et.expressions = new ArrayList<>();

et.evaluator = new AccessExpressionAntlrEvaluator(Stream.of(testDataSet.auths)
.map(a -> Authorizations.of(Set.of(a))).collect(Collectors.toList()));
.map(a -> ACCESS.newAuthorizations(Set.of(a))).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.expressions.add(exp);
et.parsedExpressions.add(AccessExpressionAntlrParser.parseAccessExpression(exp));
}
}
Expand Down Expand Up @@ -160,7 +160,7 @@ public void measureEvaluation(BenchmarkState state, Blackhole blackhole) {
@Benchmark
public void measureEvaluationAndParsing(BenchmarkState state, Blackhole blackhole) {
for (EvaluatorTests evaluatorTests : state.getEvaluatorTests()) {
for (byte[] expression : evaluatorTests.expressions) {
for (String expression : evaluatorTests.expressions) {
blackhole.consume(evaluatorTests.evaluator.canAccess(expression));
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@
*/
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;
Expand All @@ -40,7 +39,7 @@
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.Access;
import org.apache.accumulo.access.Authorizations;
import org.apache.accumulo.access.InvalidAccessExpressionException;
import org.apache.accumulo.access.antlr.TestDataLoader;
Expand All @@ -55,6 +54,8 @@

public class Antlr4Tests {

public static final Access ACCESS = Access.builder().build();

private void testParse(String input) throws Exception {
CodePointCharStream expression = CharStreams.fromString(input);
final AtomicLong errors = new AtomicLong(0);
Expand Down Expand Up @@ -108,10 +109,10 @@ public void testCompareWithAccessExpressionImplParsing() throws Exception {
ExpectedResult result = test.expectedResult;
for (String cv : test.expressions) {
if (result == ExpectedResult.ERROR) {
assertThrows(InvalidAccessExpressionException.class, () -> AccessExpression.of(cv));
assertThrows(InvalidAccessExpressionException.class, () -> ACCESS.newExpression(cv));
assertThrows(AssertionError.class, () -> testParse(cv));
} else {
AccessExpression.of(cv);
ACCESS.validateExpression(cv);
testParse(cv);
}
}
Expand All @@ -122,15 +123,15 @@ public void testCompareWithAccessExpressionImplParsing() throws Exception {
@Test
public void testSimpleEvaluation() throws Exception {
String accessExpression = "(one&two)|(foo&bar)";
Authorizations auths = Authorizations.of(Set.of("four", "three", "one", "two"));
Authorizations auths = ACCESS.newAuthorizations(Set.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(Set.of("A", "C"));
Authorizations auths = ACCESS.newAuthorizations(Set.of("A", "C"));
AccessExpressionAntlrEvaluator eval = new AccessExpressionAntlrEvaluator(List.of(auths));
assertFalse(eval.canAccess(accessExpression));
}
Expand All @@ -143,67 +144,29 @@ public void testCompareAntlrEvaluationAgainstAccessEvaluatorImpl() throws Except
for (TestDataSet testSet : testData) {

List<Authorizations> authSets = Stream.of(testSet.auths)
.map(a -> Authorizations.of(Set.of(a))).collect(Collectors.toList());
AccessEvaluator evaluator = AccessEvaluator.of(authSets);
.map(a -> ACCESS.newAuthorizations(Set.of(a))).collect(Collectors.toList());
AccessEvaluator evaluator = ACCESS.newEvaluator(authSets);
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);
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);

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);
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);

break;
case ERROR:
assertThrows(InvalidAccessExpressionException.class,
() -> evaluator.canAccess(expression), expression);
assertThrows(InvalidAccessExpressionException.class,
() -> evaluator.canAccess(expression.getBytes(UTF_8)), expression);
assertThrows(InvalidAccessExpressionException.class,
() -> evaluator.canAccess(AccessExpression.of(expression)), expression);
assertThrows(InvalidAccessExpressionException.class,
() -> evaluator.canAccess(AccessExpression.of(expression.getBytes(UTF_8))),
expression);

assertThrows(InvalidAccessExpressionException.class,
() -> antlr.canAccess(expression), expression);
assertThrows(InvalidAccessExpressionException.class,
() -> antlr.canAccess(expression.getBytes(UTF_8)), expression);
assertThrows(InvalidAccessExpressionException.class,
() -> antlr.canAccess(AccessExpression.of(expression)), expression);
assertThrows(InvalidAccessExpressionException.class,
() -> antlr.canAccess(AccessExpression.of(expression.getBytes(UTF_8))),
expression);
break;
default:
throw new IllegalArgumentException();
Expand Down
Loading