Skip to content
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@
import java.util.List;
import java.util.Objects;

import static org.elasticsearch.core.PathUtils.getDefaultFileSystem;
import static org.elasticsearch.entitlement.runtime.policy.entitlements.FileEntitlement.normalizePath;

public final class FileAccessTree {
public static final FileAccessTree EMPTY = new FileAccessTree(List.of());

Expand All @@ -27,11 +30,11 @@ private FileAccessTree(List<FileEntitlement> fileEntitlements) {
List<String> readPaths = new ArrayList<>();
List<String> writePaths = new ArrayList<>();
for (FileEntitlement fileEntitlement : fileEntitlements) {
var mode = fileEntitlement.mode();
if (mode == FileEntitlement.Mode.READ_WRITE) {
writePaths.add(fileEntitlement.path());
String path = fileEntitlement.path();
if (fileEntitlement.mode() == FileEntitlement.Mode.READ_WRITE) {
writePaths.add(path);
}
readPaths.add(fileEntitlement.path());
readPaths.add(path);
}

readPaths.sort(String::compareTo);
Expand All @@ -46,15 +49,11 @@ public static FileAccessTree of(List<FileEntitlement> fileEntitlements) {
}

boolean canRead(Path path) {
return checkPath(normalize(path), readPaths);
return checkPath(normalizePath(path), readPaths);
}

boolean canWrite(Path path) {
return checkPath(normalize(path), writePaths);
}

private static String normalize(Path path) {
return path.toAbsolutePath().normalize().toString();
return checkPath(normalizePath(path), writePaths);
}

private static boolean checkPath(String path, String[] paths) {
Expand All @@ -64,7 +63,7 @@ private static boolean checkPath(String path, String[] paths) {
int ndx = Arrays.binarySearch(paths, path);
if (ndx < -1) {
String maybeParent = paths[-ndx - 2];
return path.startsWith(maybeParent);
return path.startsWith(maybeParent + getDefaultFileSystem().getSeparator());
}
return ndx >= 0;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,15 @@
import org.elasticsearch.entitlement.runtime.policy.ExternalEntitlement;
import org.elasticsearch.entitlement.runtime.policy.PolicyValidationException;

import java.nio.file.Path;
import java.nio.file.Paths;

/**
* Describes a file entitlement with a path and mode.
* Describes entitlement to access files at a particular location.
*
* @param path the location of the files. Will be automatically {@link #normalizePath normalized}.
* For directories, implicitly includes access to all contained files and (recursively) subdirectories.
* @param mode the type of operation
*/
public record FileEntitlement(String path, Mode mode) implements Entitlement {

Expand All @@ -25,11 +30,17 @@ public enum Mode {
}

public FileEntitlement {
path = normalizePath(path);
path = normalizePath(Paths.get(path));
}

private static String normalizePath(String path) {
return Paths.get(path).toAbsolutePath().normalize().toString();
/**
* @return the "canonical" form of the given {@code path}, to be used for entitlement checks.
*/
public static String normalizePath(Path path) {
// Note that toAbsolutePath produces paths separated by File.separator,
// so on Windows, if the given path uses forward slashes, this consistently
// converts it to backslashes.
return path.toAbsolutePath().normalize().toString();
}

private static Mode parseMode(String mode) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import java.nio.file.Path;
import java.util.List;

import static org.elasticsearch.core.PathUtils.getDefaultFileSystem;
import static org.hamcrest.Matchers.is;

public class FileAccessTreeTests extends ESTestCase {
Expand All @@ -41,7 +42,9 @@ public void testRead() {
var tree = FileAccessTree.of(List.of(entitlement("foo", "read")));
assertThat(tree.canRead(path("foo")), is(true));
assertThat(tree.canRead(path("foo/subdir")), is(true));
assertThat(tree.canRead(path("food")), is(false));
assertThat(tree.canWrite(path("foo")), is(false));
assertThat(tree.canWrite(path("food")), is(false));

assertThat(tree.canRead(path("before")), is(false));
assertThat(tree.canRead(path("later")), is(false));
Expand All @@ -51,7 +54,9 @@ public void testWrite() {
var tree = FileAccessTree.of(List.of(entitlement("foo", "read_write")));
assertThat(tree.canWrite(path("foo")), is(true));
assertThat(tree.canWrite(path("foo/subdir")), is(true));
assertThat(tree.canWrite(path("food")), is(false));
assertThat(tree.canRead(path("foo")), is(true));
assertThat(tree.canRead(path("food")), is(false));

assertThat(tree.canWrite(path("before")), is(false));
assertThat(tree.canWrite(path("later")), is(false));
Expand Down Expand Up @@ -83,6 +88,22 @@ public void testNormalizePath() {
assertThat(tree.canRead(path("")), is(false));
}

public void testForwardSlashes() {
String sep = getDefaultFileSystem().getSeparator();
var tree = FileAccessTree.of(List.of(entitlement("a/b", "read"), entitlement("m" + sep + "n", "read")));

// Native separators work
assertThat(tree.canRead(path("a" + sep + "b")), is(true));
assertThat(tree.canRead(path("m" + sep + "n")), is(true));

// Forward slashes also work
assertThat(tree.canRead(path("a/b")), is(true));
assertThat(tree.canRead(path("m/n")), is(true));

// In case the native separator is a backslash, don't treat that as an escape
assertThat(tree.canRead(path("m\n")), is(false));
}

FileEntitlement entitlement(String path, String mode) {
Path p = path(path);
return new FileEntitlement(p.toString(), mode);
Expand Down