java.lang.Object
org.elasticsearch.entitlement.runtime.policy.FileAccessTree

public final class FileAccessTree extends Object

This structure facilitates looking up files entitlements for a particular component+module combination, given that grants can occur at the directory level in addition to the individual file level.

Broadly, this class operates on strings rather than abstractions like File or Path, since those can have behaviour that varies by platform in surprising ways; using strings makes the behaviour predictable. The strings are produced by a method called normalizePath(java.nio.file.Path) to make sure they are absolute paths with consistent separator characters.

Internally, it does not use a tree data structure; the name "tree" refers to the tree structure of the filesystem, not the choice of data structure. It takes advantage of the fact that, after normalization, the name of a directory containing a file is a prefix of the file's own name. The permissions are maintained in several sorted arrays of String, and permission checks are implemented as a binary search within these arrays.

We want the binary search to locate the relevant entry immediately, either because there's an exact match, or else because there's no exact match and the binary search points us as an entry for the containing directory. This seems straightforward if the paths are absolute and sorted, but there are several subtleties.

Firstly, there could be intervening siblings; for example, in ["/a", "/a/b"], if we look up "/a/c", the binary search will land on "/a/b", which is not a relevant entry for "/a/c". The solution here is (1) segregate the read and write permissions so that each array covers one homogeneous kind of permission, and (2) prune the list so redundant child entries are removed when they are already covered by parent entries. In our example, this pruning process would produce the array ["/a"] only, and so the binary search for "/a/c" lands on the relevant entry for the containing directory.

Secondly, the path separator (whether slash or backslash) sorts after the dot character. This means, for example, if the array contains ["/a", "/a.xml"] and we look up "/a/b" using normal string comparison, the binary search would land on "/a.xml", which is neither an exact match nor a containing directory, and so the lookup would incorrectly report no match, even though "/a" is in the array. To fix this, we define FileAccessTreeComparison.pathComparator() which sorts path separators before any other character. In the example, this would cause "/a/b" to sort between "/a" and "/a.xml" so that it correctly finds "/a".

With the paths pruned, sorted, and segregated by permission, each binary search has the following properties:
  • If an exact match exists, it will be found at the returned index
  • Else, if a containing folder exists, it will be found immediately before the returned index
  • Else, there is no match
Permission is granted if both:
  • there is no match in exclusivePaths, and
  • there is a match in the array corresponding to the desired operation (read or write).

Some additional care is required in the unit tests for this code, since it must also run on Windows where the separator is a backslash and absolute paths don't start with a separator. See FileAccessTreeTests#testDuplicateExclusivePaths for an example.

  • Method Details

    • withoutExclusivePaths

      public static FileAccessTree withoutExclusivePaths(FilesEntitlement filesEntitlement, PathLookup pathLookup, Collection<Path> componentPaths)
      A special factory method to create a FileAccessTree with no ExclusivePaths, e.g. for quick validation or for default file access
    • canRead

      public boolean canRead(Path path)
    • canWrite

      public boolean canWrite(Path path)
    • equals

      public boolean equals(Object o)
      Overrides:
      equals in class Object
    • hashCode

      public int hashCode()
      Overrides:
      hashCode in class Object