diff --git a/core/src/main/java/org/apache/accumulo/core/client/summary/summarizers/AuthorizationSummarizer.java b/core/src/main/java/org/apache/accumulo/core/client/summary/summarizers/AuthorizationSummarizer.java index ff60606a784..16d763da3fd 100644 --- a/core/src/main/java/org/apache/accumulo/core/client/summary/summarizers/AuthorizationSummarizer.java +++ b/core/src/main/java/org/apache/accumulo/core/client/summary/summarizers/AuthorizationSummarizer.java @@ -24,9 +24,9 @@ import java.util.Set; import java.util.function.Consumer; -import org.apache.accumulo.access.AccessExpression; import org.apache.accumulo.core.client.admin.TableOperations; import org.apache.accumulo.core.client.summary.CountingSummarizer; +import org.apache.accumulo.core.clientImpl.access.BytesAccess; import org.apache.accumulo.core.data.ArrayByteSequence; import org.apache.accumulo.core.data.ByteSequence; import org.apache.accumulo.core.data.Key; @@ -82,7 +82,7 @@ public void convert(Key k, Value v, Consumer consumer) { Set auths = cache.get(vis); if (auths == null) { var newAuths = new HashSet(); - AccessExpression.findAuthorizations(vis.toArray(), + BytesAccess.findAuthorizations(vis.toArray(), auth -> newAuths.add(new ArrayByteSequence(auth))); cache.put(new ArrayByteSequence(vis), newAuths); auths = newAuths; diff --git a/core/src/main/java/org/apache/accumulo/core/clientImpl/ConditionalWriterImpl.java b/core/src/main/java/org/apache/accumulo/core/clientImpl/ConditionalWriterImpl.java index 2103f90392c..51739bf45e2 100644 --- a/core/src/main/java/org/apache/accumulo/core/clientImpl/ConditionalWriterImpl.java +++ b/core/src/main/java/org/apache/accumulo/core/clientImpl/ConditionalWriterImpl.java @@ -43,7 +43,6 @@ import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.TimeUnit; -import org.apache.accumulo.access.AccessEvaluator; import org.apache.accumulo.access.InvalidAccessExpressionException; import org.apache.accumulo.core.client.AccumuloException; import org.apache.accumulo.core.client.AccumuloSecurityException; @@ -52,6 +51,7 @@ import org.apache.accumulo.core.client.Durability; import org.apache.accumulo.core.client.TimedOutException; import org.apache.accumulo.core.clientImpl.ClientTabletCache.TabletServerMutations; +import org.apache.accumulo.core.clientImpl.access.BytesAccess; import org.apache.accumulo.core.clientImpl.thrift.TInfo; import org.apache.accumulo.core.clientImpl.thrift.ThriftSecurityException; import org.apache.accumulo.core.data.ByteSequence; @@ -97,7 +97,7 @@ public class ConditionalWriterImpl implements ConditionalWriter { private static final int MAX_SLEEP = 30000; private final Authorizations auths; - private final AccessEvaluator accessEvaluator; + private final BytesAccess.BytesEvaluator accessEvaluator; private final Map cache = Collections.synchronizedMap(new LRUMap<>(1000)); private final ClientContext context; private final ClientTabletCache locator; @@ -375,7 +375,7 @@ private TabletServerMutations dequeue(String location) { this.config = config; this.context = context; this.auths = config.getAuthorizations(); - this.accessEvaluator = AccessEvaluator.of(config.getAuthorizations().toAccessAuthorizations()); + this.accessEvaluator = BytesAccess.newEvaluator(config.getAuthorizations()); this.threadPool = context.threadPools().createScheduledExecutorService( config.getMaxWriteThreads(), CONDITIONAL_WRITER_POOL.poolName); this.locator = new SyncingClientTabletCache(context, tableId); diff --git a/core/src/main/java/org/apache/accumulo/core/clientImpl/access/BytesAccess.java b/core/src/main/java/org/apache/accumulo/core/clientImpl/access/BytesAccess.java new file mode 100644 index 00000000000..451c0eff3a7 --- /dev/null +++ b/core/src/main/java/org/apache/accumulo/core/clientImpl/access/BytesAccess.java @@ -0,0 +1,109 @@ +/* + * 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.core.clientImpl.access; + +import static java.nio.charset.StandardCharsets.ISO_8859_1; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.function.Consumer; + +import org.apache.accumulo.access.Access; +import org.apache.accumulo.access.AccessEvaluator; +import org.apache.accumulo.core.data.ArrayByteSequence; +import org.apache.accumulo.core.security.AuthorizationContainer; +import org.apache.accumulo.core.security.Authorizations; + +/** + * All Accumulo Access APIs are String based. Accumulo's legacy APIs for access control are all + * based on byte[]. This class maps those legacy byte[] based APIs to use the Accumulo Access string + * based APIs. + * + *

+ * This mapping is done by using ISO_8859_1 to convert byte[] to String and vice versa. ISO_8859_1 + * is used because it maps each unsigned integer in a byte array directly to the same unsigned + * integer in the strings char array. This preserves the behavior of Accumulo legacy APIs by making + * Accumulo Access operate on the exact same data as the legacy APIs did. + * + *

+ * All transformations using ISO_8859_1 should remain completely within this class and never escape. + * For example if a string escaped this class it could lead to an overall situation where + * {@code new String(bytes,ISO_8859_1).getBytes(UTF_8)} accidentally happens. + * + *

+ * In addition to using ISO_8859_1, an instance of Accumulo Access is created that does no + * validation of authorizations. By default, Accumulo Access would reject some chars which + * conceptually would reject some bytes. The legacy Accumulo APIs accepted any bytes. + * + *

+ * This class is meant to serve as a bridge between legacy behavior that accepted any byte in an + * access expression and newer default behavior in Accumulo Access that only accepts valid Unicode. + */ +public class BytesAccess { + + private static final Access ACCESS = + Access.builder().authorizationValidator((auth, authChars) -> true).build(); + + public static void validate(byte[] expression) { + ACCESS.validateExpression(new String(expression, ISO_8859_1)); + } + + public static byte[] quote(byte[] auth) { + return ACCESS.quote(new String(auth, ISO_8859_1)).getBytes(ISO_8859_1); + } + + public static String quote(String auth) { + // TODO is this safe? does not go through a byte array + return ACCESS.quote(auth); + } + + public static void findAuthorizations(byte[] expression, Consumer consumer) { + ACCESS.findAuthorizations(new String(expression, ISO_8859_1), + authString -> consumer.accept(authString.getBytes(ISO_8859_1))); + } + + public static class BytesEvaluator { + + private final AccessEvaluator evaluator; + + private BytesEvaluator(AccessEvaluator evaluator) { + this.evaluator = evaluator; + } + + public boolean canAccess(byte[] expression) { + return evaluator.canAccess(new String(expression, ISO_8859_1)); + } + } + + public static BytesEvaluator newEvaluator(Authorizations auths) { + List bytesAuths = auths.getAuthorizations(); + Set stringAuths = new HashSet<>(bytesAuths.size()); + for (var auth : bytesAuths) { + stringAuths.add(new String(auth, ISO_8859_1)); + } + return new BytesEvaluator(ACCESS.newEvaluator(ACCESS.newAuthorizations(stringAuths))); + } + + public static BytesEvaluator newEvaluator(AuthorizationContainer authContainer) { + AccessEvaluator.Authorizer authorizer = authString -> authContainer + .contains(new ArrayByteSequence(authString.getBytes(ISO_8859_1))); + return new BytesEvaluator(ACCESS.newEvaluator(authorizer)); + } +} diff --git a/core/src/main/java/org/apache/accumulo/core/data/constraints/VisibilityConstraint.java b/core/src/main/java/org/apache/accumulo/core/data/constraints/VisibilityConstraint.java index 2bd0fc1ec9e..c9fcdc235b1 100644 --- a/core/src/main/java/org/apache/accumulo/core/data/constraints/VisibilityConstraint.java +++ b/core/src/main/java/org/apache/accumulo/core/data/constraints/VisibilityConstraint.java @@ -24,9 +24,8 @@ import java.util.HashSet; import java.util.List; -import org.apache.accumulo.access.AccessEvaluator; import org.apache.accumulo.access.InvalidAccessExpressionException; -import org.apache.accumulo.core.data.ArrayByteSequence; +import org.apache.accumulo.core.clientImpl.access.BytesAccess; import org.apache.accumulo.core.data.ColumnUpdate; import org.apache.accumulo.core.data.Mutation; @@ -64,7 +63,7 @@ public List check(Environment env, Mutation mutation) { ok = new HashSet<>(); } - AccessEvaluator ve = null; + BytesAccess.BytesEvaluator ve = null; for (ColumnUpdate update : updates) { @@ -79,7 +78,7 @@ public List check(Environment env, Mutation mutation) { if (ve == null) { var authContainer = env.getAuthorizationsContainer(); - ve = AccessEvaluator.of(auth -> authContainer.contains(new ArrayByteSequence(auth))); + ve = BytesAccess.newEvaluator(authContainer); } if (!ve.canAccess(cv)) { diff --git a/core/src/main/java/org/apache/accumulo/core/iterators/user/TransformingIterator.java b/core/src/main/java/org/apache/accumulo/core/iterators/user/TransformingIterator.java index c8915b1e528..c9aeb447a97 100644 --- a/core/src/main/java/org/apache/accumulo/core/iterators/user/TransformingIterator.java +++ b/core/src/main/java/org/apache/accumulo/core/iterators/user/TransformingIterator.java @@ -30,10 +30,9 @@ import java.util.Map.Entry; import java.util.NoSuchElementException; -import org.apache.accumulo.access.AccessEvaluator; -import org.apache.accumulo.access.AccessExpression; import org.apache.accumulo.access.InvalidAccessExpressionException; import org.apache.accumulo.core.client.IteratorSetting; +import org.apache.accumulo.core.clientImpl.access.BytesAccess; import org.apache.accumulo.core.conf.ConfigurationTypeHelper; import org.apache.accumulo.core.data.ByteSequence; import org.apache.accumulo.core.data.Key; @@ -102,7 +101,7 @@ public abstract class TransformingIterator extends WrappingIterator implements O protected Collection seekColumnFamilies; protected boolean seekColumnFamiliesInclusive; - private AccessEvaluator ve = null; + private BytesAccess.BytesEvaluator ve = null; private LRUMap visibleCache = null; private LRUMap parsedVisibilitiesCache = null; private long maxBufferSize; @@ -118,7 +117,7 @@ public void init(SortedKeyValueIterator source, Map op if (scanning) { String auths = options.get(AUTH_OPT); if (auths != null && !auths.isEmpty()) { - ve = AccessEvaluator.of(new Authorizations(auths.getBytes(UTF_8)).toAccessAuthorizations()); + ve = BytesAccess.newEvaluator(new Authorizations(auths.getBytes(UTF_8))); visibleCache = new LRUMap<>(100); } } @@ -412,7 +411,7 @@ protected boolean canSee(Key key) { Boolean parsed = parsedVisibilitiesCache.get(visibility); if (parsed == null) { try { - AccessExpression.validate(visibility.toArray()); + BytesAccess.validate(visibility.toArray()); parsedVisibilitiesCache.put(visibility, Boolean.TRUE); } catch (InvalidAccessExpressionException e) { log.error("Parse error after transformation : {}", visibility); diff --git a/core/src/main/java/org/apache/accumulo/core/iterators/user/VisibilityFilter.java b/core/src/main/java/org/apache/accumulo/core/iterators/user/VisibilityFilter.java index 21a8bba5868..92e5079ab65 100644 --- a/core/src/main/java/org/apache/accumulo/core/iterators/user/VisibilityFilter.java +++ b/core/src/main/java/org/apache/accumulo/core/iterators/user/VisibilityFilter.java @@ -23,10 +23,9 @@ import java.io.IOException; import java.util.Map; -import org.apache.accumulo.access.AccessEvaluator; -import org.apache.accumulo.access.AccessExpression; import org.apache.accumulo.access.InvalidAccessExpressionException; import org.apache.accumulo.core.client.IteratorSetting; +import org.apache.accumulo.core.clientImpl.access.BytesAccess; import org.apache.accumulo.core.data.ArrayByteSequence; import org.apache.accumulo.core.data.ByteSequence; import org.apache.accumulo.core.data.Key; @@ -45,7 +44,7 @@ */ public class VisibilityFilter extends Filter implements OptionDescriber { - private AccessEvaluator accessEvaluator; + private BytesAccess.BytesEvaluator accessEvaluator; protected Map cache; private final ArrayByteSequence testVis = new ArrayByteSequence(new byte[0]); @@ -68,7 +67,7 @@ public void init(SortedKeyValueIterator source, Map op Authorizations authObj = auths == null || auths.isEmpty() ? new Authorizations() : new Authorizations(auths.getBytes(UTF_8)); - this.accessEvaluator = AccessEvaluator.of(authObj.toAccessAuthorizations()); + this.accessEvaluator = BytesAccess.newEvaluator(authObj); } this.cache = new LRUMap<>(1000); } @@ -96,7 +95,7 @@ public boolean accept(Key k, Value v) { } final ArrayByteSequence copy = new ArrayByteSequence(testVis); try { - AccessExpression.validate(copy.toArray()); + BytesAccess.validate(copy.toArray()); // cache a copy of testVis cache.put(copy, true); return true; diff --git a/core/src/main/java/org/apache/accumulo/core/iteratorsImpl/system/VisibilityFilter.java b/core/src/main/java/org/apache/accumulo/core/iteratorsImpl/system/VisibilityFilter.java index 2f7167cdd97..64f5725f23c 100644 --- a/core/src/main/java/org/apache/accumulo/core/iteratorsImpl/system/VisibilityFilter.java +++ b/core/src/main/java/org/apache/accumulo/core/iteratorsImpl/system/VisibilityFilter.java @@ -18,8 +18,8 @@ */ package org.apache.accumulo.core.iteratorsImpl.system; -import org.apache.accumulo.access.AccessEvaluator; import org.apache.accumulo.access.InvalidAccessExpressionException; +import org.apache.accumulo.core.clientImpl.access.BytesAccess; import org.apache.accumulo.core.data.ArrayByteSequence; import org.apache.accumulo.core.data.ByteSequence; import org.apache.accumulo.core.data.Key; @@ -41,7 +41,7 @@ * class. */ public class VisibilityFilter extends SynchronizedServerFilter { - protected final AccessEvaluator ve; + protected final BytesAccess.BytesEvaluator ve; protected final ArrayByteSequence defaultVisibility; protected final LRUMap cache; protected final Authorizations authorizations; @@ -53,7 +53,7 @@ public class VisibilityFilter extends SynchronizedServerFilter { private VisibilityFilter(SortedKeyValueIterator iterator, Authorizations authorizations, byte[] defaultVisibility) { super(iterator); - this.ve = AccessEvaluator.of(authorizations.toAccessAuthorizations()); + this.ve = BytesAccess.newEvaluator(authorizations); this.authorizations = authorizations; this.defaultVisibility = new ArrayByteSequence(defaultVisibility); this.cache = new LRUMap<>(1000); diff --git a/core/src/main/java/org/apache/accumulo/core/security/Authorizations.java b/core/src/main/java/org/apache/accumulo/core/security/Authorizations.java index c9231ace390..cb577c42eaa 100644 --- a/core/src/main/java/org/apache/accumulo/core/security/Authorizations.java +++ b/core/src/main/java/org/apache/accumulo/core/security/Authorizations.java @@ -388,21 +388,4 @@ public String serialize() { return sb.toString(); } - - /** - * Converts to an Accumulo Access Authorizations object. - * - * @since 4.0.0 - */ - public org.apache.accumulo.access.Authorizations toAccessAuthorizations() { - if (auths.isEmpty()) { - return org.apache.accumulo.access.Authorizations.of(); - } else { - Set auths = new HashSet<>(authsList.size()); - for (var auth : authsList) { - auths.add(new String(auth, UTF_8)); - } - return org.apache.accumulo.access.Authorizations.of(auths); - } - } } diff --git a/core/src/main/java/org/apache/accumulo/core/security/ColumnVisibility.java b/core/src/main/java/org/apache/accumulo/core/security/ColumnVisibility.java index 33a377008c4..f63559938dd 100644 --- a/core/src/main/java/org/apache/accumulo/core/security/ColumnVisibility.java +++ b/core/src/main/java/org/apache/accumulo/core/security/ColumnVisibility.java @@ -29,8 +29,10 @@ import java.util.TreeSet; import java.util.function.Supplier; +import org.apache.accumulo.access.Access; import org.apache.accumulo.access.AccessExpression; import org.apache.accumulo.access.InvalidAccessExpressionException; +import org.apache.accumulo.core.clientImpl.access.BytesAccess; import org.apache.accumulo.core.data.ArrayByteSequence; import org.apache.accumulo.core.data.ByteSequence; import org.apache.accumulo.core.util.BadArgumentException; @@ -525,7 +527,7 @@ public ColumnVisibility(Text expression) { public ColumnVisibility(byte[] expression) { this.expression = expression; try { - AccessExpression.validate(this.expression); + BytesAccess.validate(this.expression); } catch (InvalidAccessExpressionException e) { // This is thrown for compatability with the exception this class used to throw when it parsed // exceptions itself. @@ -607,11 +609,11 @@ public Node getParseTree() { * * @param term term to quote * @return quoted term (unquoted if unnecessary) - * @deprecated use {@link AccessExpression#quote(String)} + * @deprecated use {@link Access#quote(String)} */ @Deprecated(since = "4.0.0") public static String quote(String term) { - return AccessExpression.quote(term); + return BytesAccess.quote(term); } /** @@ -621,10 +623,10 @@ public static String quote(String term) { * @param term term to quote, encoded as UTF-8 bytes * @return quoted term (unquoted if unnecessary), encoded as UTF-8 bytes * @see #quote(String) - * @deprecated use {@link AccessExpression#quote(byte[])} + * @deprecated use {@link Access#quote(String)} */ @Deprecated(since = "4.0.0") public static byte[] quote(byte[] term) { - return AccessExpression.quote(term); + return BytesAccess.quote(term); } } diff --git a/core/src/main/java/org/apache/accumulo/core/security/VisibilityEvaluator.java b/core/src/main/java/org/apache/accumulo/core/security/VisibilityEvaluator.java index 62512d19aa7..4b6fa7a2de1 100644 --- a/core/src/main/java/org/apache/accumulo/core/security/VisibilityEvaluator.java +++ b/core/src/main/java/org/apache/accumulo/core/security/VisibilityEvaluator.java @@ -18,9 +18,8 @@ */ package org.apache.accumulo.core.security; -import org.apache.accumulo.access.AccessEvaluator; import org.apache.accumulo.access.InvalidAccessExpressionException; -import org.apache.accumulo.core.data.ArrayByteSequence; +import org.apache.accumulo.core.clientImpl.access.BytesAccess; /** * A class which evaluates visibility expressions against a set of authorizations. @@ -29,7 +28,7 @@ */ @Deprecated(since = "4.0.0") public class VisibilityEvaluator { - private final AccessEvaluator accessEvaluator; + private final BytesAccess.BytesEvaluator accessEvaluator; /** * Properly escapes an authorization string. The string can be quoted if desired. @@ -74,8 +73,7 @@ public static byte[] escape(byte[] auth, boolean quote) { */ public VisibilityEvaluator(AuthorizationContainer authsContainer) { // TODO need to look into efficiency and correctness of this - this.accessEvaluator = - AccessEvaluator.of(auth -> authsContainer.contains(new ArrayByteSequence(auth))); + this.accessEvaluator = BytesAccess.newEvaluator(authsContainer); } /** @@ -85,7 +83,7 @@ public VisibilityEvaluator(AuthorizationContainer authsContainer) { * @param authorizations authorizations object */ public VisibilityEvaluator(Authorizations authorizations) { - this.accessEvaluator = AccessEvaluator.of(authorizations.toAccessAuthorizations()); + this.accessEvaluator = BytesAccess.newEvaluator(authorizations); } /** diff --git a/core/src/test/java/org/apache/accumulo/core/clientImpl/security/BytesAccessTest.java b/core/src/test/java/org/apache/accumulo/core/clientImpl/security/BytesAccessTest.java new file mode 100644 index 00000000000..a753a4c4e0b --- /dev/null +++ b/core/src/test/java/org/apache/accumulo/core/clientImpl/security/BytesAccessTest.java @@ -0,0 +1,91 @@ +/* + * 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.core.clientImpl.security; + +import static java.nio.charset.StandardCharsets.ISO_8859_1; +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.accumulo.core.clientImpl.access.BytesAccess; +import org.junit.jupiter.api.Test; + +public class BytesAccessTest { + @Test + public void testIso8859() { + // BytesAccess heavily depends on a 1:1 conversion of data from byte[] to string when using + // ISO_8859_1. This test validates those assumptions. + + byte[] data = new byte[256]; + for (int i = 0; i < 256; i++) { + data[i] = (byte) i; + } + + var str = new String(data, ISO_8859_1); + + assertEquals(256, str.length()); + var chars = str.toCharArray(); + assertEquals(256, chars.length); + for (int i = 0; i < 256; i++) { + assertEquals(i, str.charAt(i)); + assertEquals(i, chars[i]); + } + + byte[] data2 = str.getBytes(ISO_8859_1); + assertArrayEquals(data, data2); + } + + @Test + public void testQuote() { + byte[] data = new byte[256]; + for (int i = 0; i < 256; i++) { + data[i] = (byte) i; + } + + var quoted = BytesAccess.quote(data); + assertEquals(260, quoted.length); + assertEquals('"', quoted[0]); + assertEquals('"', quoted[259]); + int expected = 0; + for (int i = 1; i < 259; i++) { + if (quoted[i] == '\\') { + i++; + assertTrue('"' == quoted[i] || '\\' == quoted[i]); + } + assertEquals(expected, 0xff & quoted[i]); + expected++; + } + } + + @Test + public void testFindAuths() { + byte[] exp = new byte[] {'"', 0, 1, '"', '&', 'A', 'B', '&', '"', 3, 4, 5, '"'}; + + List seenAuths = new ArrayList<>(); + BytesAccess.findAuthorizations(exp, seenAuths::add); + + assertEquals(3, seenAuths.size()); + assertArrayEquals(new byte[] {0, 1}, seenAuths.get(0)); + assertArrayEquals(new byte[] {'A', 'B'}, seenAuths.get(1)); + assertArrayEquals(new byte[] {3, 4, 5}, seenAuths.get(2)); + } +} diff --git a/core/src/test/java/org/apache/accumulo/core/security/VisibilityEvaluatorTest.java b/core/src/test/java/org/apache/accumulo/core/security/VisibilityEvaluatorTest.java index 31ae8ea3ed4..aa9cb1a702d 100644 --- a/core/src/test/java/org/apache/accumulo/core/security/VisibilityEvaluatorTest.java +++ b/core/src/test/java/org/apache/accumulo/core/security/VisibilityEvaluatorTest.java @@ -23,6 +23,9 @@ import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; +import java.util.List; + +import org.apache.accumulo.core.data.ArrayByteSequence; import org.apache.accumulo.core.util.ByteArraySet; import org.junit.jupiter.api.Test; @@ -110,4 +113,39 @@ public void testNonAscii() throws VisibilityParseException { ct.evaluate(new ColumnVisibility(quote("五") + "&(" + quote("四") + "|" + quote("三") + ")"))); assertFalse(ct.evaluate(new ColumnVisibility("\"五\"&(\"四\"|\"三\")"))); } + + @Test + public void testBinary() throws VisibilityParseException { + for (int i = 0; i < 256; i++) { + if (i == '\\' || i == '"') { + // these have to be escaped + continue; + } + + byte b = (byte) i; + + byte[] exp1 = new byte[] {'"', b, 2, '"', '&', '"', (byte) 250, 6, '"'}; + byte[] exp2 = new byte[] {'"', b, 2, '"', '|', '"', (byte) 250, 6, '"'}; + + var auths1 = new Authorizations(List.of(new byte[] {b, 2}, new byte[] {(byte) 250, 6})); + VisibilityEvaluator ve1 = new VisibilityEvaluator(auths1); + assertTrue(ve1.evaluate(new ColumnVisibility(exp1))); + assertTrue(ve1.evaluate(new ColumnVisibility(exp2))); + + var auths2 = new Authorizations(List.of(new byte[] {b, 2})); + VisibilityEvaluator ve2 = new VisibilityEvaluator(auths2); + assertFalse(ve2.evaluate(new ColumnVisibility(exp1))); + assertTrue(ve2.evaluate(new ColumnVisibility(exp2))); + + var auths3 = new Authorizations(List.of(new byte[] {b, 2, 0})); + VisibilityEvaluator ve3 = new VisibilityEvaluator(auths3); + assertFalse(ve3.evaluate(new ColumnVisibility(exp1))); + assertFalse(ve3.evaluate(new ColumnVisibility(exp2))); + + var bs = new ArrayByteSequence(new byte[] {b, 2}); + VisibilityEvaluator ve4 = new VisibilityEvaluator(auth -> auth.equals(bs)); + assertFalse(ve4.evaluate(new ColumnVisibility(exp1))); + assertTrue(ve4.evaluate(new ColumnVisibility(exp2))); + } + } }