diff --git a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/fs/permission/AclEntry.java b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/fs/permission/AclEntry.java
index 2cb2250ccbc..3d57e06d471 100644
--- a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/fs/permission/AclEntry.java
+++ b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/fs/permission/AclEntry.java
@@ -18,11 +18,8 @@
package org.apache.hadoop.fs.permission;
import static org.apache.hadoop.fs.permission.AclEntryScope.*;
-import static org.apache.hadoop.fs.permission.AclEntryType.*;
import com.google.common.base.Objects;
-import com.google.common.collect.ComparisonChain;
-import com.google.common.collect.Ordering;
import org.apache.hadoop.classification.InterfaceAudience;
import org.apache.hadoop.classification.InterfaceStability;
@@ -31,20 +28,12 @@
* Defines a single entry in an ACL. An ACL entry has a type (user, group,
* mask, or other), an optional name (referring to a specific user or group), a
* set of permissions (any combination of read, write and execute), and a scope
- * (access or default). The natural ordering for entries within an ACL is:
- *
- * - owner entry (unnamed user)
- * - all named user entries (internal ordering undefined)
- * - owning group entry (unnamed group)
- * - all named group entries (internal ordering undefined)
- * - other entry
- *
- * All access ACL entries sort ahead of all default ACL entries. AclEntry
- * instances are immutable. Use a {@link Builder} to create a new instance.
+ * (access or default). AclEntry instances are immutable. Use a {@link Builder}
+ * to create a new instance.
*/
@InterfaceAudience.Public
@InterfaceStability.Evolving
-public class AclEntry implements Comparable {
+public class AclEntry {
private final AclEntryType type;
private final String name;
private final FsAction permission;
@@ -106,15 +95,6 @@ public int hashCode() {
return Objects.hashCode(type, name, permission, scope);
}
- @Override
- public int compareTo(AclEntry other) {
- return ComparisonChain.start()
- .compare(scope, other.scope, Ordering.explicit(ACCESS, DEFAULT))
- .compare(type, other.type, Ordering.explicit(USER, GROUP, MASK, OTHER))
- .compare(name, other.name, Ordering.natural().nullsFirst())
- .result();
- }
-
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
diff --git a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/fs/permission/AclStatus.java b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/fs/permission/AclStatus.java
index c881b0faab8..4a7258f0a27 100644
--- a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/fs/permission/AclStatus.java
+++ b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/fs/permission/AclStatus.java
@@ -17,7 +17,6 @@
*/
package org.apache.hadoop.fs.permission;
-import java.util.Collections;
import java.util.List;
import org.apache.hadoop.classification.InterfaceAudience;
@@ -197,8 +196,6 @@ private AclStatus(String owner, String group, boolean stickyBit,
this.owner = owner;
this.group = group;
this.stickyBit = stickyBit;
- List entriesCopy = Lists.newArrayList(entries);
- Collections.sort(entriesCopy);
- this.entries = entriesCopy;
+ this.entries = Lists.newArrayList(entries);
}
}
diff --git a/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/fs/permission/TestAcl.java b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/fs/permission/TestAcl.java
index 94db270f984..f33da8aa8be 100644
--- a/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/fs/permission/TestAcl.java
+++ b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/fs/permission/TestAcl.java
@@ -19,13 +19,9 @@
import static org.junit.Assert.*;
-import java.util.List;
-
import org.junit.BeforeClass;
import org.junit.Test;
-import com.google.common.collect.Lists;
-
/**
* Tests covering basic functionality of the ACL objects.
*/
@@ -165,35 +161,6 @@ public void testEntryHashCode() {
assertFalse(ENTRY3.hashCode() == ENTRY4.hashCode());
}
- @Test
- public void testEntryNaturalOrdering() {
- AclEntry expected[] = new AclEntry[] {
- ENTRY5, // owner
- ENTRY1, // named user
- ENTRY11, // group
- ENTRY3, // named group
- ENTRY9, // mask
- ENTRY7, // other
- ENTRY13, // default owner
- ENTRY8, // default named user
- ENTRY12, // default group
- ENTRY6, // default named group
- ENTRY10, // default mask
- ENTRY4 // default other
- };
- List actual = Lists.newArrayList(STATUS4.getEntries());
- assertNotNull(actual);
- assertEquals(expected.length, actual.size());
- for (int i = 0; i < expected.length; ++i) {
- AclEntry expectedEntry = expected[i];
- AclEntry actualEntry = actual.get(i);
- assertEquals(
- String.format("At position %d, expected = %s, actual = %s", i,
- expectedEntry, actualEntry),
- expectedEntry, actualEntry);
- }
- }
-
@Test
public void testEntryScopeIsAccessIfUnspecified() {
assertEquals(AclEntryScope.ACCESS, ENTRY1.getScope());
diff --git a/hadoop-hdfs-project/hadoop-hdfs/CHANGES-HDFS-4685.txt b/hadoop-hdfs-project/hadoop-hdfs/CHANGES-HDFS-4685.txt
index 7b07ed58e6a..77d4c387167 100644
--- a/hadoop-hdfs-project/hadoop-hdfs/CHANGES-HDFS-4685.txt
+++ b/hadoop-hdfs-project/hadoop-hdfs/CHANGES-HDFS-4685.txt
@@ -26,6 +26,8 @@ HDFS-4685 (Unreleased)
HADOOP-10192. FileSystem#getAclStatus has incorrect JavaDocs. (cnauroth)
+ HDFS-5673. Implement logic for modification of ACLs. (cnauroth)
+
OPTIMIZATIONS
BUG FIXES
diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/protocol/AclException.java b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/protocol/AclException.java
new file mode 100644
index 00000000000..12109999d8d
--- /dev/null
+++ b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/protocol/AclException.java
@@ -0,0 +1,39 @@
+/**
+ * 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
+ *
+ * http://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.hadoop.hdfs.protocol;
+
+import java.io.IOException;
+
+import org.apache.hadoop.classification.InterfaceAudience;
+
+/**
+ * Indicates a failure manipulating an ACL.
+ */
+@InterfaceAudience.Private
+public class AclException extends IOException {
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * Creates a new AclException.
+ *
+ * @param message String message
+ */
+ public AclException(String message) {
+ super(message);
+ }
+}
diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/AclTransformation.java b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/AclTransformation.java
new file mode 100644
index 00000000000..06e95ff2beb
--- /dev/null
+++ b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/AclTransformation.java
@@ -0,0 +1,480 @@
+/**
+ * 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
+ *
+ * http://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.hadoop.hdfs.server.namenode;
+
+import static org.apache.hadoop.fs.permission.AclEntryScope.*;
+import static org.apache.hadoop.fs.permission.AclEntryType.*;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.EnumMap;
+import java.util.EnumSet;
+import java.util.Iterator;
+import java.util.List;
+
+import com.google.common.base.Objects;
+import com.google.common.collect.ComparisonChain;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Ordering;
+
+import org.apache.hadoop.classification.InterfaceAudience;
+import org.apache.hadoop.fs.permission.AclEntry;
+import org.apache.hadoop.fs.permission.AclEntryScope;
+import org.apache.hadoop.fs.permission.AclEntryType;
+import org.apache.hadoop.fs.permission.FsAction;
+import org.apache.hadoop.fs.permission.FsPermission;
+import org.apache.hadoop.hdfs.protocol.AclException;
+
+/**
+ * AclTransformation defines the operations that can modify an ACL. All ACL
+ * modifications take as input an existing ACL and apply logic to add new
+ * entries, modify existing entries or remove old entries. Some operations also
+ * accept an ACL spec: a list of entries that further describes the requested
+ * change. Different operations interpret the ACL spec differently. In the
+ * case of adding an ACL to an inode that previously did not have one, the
+ * existing ACL can be a "minimal ACL" containing exactly 3 entries for owner,
+ * group and other, all derived from the {@link FsPermission} bits.
+ *
+ * The algorithms implemented here require sorted lists of ACL entries. For any
+ * existing ACL, it is assumed that the entries are sorted. This is because all
+ * ACL creation and modification is intended to go through these methods, and
+ * they all guarantee correct sort order in their outputs. However, an ACL spec
+ * is considered untrusted user input, so all operations pre-sort the ACL spec as
+ * the first step.
+ */
+@InterfaceAudience.Private
+final class AclTransformation {
+ private static final int MAX_ENTRIES = 32;
+
+ /**
+ * Filters (discards) any existing ACL entries that have the same scope, type
+ * and name of any entry in the ACL spec. If necessary, recalculates the mask
+ * entries. If necessary, default entries may be inferred by copying the
+ * permissions of the corresponding access entries. It is invalid to request
+ * removal of the mask entry from an ACL that would otherwise require a mask
+ * entry, due to existing named entries or an unnamed group entry.
+ *
+ * @param existingAcl List existing ACL
+ * @param inAclSpec List ACL spec describing entries to filter
+ * @return List new ACL
+ * @throws AclException if validation fails
+ */
+ public static List filterAclEntriesByAclSpec(
+ List existingAcl, List inAclSpec) throws AclException {
+ ValidatedAclSpec aclSpec = new ValidatedAclSpec(inAclSpec);
+ ArrayList aclBuilder = Lists.newArrayListWithCapacity(MAX_ENTRIES);
+ EnumMap providedMask =
+ Maps.newEnumMap(AclEntryScope.class);
+ EnumSet maskDirty = EnumSet.noneOf(AclEntryScope.class);
+ EnumSet scopeDirty = EnumSet.noneOf(AclEntryScope.class);
+ for (AclEntry existingEntry: existingAcl) {
+ if (aclSpec.containsKey(existingEntry)) {
+ scopeDirty.add(existingEntry.getScope());
+ if (existingEntry.getType() == MASK) {
+ maskDirty.add(existingEntry.getScope());
+ }
+ } else {
+ if (existingEntry.getType() == MASK) {
+ providedMask.put(existingEntry.getScope(), existingEntry);
+ } else {
+ aclBuilder.add(existingEntry);
+ }
+ }
+ }
+ copyDefaultsIfNeeded(aclBuilder);
+ calculateMasks(aclBuilder, providedMask, maskDirty, scopeDirty);
+ return buildAndValidateAcl(aclBuilder);
+ }
+
+ /**
+ * Filters (discards) any existing default ACL entries. The new ACL retains
+ * only the access ACL entries.
+ *
+ * @param existingAcl List existing ACL
+ * @return List new ACL
+ * @throws AclException if validation fails
+ */
+ public static List filterDefaultAclEntries(
+ List existingAcl) throws AclException {
+ ArrayList aclBuilder = Lists.newArrayListWithCapacity(MAX_ENTRIES);
+ for (AclEntry existingEntry: existingAcl) {
+ if (existingEntry.getScope() == DEFAULT) {
+ // Default entries sort after access entries, so we can exit early.
+ break;
+ }
+ aclBuilder.add(existingEntry);
+ }
+ return buildAndValidateAcl(aclBuilder);
+ }
+
+ /**
+ * Merges the entries of the ACL spec into the existing ACL. If necessary,
+ * recalculates the mask entries. If necessary, default entries may be
+ * inferred by copying the permissions of the corresponding access entries.
+ *
+ * @param existingAcl List existing ACL
+ * @param inAclSpec List ACL spec containing entries to merge
+ * @return List new ACL
+ * @throws AclException if validation fails
+ */
+ public static List mergeAclEntries(List existingAcl,
+ List inAclSpec) throws AclException {
+ ValidatedAclSpec aclSpec = new ValidatedAclSpec(inAclSpec);
+ ArrayList aclBuilder = Lists.newArrayListWithCapacity(MAX_ENTRIES);
+ List foundAclSpecEntries =
+ Lists.newArrayListWithCapacity(MAX_ENTRIES);
+ EnumMap providedMask =
+ Maps.newEnumMap(AclEntryScope.class);
+ EnumSet maskDirty = EnumSet.noneOf(AclEntryScope.class);
+ EnumSet scopeDirty = EnumSet.noneOf(AclEntryScope.class);
+ for (AclEntry existingEntry: existingAcl) {
+ AclEntry aclSpecEntry = aclSpec.findByKey(existingEntry);
+ if (aclSpecEntry != null) {
+ foundAclSpecEntries.add(aclSpecEntry);
+ scopeDirty.add(aclSpecEntry.getScope());
+ if (aclSpecEntry.getType() == MASK) {
+ providedMask.put(aclSpecEntry.getScope(), aclSpecEntry);
+ maskDirty.add(aclSpecEntry.getScope());
+ } else {
+ aclBuilder.add(aclSpecEntry);
+ }
+ } else {
+ if (existingEntry.getType() == MASK) {
+ providedMask.put(existingEntry.getScope(), existingEntry);
+ } else {
+ aclBuilder.add(existingEntry);
+ }
+ }
+ }
+ // ACL spec entries that were not replacements are new additions.
+ for (AclEntry newEntry: aclSpec) {
+ if (Collections.binarySearch(foundAclSpecEntries, newEntry,
+ ACL_ENTRY_COMPARATOR) < 0) {
+ scopeDirty.add(newEntry.getScope());
+ if (newEntry.getType() == MASK) {
+ providedMask.put(newEntry.getScope(), newEntry);
+ maskDirty.add(newEntry.getScope());
+ } else {
+ aclBuilder.add(newEntry);
+ }
+ }
+ }
+ copyDefaultsIfNeeded(aclBuilder);
+ calculateMasks(aclBuilder, providedMask, maskDirty, scopeDirty);
+ return buildAndValidateAcl(aclBuilder);
+ }
+
+ /**
+ * Completely replaces the ACL with the entries of the ACL spec. If
+ * necessary, recalculates the mask entries. If necessary, default entries
+ * are inferred by copying the permissions of the corresponding access
+ * entries. Replacement occurs separately for each of the access ACL and the
+ * default ACL. If the ACL spec contains only access entries, then the
+ * existing default entries are retained. If the ACL spec contains only
+ * default entries, then the existing access entries are retained. If the ACL
+ * spec contains both access and default entries, then both are replaced.
+ *
+ * @param existingAcl List existing ACL
+ * @param inAclSpec List ACL spec containing replacement entries
+ * @return List new ACL
+ * @throws AclException if validation fails
+ */
+ public static List replaceAclEntries(List existingAcl,
+ List inAclSpec) throws AclException {
+ ValidatedAclSpec aclSpec = new ValidatedAclSpec(inAclSpec);
+ ArrayList aclBuilder = Lists.newArrayListWithCapacity(MAX_ENTRIES);
+ // Replacement is done separately for each scope: access and default.
+ EnumMap providedMask =
+ Maps.newEnumMap(AclEntryScope.class);
+ EnumSet maskDirty = EnumSet.noneOf(AclEntryScope.class);
+ EnumSet scopeDirty = EnumSet.noneOf(AclEntryScope.class);
+ for (AclEntry aclSpecEntry: aclSpec) {
+ scopeDirty.add(aclSpecEntry.getScope());
+ if (aclSpecEntry.getType() == MASK) {
+ providedMask.put(aclSpecEntry.getScope(), aclSpecEntry);
+ maskDirty.add(aclSpecEntry.getScope());
+ } else {
+ aclBuilder.add(aclSpecEntry);
+ }
+ }
+ // Copy existing entries if the scope was not replaced.
+ for (AclEntry existingEntry: existingAcl) {
+ if (!scopeDirty.contains(existingEntry.getScope())) {
+ if (existingEntry.getType() == MASK) {
+ providedMask.put(existingEntry.getScope(), existingEntry);
+ } else {
+ aclBuilder.add(existingEntry);
+ }
+ }
+ }
+ copyDefaultsIfNeeded(aclBuilder);
+ calculateMasks(aclBuilder, providedMask, maskDirty, scopeDirty);
+ return buildAndValidateAcl(aclBuilder);
+ }
+
+ /**
+ * There is no reason to instantiate this class.
+ */
+ private AclTransformation() {
+ }
+
+ /**
+ * Comparator that enforces required ordering for entries within an ACL:
+ * -owner entry (unnamed user)
+ * -all named user entries (internal ordering undefined)
+ * -owning group entry (unnamed group)
+ * -all named group entries (internal ordering undefined)
+ * -mask entry
+ * -other entry
+ * All access ACL entries sort ahead of all default ACL entries.
+ */
+ private static final Comparator ACL_ENTRY_COMPARATOR =
+ new Comparator() {
+ @Override
+ public int compare(AclEntry entry1, AclEntry entry2) {
+ return ComparisonChain.start()
+ .compare(entry1.getScope(), entry2.getScope(),
+ Ordering.explicit(ACCESS, DEFAULT))
+ .compare(entry1.getType(), entry2.getType(),
+ Ordering.explicit(USER, GROUP, MASK, OTHER))
+ .compare(entry1.getName(), entry2.getName(),
+ Ordering.natural().nullsFirst())
+ .result();
+ }
+ };
+
+ /**
+ * Builds the final list of ACL entries to return by trimming, sorting and
+ * validating the ACL entries that have been added.
+ *
+ * @param aclBuilder ArrayList containing entries to build
+ * @return List unmodifiable, sorted list of ACL entries
+ * @throws AclException if validation fails
+ */
+ private static List buildAndValidateAcl(
+ ArrayList aclBuilder) throws AclException {
+ if (aclBuilder.size() > MAX_ENTRIES) {
+ throw new AclException("Invalid ACL: ACL has " + aclBuilder.size() +
+ " entries, which exceeds maximum of " + MAX_ENTRIES + ".");
+ }
+ aclBuilder.trimToSize();
+ Collections.sort(aclBuilder, ACL_ENTRY_COMPARATOR);
+ AclEntry userEntry = null, groupEntry = null, otherEntry = null;
+ AclEntry prevEntry = null;
+ for (AclEntry entry: aclBuilder) {
+ if (prevEntry != null &&
+ ACL_ENTRY_COMPARATOR.compare(prevEntry, entry) == 0) {
+ throw new AclException(
+ "Invalid ACL: multiple entries with same scope, type and name.");
+ }
+ if (entry.getName() != null && (entry.getType() == MASK ||
+ entry.getType() == OTHER)) {
+ throw new AclException(
+ "Invalid ACL: this entry type must not have a name: " + entry + ".");
+ }
+ if (entry.getScope() == ACCESS) {
+ if (entry.getType() == USER && entry.getName() == null) {
+ userEntry = entry;
+ }
+ if (entry.getType() == GROUP && entry.getName() == null) {
+ groupEntry = entry;
+ }
+ if (entry.getType() == OTHER && entry.getName() == null) {
+ otherEntry = entry;
+ }
+ }
+ prevEntry = entry;
+ }
+ if (userEntry == null || groupEntry == null || otherEntry == null) {
+ throw new AclException(
+ "Invalid ACL: the user, group and other entries are required.");
+ }
+ return Collections.unmodifiableList(aclBuilder);
+ }
+
+ /**
+ * Calculates mask entries required for the ACL. Mask calculation is performed
+ * separately for each scope: access and default. This method is responsible
+ * for handling the following cases of mask calculation:
+ * 1. Throws an exception if the caller attempts to remove the mask entry of an
+ * existing ACL that requires it. If the ACL has any named entries, then a
+ * mask entry is required.
+ * 2. If the caller supplied a mask in the ACL spec, use it.
+ * 3. If the caller did not supply a mask, but there are ACL entry changes in
+ * this scope, then automatically calculate a new mask. The permissions of
+ * the new mask are the union of the permissions on the group entry and all
+ * named entries.
+ *
+ * @param aclBuilder ArrayList containing entries to build
+ * @param providedMask EnumMap mapping each scope to
+ * the mask entry that was provided for that scope (if provided)
+ * @param maskDirty EnumSet which contains a scope if the mask
+ * entry is dirty (added or deleted) in that scope
+ * @param scopeDirty EnumSet which contains a scope if any entry
+ * is dirty (added or deleted) in that scope
+ * @throws AclException if validation fails
+ */
+ private static void calculateMasks(List aclBuilder,
+ EnumMap providedMask,
+ EnumSet maskDirty, EnumSet scopeDirty)
+ throws AclException {
+ EnumSet scopeFound = EnumSet.noneOf(AclEntryScope.class);
+ EnumMap unionPerms =
+ Maps.newEnumMap(AclEntryScope.class);
+ EnumSet maskNeeded = EnumSet.noneOf(AclEntryScope.class);
+ // Determine which scopes are present, which scopes need a mask, and the
+ // union of group class permissions in each scope.
+ for (AclEntry entry: aclBuilder) {
+ scopeFound.add(entry.getScope());
+ if (entry.getType() == GROUP || entry.getName() != null) {
+ FsAction scopeUnionPerms = Objects.firstNonNull(
+ unionPerms.get(entry.getScope()), FsAction.NONE);
+ unionPerms.put(entry.getScope(),
+ scopeUnionPerms.or(entry.getPermission()));
+ }
+ if (entry.getName() != null) {
+ maskNeeded.add(entry.getScope());
+ }
+ }
+ // Add mask entry if needed in each scope.
+ for (AclEntryScope scope: scopeFound) {
+ if (!providedMask.containsKey(scope) && maskNeeded.contains(scope) &&
+ maskDirty.contains(scope)) {
+ throw new AclException(
+ "Invalid ACL: mask is required, but it was deleted.");
+ } else if (providedMask.containsKey(scope) &&
+ (!scopeDirty.contains(scope) || maskDirty.contains(scope))) {
+ aclBuilder.add(providedMask.get(scope));
+ } else if (maskNeeded.contains(scope)) {
+ aclBuilder.add(new AclEntry.Builder()
+ .setScope(scope)
+ .setType(MASK)
+ .setPermission(unionPerms.get(scope))
+ .build());
+ }
+ }
+ }
+
+ /**
+ * Adds unspecified default entries by copying permissions from the
+ * corresponding access entries.
+ *
+ * @param aclBuilder ArrayList containing entries to build
+ */
+ private static void copyDefaultsIfNeeded(List aclBuilder) {
+ int pivot = -1;
+ for (int i = 0; i < aclBuilder.size(); ++i) {
+ if (aclBuilder.get(i).getScope() == DEFAULT) {
+ pivot = i;
+ break;
+ }
+ }
+ if (pivot > -1) {
+ List accessEntries = aclBuilder.subList(0, pivot);
+ List defaultEntries = aclBuilder.subList(pivot,
+ aclBuilder.size());
+ List copiedEntries = Lists.newArrayListWithCapacity(3);
+ for (AclEntryType type: EnumSet.of(USER, GROUP, OTHER)) {
+ AclEntry defaultEntryKey = new AclEntry.Builder().setScope(DEFAULT)
+ .setType(type).build();
+ int defaultEntryIndex = Collections.binarySearch(defaultEntries,
+ defaultEntryKey, ACL_ENTRY_COMPARATOR);
+ if (defaultEntryIndex < 0) {
+ AclEntry accessEntryKey = new AclEntry.Builder().setScope(ACCESS)
+ .setType(type).build();
+ int accessEntryIndex = Collections.binarySearch(accessEntries,
+ accessEntryKey, ACL_ENTRY_COMPARATOR);
+ if (accessEntryIndex >= 0) {
+ copiedEntries.add(new AclEntry.Builder()
+ .setScope(DEFAULT)
+ .setType(type)
+ .setPermission(accessEntries.get(accessEntryIndex).getPermission())
+ .build());
+ }
+ }
+ }
+ // Add all copied entries when done to prevent potential issues with binary
+ // search on a modified aclBulider during the main loop.
+ aclBuilder.addAll(copiedEntries);
+ }
+ }
+
+ /**
+ * An ACL spec that has been pre-validated and sorted.
+ */
+ private static final class ValidatedAclSpec implements Iterable {
+ private final List aclSpec;
+
+ /**
+ * Creates a ValidatedAclSpec by pre-validating and sorting the given ACL
+ * entries. Pre-validation checks that it does not exceed the maximum
+ * entries. This check is performed before modifying the ACL, and it's
+ * actually insufficient for enforcing the maximum number of entries.
+ * Transformation logic can create additional entries automatically,such as
+ * the mask and some of the default entries, so we also need additional
+ * checks during transformation. The up-front check is still valuable here
+ * so that we don't run a lot of expensive transformation logic while
+ * holding the namesystem lock for an attacker who intentionally sent a huge
+ * ACL spec.
+ *
+ * @param aclSpec List containing unvalidated input ACL spec
+ * @throws AclException if validation fails
+ */
+ public ValidatedAclSpec(List aclSpec) throws AclException {
+ if (aclSpec.size() > MAX_ENTRIES) {
+ throw new AclException("Invalid ACL: ACL spec has " + aclSpec.size() +
+ " entries, which exceeds maximum of " + MAX_ENTRIES + ".");
+ }
+ Collections.sort(aclSpec, ACL_ENTRY_COMPARATOR);
+ this.aclSpec = aclSpec;
+ }
+
+ /**
+ * Returns true if this contains an entry matching the given key. An ACL
+ * entry's key consists of scope, type and name (but not permission).
+ *
+ * @param key AclEntry search key
+ * @return boolean true if found
+ */
+ public boolean containsKey(AclEntry key) {
+ return Collections.binarySearch(aclSpec, key, ACL_ENTRY_COMPARATOR) >= 0;
+ }
+
+ /**
+ * Returns the entry matching the given key or null if not found. An ACL
+ * entry's key consists of scope, type and name (but not permission).
+ *
+ * @param key AclEntry search key
+ * @return AclEntry entry matching the given key or null if not found
+ */
+ public AclEntry findByKey(AclEntry key) {
+ int index = Collections.binarySearch(aclSpec, key, ACL_ENTRY_COMPARATOR);
+ if (index >= 0) {
+ return aclSpec.get(index);
+ }
+ return null;
+ }
+
+ @Override
+ public Iterator iterator() {
+ return aclSpec.iterator();
+ }
+ }
+}
diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/namenode/TestAclTransformation.java b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/namenode/TestAclTransformation.java
new file mode 100644
index 00000000000..26ce1236da0
--- /dev/null
+++ b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/namenode/TestAclTransformation.java
@@ -0,0 +1,1220 @@
+/**
+ * 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
+ *
+ * http://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.hadoop.hdfs.server.namenode;
+
+import static org.apache.hadoop.fs.permission.AclEntryScope.*;
+import static org.apache.hadoop.fs.permission.AclEntryType.*;
+import static org.apache.hadoop.fs.permission.FsAction.*;
+import static org.apache.hadoop.hdfs.server.namenode.AclTransformation.*;
+import static org.junit.Assert.*;
+
+import java.util.List;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
+import org.junit.Test;
+
+import org.apache.hadoop.fs.permission.AclEntry;
+import org.apache.hadoop.fs.permission.AclEntryScope;
+import org.apache.hadoop.fs.permission.AclEntryType;
+import org.apache.hadoop.fs.permission.FsAction;
+import org.apache.hadoop.hdfs.protocol.AclException;
+import org.apache.hadoop.hdfs.server.namenode.AclTransformation;
+
+/**
+ * Tests operations that modify ACLs. All tests in this suite have been
+ * cross-validated against Linux setfacl/getfacl to check for consistency of the
+ * HDFS implementation.
+ */
+public class TestAclTransformation {
+
+ private static final List ACL_SPEC_TOO_LARGE;
+ static {
+ ACL_SPEC_TOO_LARGE = Lists.newArrayListWithCapacity(33);
+ for (int i = 0; i < 33; ++i) {
+ ACL_SPEC_TOO_LARGE.add(aclEntry(ACCESS, USER, "user" + i, ALL));
+ }
+ }
+
+ @Test
+ public void testFilterAclEntriesByAclSpec() throws AclException {
+ List existing = new ImmutableList.Builder()
+ .add(aclEntry(ACCESS, USER, ALL))
+ .add(aclEntry(ACCESS, USER, "bruce", READ_WRITE))
+ .add(aclEntry(ACCESS, USER, "diana", READ_EXECUTE))
+ .add(aclEntry(ACCESS, GROUP, READ))
+ .add(aclEntry(ACCESS, GROUP, "sales", READ_EXECUTE))
+ .add(aclEntry(ACCESS, GROUP, "execs", READ_WRITE))
+ .add(aclEntry(ACCESS, MASK, ALL))
+ .add(aclEntry(ACCESS, OTHER, READ))
+ .build();
+ List aclSpec = Lists.newArrayList(
+ aclEntry(ACCESS, USER, "diana"),
+ aclEntry(ACCESS, GROUP, "sales"));
+ List expected = new ImmutableList.Builder()
+ .add(aclEntry(ACCESS, USER, ALL))
+ .add(aclEntry(ACCESS, USER, "bruce", READ_WRITE))
+ .add(aclEntry(ACCESS, GROUP, READ))
+ .add(aclEntry(ACCESS, GROUP, "execs", READ_WRITE))
+ .add(aclEntry(ACCESS, MASK, READ_WRITE))
+ .add(aclEntry(ACCESS, OTHER, READ))
+ .build();
+ assertEquals(expected, filterAclEntriesByAclSpec(existing, aclSpec));
+ }
+
+ @Test
+ public void testFilterAclEntriesByAclSpecUnchanged() throws AclException {
+ List existing = new ImmutableList.Builder()
+ .add(aclEntry(ACCESS, USER, ALL))
+ .add(aclEntry(ACCESS, USER, "bruce", ALL))
+ .add(aclEntry(ACCESS, GROUP, READ_EXECUTE))
+ .add(aclEntry(ACCESS, GROUP, "sales", ALL))
+ .add(aclEntry(ACCESS, MASK, ALL))
+ .add(aclEntry(ACCESS, OTHER, NONE))
+ .build();
+ List aclSpec = Lists.newArrayList(
+ aclEntry(ACCESS, USER, "clark"),
+ aclEntry(ACCESS, GROUP, "execs"));
+ assertEquals(existing, filterAclEntriesByAclSpec(existing, aclSpec));
+ }
+
+ @Test
+ public void testFilterAclEntriesByAclSpecAccessMaskCalculated()
+ throws AclException {
+ List existing = new ImmutableList.Builder()
+ .add(aclEntry(ACCESS, USER, ALL))
+ .add(aclEntry(ACCESS, USER, "bruce", READ))
+ .add(aclEntry(ACCESS, USER, "diana", READ_WRITE))
+ .add(aclEntry(ACCESS, GROUP, READ))
+ .add(aclEntry(ACCESS, MASK, READ_WRITE))
+ .add(aclEntry(ACCESS, OTHER, READ))
+ .build();
+ List aclSpec = Lists.newArrayList(
+ aclEntry(ACCESS, USER, "diana"));
+ List expected = new ImmutableList.Builder()
+ .add(aclEntry(ACCESS, USER, ALL))
+ .add(aclEntry(ACCESS, USER, "bruce", READ))
+ .add(aclEntry(ACCESS, GROUP, READ))
+ .add(aclEntry(ACCESS, MASK, READ))
+ .add(aclEntry(ACCESS, OTHER, READ))
+ .build();
+ assertEquals(expected, filterAclEntriesByAclSpec(existing, aclSpec));
+ }
+
+ @Test
+ public void testFilterAclEntriesByAclSpecDefaultMaskCalculated()
+ throws AclException {
+ List existing = new ImmutableList.Builder()
+ .add(aclEntry(ACCESS, USER, ALL))
+ .add(aclEntry(ACCESS, GROUP, READ))
+ .add(aclEntry(ACCESS, OTHER, READ))
+ .add(aclEntry(DEFAULT, USER, ALL))
+ .add(aclEntry(DEFAULT, USER, "bruce", READ))
+ .add(aclEntry(DEFAULT, USER, "diana", READ_WRITE))
+ .add(aclEntry(DEFAULT, GROUP, READ))
+ .add(aclEntry(DEFAULT, MASK, READ_WRITE))
+ .add(aclEntry(DEFAULT, OTHER, NONE))
+ .build();
+ List aclSpec = Lists.newArrayList(
+ aclEntry(DEFAULT, USER, "diana"));
+ List expected = new ImmutableList.Builder()
+ .add(aclEntry(ACCESS, USER, ALL))
+ .add(aclEntry(ACCESS, GROUP, READ))
+ .add(aclEntry(ACCESS, OTHER, READ))
+ .add(aclEntry(DEFAULT, USER, ALL))
+ .add(aclEntry(DEFAULT, USER, "bruce", READ))
+ .add(aclEntry(DEFAULT, GROUP, READ))
+ .add(aclEntry(DEFAULT, MASK, READ))
+ .add(aclEntry(DEFAULT, OTHER, NONE))
+ .build();
+ assertEquals(expected, filterAclEntriesByAclSpec(existing, aclSpec));
+ }
+
+ @Test
+ public void testFilterAclEntriesByAclSpecDefaultMaskPreserved()
+ throws AclException {
+ List existing = new ImmutableList.Builder()
+ .add(aclEntry(ACCESS, USER, ALL))
+ .add(aclEntry(ACCESS, USER, "bruce", READ))
+ .add(aclEntry(ACCESS, USER, "diana", READ_WRITE))
+ .add(aclEntry(ACCESS, GROUP, READ))
+ .add(aclEntry(ACCESS, MASK, READ_WRITE))
+ .add(aclEntry(ACCESS, OTHER, READ))
+ .add(aclEntry(DEFAULT, USER, ALL))
+ .add(aclEntry(DEFAULT, USER, "diana", ALL))
+ .add(aclEntry(DEFAULT, GROUP, READ))
+ .add(aclEntry(DEFAULT, MASK, READ))
+ .add(aclEntry(DEFAULT, OTHER, NONE))
+ .build();
+ List aclSpec = Lists.newArrayList(
+ aclEntry(ACCESS, USER, "diana"));
+ List expected = new ImmutableList.Builder()
+ .add(aclEntry(ACCESS, USER, ALL))
+ .add(aclEntry(ACCESS, USER, "bruce", READ))
+ .add(aclEntry(ACCESS, GROUP, READ))
+ .add(aclEntry(ACCESS, MASK, READ))
+ .add(aclEntry(ACCESS, OTHER, READ))
+ .add(aclEntry(DEFAULT, USER, ALL))
+ .add(aclEntry(DEFAULT, USER, "diana", ALL))
+ .add(aclEntry(DEFAULT, GROUP, READ))
+ .add(aclEntry(DEFAULT, MASK, READ))
+ .add(aclEntry(DEFAULT, OTHER, NONE))
+ .build();
+ assertEquals(expected, filterAclEntriesByAclSpec(existing, aclSpec));
+ }
+
+ @Test
+ public void testFilterAclEntriesByAclSpecAccessMaskPreserved()
+ throws AclException {
+ List existing = new ImmutableList.Builder()
+ .add(aclEntry(ACCESS, USER, ALL))
+ .add(aclEntry(ACCESS, USER, "bruce", READ))
+ .add(aclEntry(ACCESS, USER, "diana", READ_WRITE))
+ .add(aclEntry(ACCESS, GROUP, READ))
+ .add(aclEntry(ACCESS, MASK, READ))
+ .add(aclEntry(ACCESS, OTHER, READ))
+ .add(aclEntry(DEFAULT, USER, ALL))
+ .add(aclEntry(DEFAULT, USER, "bruce", READ))
+ .add(aclEntry(DEFAULT, USER, "diana", READ_WRITE))
+ .add(aclEntry(DEFAULT, GROUP, READ))
+ .add(aclEntry(DEFAULT, MASK, READ_WRITE))
+ .add(aclEntry(DEFAULT, OTHER, NONE))
+ .build();
+ List aclSpec = Lists.newArrayList(
+ aclEntry(DEFAULT, USER, "diana"));
+ List expected = new ImmutableList.Builder()
+ .add(aclEntry(ACCESS, USER, ALL))
+ .add(aclEntry(ACCESS, USER, "bruce", READ))
+ .add(aclEntry(ACCESS, USER, "diana", READ_WRITE))
+ .add(aclEntry(ACCESS, GROUP, READ))
+ .add(aclEntry(ACCESS, MASK, READ))
+ .add(aclEntry(ACCESS, OTHER, READ))
+ .add(aclEntry(DEFAULT, USER, ALL))
+ .add(aclEntry(DEFAULT, USER, "bruce", READ))
+ .add(aclEntry(DEFAULT, GROUP, READ))
+ .add(aclEntry(DEFAULT, MASK, READ))
+ .add(aclEntry(DEFAULT, OTHER, NONE))
+ .build();
+ assertEquals(expected, filterAclEntriesByAclSpec(existing, aclSpec));
+ }
+
+ @Test
+ public void testFilterAclEntriesByAclSpecAutomaticDefaultUser()
+ throws AclException {
+ List existing = new ImmutableList.Builder()
+ .add(aclEntry(ACCESS, USER, ALL))
+ .add(aclEntry(ACCESS, GROUP, READ))
+ .add(aclEntry(ACCESS, OTHER, READ))
+ .add(aclEntry(DEFAULT, USER, READ_WRITE))
+ .add(aclEntry(DEFAULT, USER, "bruce", READ))
+ .add(aclEntry(DEFAULT, GROUP, READ))
+ .add(aclEntry(DEFAULT, MASK, READ))
+ .add(aclEntry(DEFAULT, OTHER, NONE))
+ .build();
+ List aclSpec = Lists.newArrayList(
+ aclEntry(DEFAULT, USER));
+ List expected = new ImmutableList.Builder()
+ .add(aclEntry(ACCESS, USER, ALL))
+ .add(aclEntry(ACCESS, GROUP, READ))
+ .add(aclEntry(ACCESS, OTHER, READ))
+ .add(aclEntry(DEFAULT, USER, ALL))
+ .add(aclEntry(DEFAULT, USER, "bruce", READ))
+ .add(aclEntry(DEFAULT, GROUP, READ))
+ .add(aclEntry(DEFAULT, MASK, READ))
+ .add(aclEntry(DEFAULT, OTHER, NONE))
+ .build();
+ assertEquals(expected, filterAclEntriesByAclSpec(existing, aclSpec));
+ }
+
+ @Test
+ public void testFilterAclEntriesByAclSpecAutomaticDefaultGroup()
+ throws AclException {
+ List existing = new ImmutableList.Builder()
+ .add(aclEntry(ACCESS, USER, ALL))
+ .add(aclEntry(ACCESS, GROUP, READ))
+ .add(aclEntry(ACCESS, OTHER, READ))
+ .add(aclEntry(DEFAULT, USER, READ_WRITE))
+ .add(aclEntry(DEFAULT, GROUP, READ_WRITE))
+ .add(aclEntry(DEFAULT, OTHER, NONE))
+ .build();
+ List aclSpec = Lists.newArrayList(
+ aclEntry(DEFAULT, GROUP));
+ List expected = new ImmutableList.Builder()
+ .add(aclEntry(ACCESS, USER, ALL))
+ .add(aclEntry(ACCESS, GROUP, READ))
+ .add(aclEntry(ACCESS, OTHER, READ))
+ .add(aclEntry(DEFAULT, USER, READ_WRITE))
+ .add(aclEntry(DEFAULT, GROUP, READ))
+ .add(aclEntry(DEFAULT, OTHER, NONE))
+ .build();
+ assertEquals(expected, filterAclEntriesByAclSpec(existing, aclSpec));
+ }
+
+ @Test
+ public void testFilterAclEntriesByAclSpecAutomaticDefaultOther()
+ throws AclException {
+ List existing = new ImmutableList.Builder()
+ .add(aclEntry(ACCESS, USER, ALL))
+ .add(aclEntry(ACCESS, GROUP, READ))
+ .add(aclEntry(ACCESS, OTHER, READ))
+ .add(aclEntry(DEFAULT, USER, READ_WRITE))
+ .add(aclEntry(DEFAULT, GROUP, READ_WRITE))
+ .add(aclEntry(DEFAULT, OTHER, NONE))
+ .build();
+ List aclSpec = Lists.newArrayList(
+ aclEntry(DEFAULT, OTHER));
+ List expected = new ImmutableList.Builder()
+ .add(aclEntry(ACCESS, USER, ALL))
+ .add(aclEntry(ACCESS, GROUP, READ))
+ .add(aclEntry(ACCESS, OTHER, READ))
+ .add(aclEntry(DEFAULT, USER, READ_WRITE))
+ .add(aclEntry(DEFAULT, GROUP, READ_WRITE))
+ .add(aclEntry(DEFAULT, OTHER, READ))
+ .build();
+ assertEquals(expected, filterAclEntriesByAclSpec(existing, aclSpec));
+ }
+
+ @Test
+ public void testFilterAclEntriesByAclSpecEmptyAclSpec() throws AclException {
+ List existing = new ImmutableList.Builder()
+ .add(aclEntry(ACCESS, USER, ALL))
+ .add(aclEntry(ACCESS, USER, "bruce", READ_WRITE))
+ .add(aclEntry(ACCESS, GROUP, READ))
+ .add(aclEntry(ACCESS, MASK, ALL))
+ .add(aclEntry(ACCESS, OTHER, READ))
+ .add(aclEntry(DEFAULT, USER, ALL))
+ .add(aclEntry(DEFAULT, USER, "bruce", READ_WRITE))
+ .add(aclEntry(DEFAULT, GROUP, READ))
+ .add(aclEntry(DEFAULT, MASK, ALL))
+ .add(aclEntry(DEFAULT, OTHER, READ))
+ .build();
+ List aclSpec = Lists.newArrayList();
+ assertEquals(existing, filterAclEntriesByAclSpec(existing, aclSpec));
+ }
+
+ @Test(expected=AclException.class)
+ public void testFilterAclEntriesByAclSpecRemoveAccessMaskRequired()
+ throws AclException {
+ List existing = new ImmutableList.Builder()
+ .add(aclEntry(ACCESS, USER, ALL))
+ .add(aclEntry(ACCESS, USER, "bruce", READ))
+ .add(aclEntry(ACCESS, GROUP, READ))
+ .add(aclEntry(ACCESS, MASK, ALL))
+ .add(aclEntry(ACCESS, OTHER, NONE))
+ .build();
+ List aclSpec = Lists.newArrayList(
+ aclEntry(ACCESS, MASK));
+ filterAclEntriesByAclSpec(existing, aclSpec);
+ }
+
+ @Test(expected=AclException.class)
+ public void testFilterAclEntriesByAclSpecRemoveDefaultMaskRequired()
+ throws AclException {
+ List existing = new ImmutableList.Builder()
+ .add(aclEntry(ACCESS, USER, ALL))
+ .add(aclEntry(ACCESS, GROUP, READ))
+ .add(aclEntry(ACCESS, OTHER, NONE))
+ .add(aclEntry(DEFAULT, USER, ALL))
+ .add(aclEntry(DEFAULT, USER, "bruce", READ))
+ .add(aclEntry(DEFAULT, GROUP, READ))
+ .add(aclEntry(DEFAULT, MASK, ALL))
+ .add(aclEntry(DEFAULT, OTHER, NONE))
+ .build();
+ List aclSpec = Lists.newArrayList(
+ aclEntry(DEFAULT, MASK));
+ filterAclEntriesByAclSpec(existing, aclSpec);
+ }
+
+ @Test(expected=AclException.class)
+ public void testFilterAclEntriesByAclSpecInputTooLarge() throws AclException {
+ List existing = new ImmutableList.Builder()
+ .add(aclEntry(ACCESS, USER, ALL))
+ .add(aclEntry(ACCESS, GROUP, READ))
+ .add(aclEntry(ACCESS, OTHER, NONE))
+ .build();
+ filterAclEntriesByAclSpec(existing, ACL_SPEC_TOO_LARGE);
+ }
+
+ @Test
+ public void testFilterDefaultAclEntries() throws AclException {
+ List existing = new ImmutableList.Builder()
+ .add(aclEntry(ACCESS, USER, ALL))
+ .add(aclEntry(ACCESS, USER, "bruce", READ_WRITE))
+ .add(aclEntry(ACCESS, GROUP, READ_EXECUTE))
+ .add(aclEntry(ACCESS, GROUP, "sales", READ_EXECUTE))
+ .add(aclEntry(ACCESS, MASK, ALL))
+ .add(aclEntry(ACCESS, OTHER, NONE))
+ .add(aclEntry(DEFAULT, USER, ALL))
+ .add(aclEntry(DEFAULT, USER, "bruce", READ_WRITE))
+ .add(aclEntry(DEFAULT, GROUP, READ))
+ .add(aclEntry(DEFAULT, GROUP, "sales", READ_EXECUTE))
+ .add(aclEntry(DEFAULT, MASK, READ_WRITE))
+ .add(aclEntry(DEFAULT, OTHER, READ_EXECUTE))
+ .build();
+ List expected = new ImmutableList.Builder()
+ .add(aclEntry(ACCESS, USER, ALL))
+ .add(aclEntry(ACCESS, USER, "bruce", READ_WRITE))
+ .add(aclEntry(ACCESS, GROUP, READ_EXECUTE))
+ .add(aclEntry(ACCESS, GROUP, "sales", READ_EXECUTE))
+ .add(aclEntry(ACCESS, MASK, ALL))
+ .add(aclEntry(ACCESS, OTHER, NONE))
+ .build();
+ assertEquals(expected, filterDefaultAclEntries(existing));
+ }
+
+ @Test
+ public void testFilterDefaultAclEntriesUnchanged() throws AclException {
+ List existing = new ImmutableList.Builder()
+ .add(aclEntry(ACCESS, USER, ALL))
+ .add(aclEntry(ACCESS, USER, "bruce", ALL))
+ .add(aclEntry(ACCESS, GROUP, READ_EXECUTE))
+ .add(aclEntry(ACCESS, GROUP, "sales", ALL))
+ .add(aclEntry(ACCESS, MASK, ALL))
+ .add(aclEntry(ACCESS, OTHER, NONE))
+ .build();
+ assertEquals(existing, filterDefaultAclEntries(existing));
+ }
+
+ @Test
+ public void testMergeAclEntries() throws AclException {
+ List existing = new ImmutableList.Builder()
+ .add(aclEntry(ACCESS, USER, ALL))
+ .add(aclEntry(ACCESS, GROUP, READ_EXECUTE))
+ .add(aclEntry(ACCESS, OTHER, NONE))
+ .build();
+ List aclSpec = Lists.newArrayList(
+ aclEntry(ACCESS, USER, "bruce", ALL));
+ List expected = new ImmutableList.Builder()
+ .add(aclEntry(ACCESS, USER, ALL))
+ .add(aclEntry(ACCESS, USER, "bruce", ALL))
+ .add(aclEntry(ACCESS, GROUP, READ_EXECUTE))
+ .add(aclEntry(ACCESS, MASK, ALL))
+ .add(aclEntry(ACCESS, OTHER, NONE))
+ .build();
+ assertEquals(expected, mergeAclEntries(existing, aclSpec));
+ }
+
+ @Test
+ public void testMergeAclEntriesUnchanged() throws AclException {
+ List existing = new ImmutableList.Builder()
+ .add(aclEntry(ACCESS, USER, ALL))
+ .add(aclEntry(ACCESS, USER, "bruce", ALL))
+ .add(aclEntry(ACCESS, GROUP, READ_EXECUTE))
+ .add(aclEntry(ACCESS, GROUP, "sales", ALL))
+ .add(aclEntry(ACCESS, MASK, ALL))
+ .add(aclEntry(ACCESS, OTHER, NONE))
+ .add(aclEntry(DEFAULT, USER, ALL))
+ .add(aclEntry(DEFAULT, USER, "bruce", ALL))
+ .add(aclEntry(DEFAULT, GROUP, READ_EXECUTE))
+ .add(aclEntry(DEFAULT, GROUP, "sales", ALL))
+ .add(aclEntry(DEFAULT, MASK, ALL))
+ .add(aclEntry(DEFAULT, OTHER, NONE))
+ .build();
+ List aclSpec = Lists.newArrayList(
+ aclEntry(ACCESS, USER, ALL),
+ aclEntry(ACCESS, USER, "bruce", ALL),
+ aclEntry(ACCESS, GROUP, READ_EXECUTE),
+ aclEntry(ACCESS, GROUP, "sales", ALL),
+ aclEntry(ACCESS, MASK, ALL),
+ aclEntry(ACCESS, OTHER, NONE),
+ aclEntry(DEFAULT, USER, ALL),
+ aclEntry(DEFAULT, USER, "bruce", ALL),
+ aclEntry(DEFAULT, GROUP, READ_EXECUTE),
+ aclEntry(DEFAULT, GROUP, "sales", ALL),
+ aclEntry(DEFAULT, MASK, ALL),
+ aclEntry(DEFAULT, OTHER, NONE));
+ assertEquals(existing, mergeAclEntries(existing, aclSpec));
+ }
+
+ @Test
+ public void testMergeAclEntriesMultipleNewBeforeExisting()
+ throws AclException {
+ List existing = new ImmutableList.Builder()
+ .add(aclEntry(ACCESS, USER, ALL))
+ .add(aclEntry(ACCESS, USER, "diana", READ))
+ .add(aclEntry(ACCESS, GROUP, READ_EXECUTE))
+ .add(aclEntry(ACCESS, MASK, READ_EXECUTE))
+ .add(aclEntry(ACCESS, OTHER, NONE))
+ .build();
+ List aclSpec = Lists.newArrayList(
+ aclEntry(ACCESS, USER, "bruce", READ_EXECUTE),
+ aclEntry(ACCESS, USER, "clark", READ_EXECUTE),
+ aclEntry(ACCESS, USER, "diana", READ_EXECUTE));
+ List expected = new ImmutableList.Builder()
+ .add(aclEntry(ACCESS, USER, ALL))
+ .add(aclEntry(ACCESS, USER, "bruce", READ_EXECUTE))
+ .add(aclEntry(ACCESS, USER, "clark", READ_EXECUTE))
+ .add(aclEntry(ACCESS, USER, "diana", READ_EXECUTE))
+ .add(aclEntry(ACCESS, GROUP, READ_EXECUTE))
+ .add(aclEntry(ACCESS, MASK, READ_EXECUTE))
+ .add(aclEntry(ACCESS, OTHER, NONE))
+ .build();
+ assertEquals(expected, mergeAclEntries(existing, aclSpec));
+ }
+
+ @Test
+ public void testMergeAclEntriesAccessMaskCalculated() throws AclException {
+ List existing = new ImmutableList.Builder()
+ .add(aclEntry(ACCESS, USER, ALL))
+ .add(aclEntry(ACCESS, USER, "bruce", READ))
+ .add(aclEntry(ACCESS, GROUP, READ))
+ .add(aclEntry(ACCESS, MASK, READ))
+ .add(aclEntry(ACCESS, OTHER, READ))
+ .build();
+ List aclSpec = Lists.newArrayList(
+ aclEntry(ACCESS, USER, "bruce", READ_EXECUTE),
+ aclEntry(ACCESS, USER, "diana", READ));
+ List expected = new ImmutableList.Builder()
+ .add(aclEntry(ACCESS, USER, ALL))
+ .add(aclEntry(ACCESS, USER, "bruce", READ_EXECUTE))
+ .add(aclEntry(ACCESS, USER, "diana", READ))
+ .add(aclEntry(ACCESS, GROUP, READ))
+ .add(aclEntry(ACCESS, MASK, READ_EXECUTE))
+ .add(aclEntry(ACCESS, OTHER, READ))
+ .build();
+ assertEquals(expected, mergeAclEntries(existing, aclSpec));
+ }
+
+ @Test
+ public void testMergeAclEntriesDefaultMaskCalculated() throws AclException {
+ List existing = new ImmutableList.Builder()
+ .add(aclEntry(ACCESS, USER, ALL))
+ .add(aclEntry(ACCESS, GROUP, READ))
+ .add(aclEntry(ACCESS, OTHER, READ))
+ .add(aclEntry(DEFAULT, USER, ALL))
+ .add(aclEntry(DEFAULT, USER, "bruce", READ))
+ .add(aclEntry(DEFAULT, GROUP, READ))
+ .add(aclEntry(DEFAULT, MASK, READ))
+ .add(aclEntry(DEFAULT, OTHER, NONE))
+ .build();
+ List aclSpec = Lists.newArrayList(
+ aclEntry(DEFAULT, USER, "bruce", READ_WRITE),
+ aclEntry(DEFAULT, USER, "diana", READ_EXECUTE));
+ List expected = new ImmutableList.Builder()
+ .add(aclEntry(ACCESS, USER, ALL))
+ .add(aclEntry(ACCESS, GROUP, READ))
+ .add(aclEntry(ACCESS, OTHER, READ))
+ .add(aclEntry(DEFAULT, USER, ALL))
+ .add(aclEntry(DEFAULT, USER, "bruce", READ_WRITE))
+ .add(aclEntry(DEFAULT, USER, "diana", READ_EXECUTE))
+ .add(aclEntry(DEFAULT, GROUP, READ))
+ .add(aclEntry(DEFAULT, MASK, ALL))
+ .add(aclEntry(DEFAULT, OTHER, NONE))
+ .build();
+ assertEquals(expected, mergeAclEntries(existing, aclSpec));
+ }
+
+ @Test
+ public void testMergeAclEntriesDefaultMaskPreserved() throws AclException {
+ List existing = new ImmutableList.Builder()
+ .add(aclEntry(ACCESS, USER, ALL))
+ .add(aclEntry(ACCESS, GROUP, READ))
+ .add(aclEntry(ACCESS, OTHER, READ))
+ .add(aclEntry(DEFAULT, USER, ALL))
+ .add(aclEntry(DEFAULT, USER, "diana", ALL))
+ .add(aclEntry(DEFAULT, GROUP, READ))
+ .add(aclEntry(DEFAULT, MASK, READ))
+ .add(aclEntry(DEFAULT, OTHER, NONE))
+ .build();
+ List aclSpec = Lists.newArrayList(
+ aclEntry(ACCESS, USER, "diana", FsAction.READ_EXECUTE));
+ List expected = new ImmutableList.Builder()
+ .add(aclEntry(ACCESS, USER, ALL))
+ .add(aclEntry(ACCESS, USER, "diana", READ_EXECUTE))
+ .add(aclEntry(ACCESS, GROUP, READ))
+ .add(aclEntry(ACCESS, MASK, READ_EXECUTE))
+ .add(aclEntry(ACCESS, OTHER, READ))
+ .add(aclEntry(DEFAULT, USER, ALL))
+ .add(aclEntry(DEFAULT, USER, "diana", ALL))
+ .add(aclEntry(DEFAULT, GROUP, READ))
+ .add(aclEntry(DEFAULT, MASK, READ))
+ .add(aclEntry(DEFAULT, OTHER, NONE))
+ .build();
+ assertEquals(expected, mergeAclEntries(existing, aclSpec));
+ }
+
+ @Test
+ public void testMergeAclEntriesAccessMaskPreserved() throws AclException {
+ List existing = new ImmutableList.Builder()
+ .add(aclEntry(ACCESS, USER, ALL))
+ .add(aclEntry(ACCESS, USER, "bruce", READ))
+ .add(aclEntry(ACCESS, USER, "diana", READ_WRITE))
+ .add(aclEntry(ACCESS, GROUP, READ))
+ .add(aclEntry(ACCESS, MASK, READ))
+ .add(aclEntry(ACCESS, OTHER, READ))
+ .add(aclEntry(DEFAULT, USER, ALL))
+ .add(aclEntry(DEFAULT, USER, "bruce", READ))
+ .add(aclEntry(DEFAULT, USER, "diana", READ_WRITE))
+ .add(aclEntry(DEFAULT, GROUP, READ))
+ .add(aclEntry(DEFAULT, MASK, READ_WRITE))
+ .add(aclEntry(DEFAULT, OTHER, NONE))
+ .build();
+ List aclSpec = Lists.newArrayList(
+ aclEntry(DEFAULT, USER, "diana", READ_EXECUTE));
+ List expected = new ImmutableList.Builder()
+ .add(aclEntry(ACCESS, USER, ALL))
+ .add(aclEntry(ACCESS, USER, "bruce", READ))
+ .add(aclEntry(ACCESS, USER, "diana", READ_WRITE))
+ .add(aclEntry(ACCESS, GROUP, READ))
+ .add(aclEntry(ACCESS, MASK, READ))
+ .add(aclEntry(ACCESS, OTHER, READ))
+ .add(aclEntry(DEFAULT, USER, ALL))
+ .add(aclEntry(DEFAULT, USER, "bruce", READ))
+ .add(aclEntry(DEFAULT, USER, "diana", READ_EXECUTE))
+ .add(aclEntry(DEFAULT, GROUP, READ))
+ .add(aclEntry(DEFAULT, MASK, READ_EXECUTE))
+ .add(aclEntry(DEFAULT, OTHER, NONE))
+ .build();
+ assertEquals(expected, mergeAclEntries(existing, aclSpec));
+ }
+
+ @Test
+ public void testMergeAclEntriesAutomaticDefaultUser() throws AclException {
+ List existing = new ImmutableList.Builder()
+ .add(aclEntry(ACCESS, USER, ALL))
+ .add(aclEntry(ACCESS, GROUP, READ))
+ .add(aclEntry(ACCESS, OTHER, READ))
+ .build();
+ List aclSpec = Lists.newArrayList(
+ aclEntry(DEFAULT, GROUP, READ_EXECUTE),
+ aclEntry(DEFAULT, OTHER, READ));
+ List expected = new ImmutableList.Builder()
+ .add(aclEntry(ACCESS, USER, ALL))
+ .add(aclEntry(ACCESS, GROUP, READ))
+ .add(aclEntry(ACCESS, OTHER, READ))
+ .add(aclEntry(DEFAULT, USER, ALL))
+ .add(aclEntry(DEFAULT, GROUP, READ_EXECUTE))
+ .add(aclEntry(DEFAULT, OTHER, READ))
+ .build();
+ assertEquals(expected, mergeAclEntries(existing, aclSpec));
+ }
+
+ @Test
+ public void testMergeAclEntriesAutomaticDefaultGroup() throws AclException {
+ List existing = new ImmutableList.Builder()
+ .add(aclEntry(ACCESS, USER, ALL))
+ .add(aclEntry(ACCESS, GROUP, READ))
+ .add(aclEntry(ACCESS, OTHER, READ))
+ .build();
+ List aclSpec = Lists.newArrayList(
+ aclEntry(DEFAULT, USER, READ_EXECUTE),
+ aclEntry(DEFAULT, OTHER, READ));
+ List expected = new ImmutableList.Builder()
+ .add(aclEntry(ACCESS, USER, ALL))
+ .add(aclEntry(ACCESS, GROUP, READ))
+ .add(aclEntry(ACCESS, OTHER, READ))
+ .add(aclEntry(DEFAULT, USER, READ_EXECUTE))
+ .add(aclEntry(DEFAULT, GROUP, READ))
+ .add(aclEntry(DEFAULT, OTHER, READ))
+ .build();
+ assertEquals(expected, mergeAclEntries(existing, aclSpec));
+ }
+
+ @Test
+ public void testMergeAclEntriesAutomaticDefaultOther() throws AclException {
+ List existing = new ImmutableList.Builder()
+ .add(aclEntry(ACCESS, USER, ALL))
+ .add(aclEntry(ACCESS, GROUP, READ))
+ .add(aclEntry(ACCESS, OTHER, NONE))
+ .build();
+ List aclSpec = Lists.newArrayList(
+ aclEntry(DEFAULT, USER, READ_EXECUTE),
+ aclEntry(DEFAULT, GROUP, READ_EXECUTE));
+ List expected = new ImmutableList.Builder()
+ .add(aclEntry(ACCESS, USER, ALL))
+ .add(aclEntry(ACCESS, GROUP, READ))
+ .add(aclEntry(ACCESS, OTHER, NONE))
+ .add(aclEntry(DEFAULT, USER, READ_EXECUTE))
+ .add(aclEntry(DEFAULT, GROUP, READ_EXECUTE))
+ .add(aclEntry(DEFAULT, OTHER, NONE))
+ .build();
+ assertEquals(expected, mergeAclEntries(existing, aclSpec));
+ }
+
+ @Test
+ public void testMergeAclEntriesProvidedAccessMask() throws AclException {
+ List existing = new ImmutableList.Builder()
+ .add(aclEntry(ACCESS, USER, ALL))
+ .add(aclEntry(ACCESS, GROUP, READ))
+ .add(aclEntry(ACCESS, OTHER, NONE))
+ .build();
+ List aclSpec = Lists.newArrayList(
+ aclEntry(ACCESS, USER, "bruce", READ_EXECUTE),
+ aclEntry(ACCESS, MASK, ALL));
+ List expected = new ImmutableList.Builder()
+ .add(aclEntry(ACCESS, USER, ALL))
+ .add(aclEntry(ACCESS, USER, "bruce", READ_EXECUTE))
+ .add(aclEntry(ACCESS, GROUP, READ))
+ .add(aclEntry(ACCESS, MASK, ALL))
+ .add(aclEntry(ACCESS, OTHER, NONE))
+ .build();
+ assertEquals(expected, mergeAclEntries(existing, aclSpec));
+ }
+
+ @Test
+ public void testMergeAclEntriesProvidedDefaultMask() throws AclException {
+ List existing = new ImmutableList.Builder()
+ .add(aclEntry(ACCESS, USER, ALL))
+ .add(aclEntry(ACCESS, GROUP, READ))
+ .add(aclEntry(ACCESS, OTHER, NONE))
+ .build();
+ List aclSpec = Lists.newArrayList(
+ aclEntry(DEFAULT, USER, ALL),
+ aclEntry(DEFAULT, GROUP, READ),
+ aclEntry(DEFAULT, MASK, ALL),
+ aclEntry(DEFAULT, OTHER, NONE));
+ List expected = new ImmutableList.Builder()
+ .add(aclEntry(ACCESS, USER, ALL))
+ .add(aclEntry(ACCESS, GROUP, READ))
+ .add(aclEntry(ACCESS, OTHER, NONE))
+ .add(aclEntry(DEFAULT, USER, ALL))
+ .add(aclEntry(DEFAULT, GROUP, READ))
+ .add(aclEntry(DEFAULT, MASK, ALL))
+ .add(aclEntry(DEFAULT, OTHER, NONE))
+ .build();
+ assertEquals(expected, mergeAclEntries(existing, aclSpec));
+ }
+
+ @Test
+ public void testMergeAclEntriesEmptyAclSpec() throws AclException {
+ List existing = new ImmutableList.Builder()
+ .add(aclEntry(ACCESS, USER, ALL))
+ .add(aclEntry(ACCESS, USER, "bruce", READ_WRITE))
+ .add(aclEntry(ACCESS, GROUP, READ))
+ .add(aclEntry(ACCESS, MASK, ALL))
+ .add(aclEntry(ACCESS, OTHER, READ))
+ .add(aclEntry(DEFAULT, USER, ALL))
+ .add(aclEntry(DEFAULT, USER, "bruce", READ_WRITE))
+ .add(aclEntry(DEFAULT, GROUP, READ))
+ .add(aclEntry(DEFAULT, MASK, ALL))
+ .add(aclEntry(DEFAULT, OTHER, READ))
+ .build();
+ List aclSpec = Lists.newArrayList();
+ assertEquals(existing, mergeAclEntries(existing, aclSpec));
+ }
+
+ @Test(expected=AclException.class)
+ public void testMergeAclEntriesInputTooLarge() throws AclException {
+ List existing = new ImmutableList.Builder()
+ .add(aclEntry(ACCESS, USER, ALL))
+ .add(aclEntry(ACCESS, GROUP, READ))
+ .add(aclEntry(ACCESS, OTHER, NONE))
+ .build();
+ mergeAclEntries(existing, ACL_SPEC_TOO_LARGE);
+ }
+
+ @Test(expected=AclException.class)
+ public void testMergeAclEntriesResultTooLarge() throws AclException {
+ ImmutableList.Builder aclBuilder =
+ new ImmutableList.Builder()
+ .add(aclEntry(ACCESS, USER, ALL));
+ for (int i = 1; i <= 28; ++i) {
+ aclBuilder.add(aclEntry(ACCESS, USER, "user" + i, READ));
+ }
+ aclBuilder
+ .add(aclEntry(ACCESS, GROUP, READ))
+ .add(aclEntry(ACCESS, MASK, READ))
+ .add(aclEntry(ACCESS, OTHER, NONE));
+ List existing = aclBuilder.build();
+ List aclSpec = Lists.newArrayList(
+ aclEntry(ACCESS, USER, "bruce", READ));
+ mergeAclEntries(existing, aclSpec);
+ }
+
+ @Test(expected=AclException.class)
+ public void testMergeAclEntriesDuplicateEntries() throws AclException {
+ List existing = new ImmutableList.Builder()
+ .add(aclEntry(ACCESS, USER, ALL))
+ .add(aclEntry(ACCESS, GROUP, READ))
+ .add(aclEntry(ACCESS, OTHER, NONE))
+ .build();
+ List aclSpec = Lists.newArrayList(
+ aclEntry(ACCESS, USER, "bruce", ALL),
+ aclEntry(ACCESS, USER, "diana", READ_WRITE),
+ aclEntry(ACCESS, USER, "clark", READ),
+ aclEntry(ACCESS, USER, "bruce", READ_EXECUTE));
+ mergeAclEntries(existing, aclSpec);
+ }
+
+ @Test(expected=AclException.class)
+ public void testMergeAclEntriesNamedMask() throws AclException {
+ List existing = new ImmutableList.Builder()
+ .add(aclEntry(ACCESS, USER, ALL))
+ .add(aclEntry(ACCESS, GROUP, READ))
+ .add(aclEntry(ACCESS, OTHER, NONE))
+ .build();
+ List aclSpec = Lists.newArrayList(
+ aclEntry(ACCESS, MASK, "bruce", READ_EXECUTE));
+ mergeAclEntries(existing, aclSpec);
+ }
+
+ @Test(expected=AclException.class)
+ public void testMergeAclEntriesNamedOther() throws AclException {
+ List existing = new ImmutableList.Builder()
+ .add(aclEntry(ACCESS, USER, ALL))
+ .add(aclEntry(ACCESS, GROUP, READ))
+ .add(aclEntry(ACCESS, OTHER, NONE))
+ .build();
+ List aclSpec = Lists.newArrayList(
+ aclEntry(ACCESS, OTHER, "bruce", READ_EXECUTE));
+ mergeAclEntries(existing, aclSpec);
+ }
+
+ @Test
+ public void testReplaceAclEntries() throws AclException {
+ List existing = new ImmutableList.Builder()
+ .add(aclEntry(ACCESS, USER, ALL))
+ .add(aclEntry(ACCESS, USER, "bruce", ALL))
+ .add(aclEntry(ACCESS, GROUP, READ_EXECUTE))
+ .add(aclEntry(ACCESS, MASK, ALL))
+ .add(aclEntry(ACCESS, OTHER, NONE))
+ .build();
+ List aclSpec = Lists.newArrayList(
+ aclEntry(ACCESS, USER, ALL),
+ aclEntry(ACCESS, USER, "bruce", READ_WRITE),
+ aclEntry(ACCESS, GROUP, READ_EXECUTE),
+ aclEntry(ACCESS, GROUP, "sales", ALL),
+ aclEntry(ACCESS, MASK, ALL),
+ aclEntry(ACCESS, OTHER, NONE),
+ aclEntry(DEFAULT, USER, ALL),
+ aclEntry(DEFAULT, USER, "bruce", READ_WRITE),
+ aclEntry(DEFAULT, GROUP, READ_EXECUTE),
+ aclEntry(DEFAULT, GROUP, "sales", ALL),
+ aclEntry(DEFAULT, MASK, ALL),
+ aclEntry(DEFAULT, OTHER, NONE));
+ List expected = new ImmutableList.Builder()
+ .add(aclEntry(ACCESS, USER, ALL))
+ .add(aclEntry(ACCESS, USER, "bruce", READ_WRITE))
+ .add(aclEntry(ACCESS, GROUP, READ_EXECUTE))
+ .add(aclEntry(ACCESS, GROUP, "sales", ALL))
+ .add(aclEntry(ACCESS, MASK, ALL))
+ .add(aclEntry(ACCESS, OTHER, NONE))
+ .add(aclEntry(DEFAULT, USER, ALL))
+ .add(aclEntry(DEFAULT, USER, "bruce", READ_WRITE))
+ .add(aclEntry(DEFAULT, GROUP, READ_EXECUTE))
+ .add(aclEntry(DEFAULT, GROUP, "sales", ALL))
+ .add(aclEntry(DEFAULT, MASK, ALL))
+ .add(aclEntry(DEFAULT, OTHER, NONE))
+ .build();
+ assertEquals(expected, replaceAclEntries(existing, aclSpec));
+ }
+
+ @Test
+ public void testReplaceAclEntriesUnchanged() throws AclException {
+ List existing = new ImmutableList.Builder()
+ .add(aclEntry(ACCESS, USER, ALL))
+ .add(aclEntry(ACCESS, USER, "bruce", ALL))
+ .add(aclEntry(ACCESS, GROUP, READ_EXECUTE))
+ .add(aclEntry(ACCESS, GROUP, "sales", ALL))
+ .add(aclEntry(ACCESS, MASK, ALL))
+ .add(aclEntry(ACCESS, OTHER, NONE))
+ .add(aclEntry(DEFAULT, USER, ALL))
+ .add(aclEntry(DEFAULT, USER, "bruce", ALL))
+ .add(aclEntry(DEFAULT, GROUP, READ_EXECUTE))
+ .add(aclEntry(DEFAULT, GROUP, "sales", ALL))
+ .add(aclEntry(DEFAULT, MASK, ALL))
+ .add(aclEntry(DEFAULT, OTHER, NONE))
+ .build();
+ List aclSpec = Lists.newArrayList(
+ aclEntry(ACCESS, USER, ALL),
+ aclEntry(ACCESS, USER, "bruce", ALL),
+ aclEntry(ACCESS, GROUP, READ_EXECUTE),
+ aclEntry(ACCESS, GROUP, "sales", ALL),
+ aclEntry(ACCESS, MASK, ALL),
+ aclEntry(ACCESS, OTHER, NONE),
+ aclEntry(DEFAULT, USER, ALL),
+ aclEntry(DEFAULT, USER, "bruce", ALL),
+ aclEntry(DEFAULT, GROUP, READ_EXECUTE),
+ aclEntry(DEFAULT, GROUP, "sales", ALL),
+ aclEntry(DEFAULT, MASK, ALL),
+ aclEntry(DEFAULT, OTHER, NONE));
+ assertEquals(existing, replaceAclEntries(existing, aclSpec));
+ }
+
+ @Test
+ public void testReplaceAclEntriesAccessMaskCalculated() throws AclException {
+ List existing = new ImmutableList.Builder()
+ .add(aclEntry(ACCESS, USER, ALL))
+ .add(aclEntry(ACCESS, GROUP, READ))
+ .add(aclEntry(ACCESS, OTHER, READ))
+ .build();
+ List aclSpec = Lists.newArrayList(
+ aclEntry(ACCESS, USER, ALL),
+ aclEntry(ACCESS, USER, "bruce", READ),
+ aclEntry(ACCESS, USER, "diana", READ_WRITE),
+ aclEntry(ACCESS, GROUP, READ),
+ aclEntry(ACCESS, OTHER, READ));
+ List expected = new ImmutableList.Builder()
+ .add(aclEntry(ACCESS, USER, ALL))
+ .add(aclEntry(ACCESS, USER, "bruce", READ))
+ .add(aclEntry(ACCESS, USER, "diana", READ_WRITE))
+ .add(aclEntry(ACCESS, GROUP, READ))
+ .add(aclEntry(ACCESS, MASK, READ_WRITE))
+ .add(aclEntry(ACCESS, OTHER, READ))
+ .build();
+ assertEquals(expected, replaceAclEntries(existing, aclSpec));
+ }
+
+ @Test
+ public void testReplaceAclEntriesDefaultMaskCalculated() throws AclException {
+ List existing = new ImmutableList.Builder()
+ .add(aclEntry(ACCESS, USER, ALL))
+ .add(aclEntry(ACCESS, GROUP, READ))
+ .add(aclEntry(ACCESS, OTHER, READ))
+ .build();
+ List aclSpec = Lists.newArrayList(
+ aclEntry(ACCESS, USER, ALL),
+ aclEntry(ACCESS, GROUP, READ),
+ aclEntry(ACCESS, OTHER, READ),
+ aclEntry(DEFAULT, USER, ALL),
+ aclEntry(DEFAULT, USER, "bruce", READ),
+ aclEntry(DEFAULT, USER, "diana", READ_WRITE),
+ aclEntry(DEFAULT, GROUP, ALL),
+ aclEntry(DEFAULT, OTHER, READ));
+ List expected = new ImmutableList.Builder()
+ .add(aclEntry(ACCESS, USER, ALL))
+ .add(aclEntry(ACCESS, GROUP, READ))
+ .add(aclEntry(ACCESS, OTHER, READ))
+ .add(aclEntry(DEFAULT, USER, ALL))
+ .add(aclEntry(DEFAULT, USER, "bruce", READ))
+ .add(aclEntry(DEFAULT, USER, "diana", READ_WRITE))
+ .add(aclEntry(DEFAULT, GROUP, ALL))
+ .add(aclEntry(DEFAULT, MASK, ALL))
+ .add(aclEntry(DEFAULT, OTHER, READ))
+ .build();
+ assertEquals(expected, replaceAclEntries(existing, aclSpec));
+ }
+
+ @Test
+ public void testReplaceAclEntriesDefaultMaskPreserved() throws AclException {
+ List existing = new ImmutableList.Builder()
+ .add(aclEntry(ACCESS, USER, ALL))
+ .add(aclEntry(ACCESS, USER, "bruce", READ))
+ .add(aclEntry(ACCESS, USER, "diana", READ_WRITE))
+ .add(aclEntry(ACCESS, GROUP, READ))
+ .add(aclEntry(ACCESS, MASK, READ_WRITE))
+ .add(aclEntry(ACCESS, OTHER, READ))
+ .add(aclEntry(DEFAULT, USER, ALL))
+ .add(aclEntry(DEFAULT, USER, "diana", ALL))
+ .add(aclEntry(DEFAULT, GROUP, READ))
+ .add(aclEntry(DEFAULT, MASK, READ))
+ .add(aclEntry(DEFAULT, OTHER, NONE))
+ .build();
+ List aclSpec = Lists.newArrayList(
+ aclEntry(ACCESS, USER, ALL),
+ aclEntry(ACCESS, USER, "bruce", READ),
+ aclEntry(ACCESS, USER, "diana", READ_WRITE),
+ aclEntry(ACCESS, GROUP, ALL),
+ aclEntry(ACCESS, OTHER, READ));
+ List expected = new ImmutableList.Builder()
+ .add(aclEntry(ACCESS, USER, ALL))
+ .add(aclEntry(ACCESS, USER, "bruce", READ))
+ .add(aclEntry(ACCESS, USER, "diana", READ_WRITE))
+ .add(aclEntry(ACCESS, GROUP, ALL))
+ .add(aclEntry(ACCESS, MASK, ALL))
+ .add(aclEntry(ACCESS, OTHER, READ))
+ .add(aclEntry(DEFAULT, USER, ALL))
+ .add(aclEntry(DEFAULT, USER, "diana", ALL))
+ .add(aclEntry(DEFAULT, GROUP, READ))
+ .add(aclEntry(DEFAULT, MASK, READ))
+ .add(aclEntry(DEFAULT, OTHER, NONE))
+ .build();
+ assertEquals(expected, replaceAclEntries(existing, aclSpec));
+ }
+
+ @Test
+ public void testReplaceAclEntriesAccessMaskPreserved() throws AclException {
+ List existing = new ImmutableList.Builder()
+ .add(aclEntry(ACCESS, USER, ALL))
+ .add(aclEntry(ACCESS, USER, "bruce", READ))
+ .add(aclEntry(ACCESS, USER, "diana", READ_WRITE))
+ .add(aclEntry(ACCESS, GROUP, READ))
+ .add(aclEntry(ACCESS, MASK, READ))
+ .add(aclEntry(ACCESS, OTHER, READ))
+ .add(aclEntry(DEFAULT, USER, ALL))
+ .add(aclEntry(DEFAULT, USER, "bruce", READ))
+ .add(aclEntry(DEFAULT, USER, "diana", READ_WRITE))
+ .add(aclEntry(DEFAULT, GROUP, READ))
+ .add(aclEntry(DEFAULT, MASK, READ_WRITE))
+ .add(aclEntry(DEFAULT, OTHER, NONE))
+ .build();
+ List aclSpec = Lists.newArrayList(
+ aclEntry(DEFAULT, USER, ALL),
+ aclEntry(DEFAULT, USER, "bruce", READ),
+ aclEntry(DEFAULT, GROUP, READ),
+ aclEntry(DEFAULT, OTHER, NONE));
+ List expected = new ImmutableList.Builder()
+ .add(aclEntry(ACCESS, USER, ALL))
+ .add(aclEntry(ACCESS, USER, "bruce", READ))
+ .add(aclEntry(ACCESS, USER, "diana", READ_WRITE))
+ .add(aclEntry(ACCESS, GROUP, READ))
+ .add(aclEntry(ACCESS, MASK, READ))
+ .add(aclEntry(ACCESS, OTHER, READ))
+ .add(aclEntry(DEFAULT, USER, ALL))
+ .add(aclEntry(DEFAULT, USER, "bruce", READ))
+ .add(aclEntry(DEFAULT, GROUP, READ))
+ .add(aclEntry(DEFAULT, MASK, READ))
+ .add(aclEntry(DEFAULT, OTHER, NONE))
+ .build();
+ assertEquals(expected, replaceAclEntries(existing, aclSpec));
+ }
+
+ @Test
+ public void testReplaceAclEntriesAutomaticDefaultUser() throws AclException {
+ List existing = new ImmutableList.Builder()
+ .add(aclEntry(ACCESS, USER, ALL))
+ .add(aclEntry(ACCESS, GROUP, READ))
+ .add(aclEntry(ACCESS, OTHER, NONE))
+ .build();
+ List aclSpec = Lists.newArrayList(
+ aclEntry(ACCESS, USER, ALL),
+ aclEntry(ACCESS, GROUP, READ),
+ aclEntry(ACCESS, OTHER, NONE),
+ aclEntry(DEFAULT, USER, "bruce", READ),
+ aclEntry(DEFAULT, GROUP, READ_WRITE),
+ aclEntry(DEFAULT, MASK, READ_WRITE),
+ aclEntry(DEFAULT, OTHER, READ));
+ List expected = new ImmutableList.Builder()
+ .add(aclEntry(ACCESS, USER, ALL))
+ .add(aclEntry(ACCESS, GROUP, READ))
+ .add(aclEntry(ACCESS, OTHER, NONE))
+ .add(aclEntry(DEFAULT, USER, ALL))
+ .add(aclEntry(DEFAULT, USER, "bruce", READ))
+ .add(aclEntry(DEFAULT, GROUP, READ_WRITE))
+ .add(aclEntry(DEFAULT, MASK, READ_WRITE))
+ .add(aclEntry(DEFAULT, OTHER, READ))
+ .build();
+ assertEquals(expected, replaceAclEntries(existing, aclSpec));
+ }
+
+ @Test
+ public void testReplaceAclEntriesAutomaticDefaultGroup() throws AclException {
+ List existing = new ImmutableList.Builder()
+ .add(aclEntry(ACCESS, USER, ALL))
+ .add(aclEntry(ACCESS, GROUP, READ))
+ .add(aclEntry(ACCESS, OTHER, NONE))
+ .build();
+ List aclSpec = Lists.newArrayList(
+ aclEntry(ACCESS, USER, ALL),
+ aclEntry(ACCESS, GROUP, READ),
+ aclEntry(ACCESS, OTHER, NONE),
+ aclEntry(DEFAULT, USER, READ_WRITE),
+ aclEntry(DEFAULT, USER, "bruce", READ),
+ aclEntry(DEFAULT, MASK, READ),
+ aclEntry(DEFAULT, OTHER, READ));
+ List expected = new ImmutableList.Builder()
+ .add(aclEntry(ACCESS, USER, ALL))
+ .add(aclEntry(ACCESS, GROUP, READ))
+ .add(aclEntry(ACCESS, OTHER, NONE))
+ .add(aclEntry(DEFAULT, USER, READ_WRITE))
+ .add(aclEntry(DEFAULT, USER, "bruce", READ))
+ .add(aclEntry(DEFAULT, GROUP, READ))
+ .add(aclEntry(DEFAULT, MASK, READ))
+ .add(aclEntry(DEFAULT, OTHER, READ))
+ .build();
+ assertEquals(expected, replaceAclEntries(existing, aclSpec));
+ }
+
+ @Test
+ public void testReplaceAclEntriesAutomaticDefaultOther() throws AclException {
+ List existing = new ImmutableList.Builder()
+ .add(aclEntry(ACCESS, USER, ALL))
+ .add(aclEntry(ACCESS, GROUP, READ))
+ .add(aclEntry(ACCESS, OTHER, NONE))
+ .build();
+ List aclSpec = Lists.newArrayList(
+ aclEntry(ACCESS, USER, ALL),
+ aclEntry(ACCESS, GROUP, READ),
+ aclEntry(ACCESS, OTHER, NONE),
+ aclEntry(DEFAULT, USER, READ_WRITE),
+ aclEntry(DEFAULT, USER, "bruce", READ),
+ aclEntry(DEFAULT, GROUP, READ_WRITE),
+ aclEntry(DEFAULT, MASK, READ_WRITE));
+ List expected = new ImmutableList.Builder()
+ .add(aclEntry(ACCESS, USER, ALL))
+ .add(aclEntry(ACCESS, GROUP, READ))
+ .add(aclEntry(ACCESS, OTHER, NONE))
+ .add(aclEntry(DEFAULT, USER, READ_WRITE))
+ .add(aclEntry(DEFAULT, USER, "bruce", READ))
+ .add(aclEntry(DEFAULT, GROUP, READ_WRITE))
+ .add(aclEntry(DEFAULT, MASK, READ_WRITE))
+ .add(aclEntry(DEFAULT, OTHER, NONE))
+ .build();
+ assertEquals(expected, replaceAclEntries(existing, aclSpec));
+ }
+
+ @Test(expected=AclException.class)
+ public void testReplaceAclEntriesInputTooLarge() throws AclException {
+ List existing = new ImmutableList.Builder()
+ .add(aclEntry(ACCESS, USER, ALL))
+ .add(aclEntry(ACCESS, GROUP, READ))
+ .add(aclEntry(ACCESS, OTHER, NONE))
+ .build();
+ replaceAclEntries(existing, ACL_SPEC_TOO_LARGE);
+ }
+
+ @Test(expected=AclException.class)
+ public void testReplaceAclEntriesResultTooLarge() throws AclException {
+ List existing = new ImmutableList.Builder()
+ .add(aclEntry(ACCESS, USER, ALL))
+ .add(aclEntry(ACCESS, GROUP, READ))
+ .add(aclEntry(ACCESS, OTHER, NONE))
+ .build();
+ List aclSpec = Lists.newArrayListWithCapacity(32);
+ aclSpec.add(aclEntry(ACCESS, USER, ALL));
+ for (int i = 1; i <= 29; ++i) {
+ aclSpec.add(aclEntry(ACCESS, USER, "user" + i, READ));
+ }
+ aclSpec.add(aclEntry(ACCESS, GROUP, READ));
+ aclSpec.add(aclEntry(ACCESS, OTHER, NONE));
+ // The ACL spec now has 32 entries. Automatic mask calculation will push it
+ // over the limit to 33.
+ replaceAclEntries(existing, aclSpec);
+ }
+
+ @Test(expected=AclException.class)
+ public void testReplaceAclEntriesDuplicateEntries() throws AclException {
+ List