Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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 @@ -21,6 +21,8 @@
import java.util.Objects;
import java.util.stream.Stream;

import static java.lang.Character.isLetter;

/**
* Describes a file entitlement with a path and mode.
*/
Expand Down Expand Up @@ -60,6 +62,51 @@ static FileData ofPathSetting(String setting, Mode mode) {
static FileData ofRelativePathSetting(String setting, BaseDir baseDir, Mode mode) {
return new RelativePathSettingFileData(setting, baseDir, mode);
}

/**
* Tests if a path is absolute or relative, taking into consideration both Unix and Windows conventions.
* Note that this leads to a conflict, resolved in favor of Unix rules: `/foo` can be either a Unix absolute path, or a Windows
* relative path with "wrong" directory separator (using non-canonical slash in Windows).
*/
static boolean isAbsolutePath(String path) {
if (path.isEmpty()) {
return false;
}
if (path.charAt(0) == '/') {
// Unix/BSD absolute
return true;
}

return isWindowsAbsolutePath(path);
}

private static boolean isSlash(char c) {
return (c == '\\') || (c == '/');
}

private static boolean isWindowsAbsolutePath(String input) {
// if a prefix is present, we expected (long) UNC or (long) absolute
if (input.startsWith("\\\\?\\")) {
return true;
}

if (input.length() > 1) {
char c0 = input.charAt(0);
char c1 = input.charAt(1);
char c = 0;
int next = 2;
if (isSlash(c0) && isSlash(c1)) {
// Two slashes or more: UNC
return true;
}
if (isLetter(c0) && c1 == ':') {
// A drive: absolute
return true;
}
}
// Otherwise relative
return false;
}
}

private sealed interface RelativeFileData extends FileData {
Expand Down Expand Up @@ -190,17 +237,15 @@ public static FilesEntitlement build(List<Object> paths) {
throw new PolicyValidationException("files entitlement with a 'relative_path' must specify 'relative_to'");
}

Path relativePath = Path.of(relativePathAsString);
if (relativePath.isAbsolute()) {
if (FileData.isAbsolutePath(relativePathAsString)) {
throw new PolicyValidationException("'relative_path' [" + relativePathAsString + "] must be relative");
}
filesData.add(FileData.ofRelativePath(relativePath, baseDir, mode));
filesData.add(FileData.ofRelativePath(Path.of(relativePathAsString), baseDir, mode));
} else if (pathAsString != null) {
Path path = Path.of(pathAsString);
if (path.isAbsolute() == false) {
if (FileData.isAbsolutePath(pathAsString) == false) {
throw new PolicyValidationException("'path' [" + pathAsString + "] must be absolute");
}
filesData.add(FileData.ofPath(path, mode));
filesData.add(FileData.ofPath(Path.of(pathAsString), mode));
} else if (pathSetting != null) {
filesData.add(FileData.ofPathSetting(pathSetting, mode));
} else if (relativePathSetting != null) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the "Elastic License
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
* Public License v 1"; you may not use this file except in compliance with, at
* your election, the "Elastic License 2.0", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/

package org.elasticsearch.entitlement.runtime.policy.entitlements;

import org.elasticsearch.test.ESTestCase;

import static org.elasticsearch.entitlement.runtime.policy.entitlements.FilesEntitlement.FileData.isAbsolutePath;
import static org.hamcrest.Matchers.is;

public class FileDataTests extends ESTestCase {

public void testPathIsAbsolute() {
var windowsNamedPipe = "\\\\.\\pipe";
var windowsDosAbsolutePath = "C:\\temp";
var unixAbsolutePath = "/tmp/foo";
var unixStyleUncPath = "//C/temp";
var uncPath = "\\\\C\\temp";
var longPath = "\\\\?\\C:\\temp";

var relativePath = "foo";
var headingSlashRelativePath = "\\foo";

assertThat(isAbsolutePath(windowsNamedPipe), is(true));
assertThat(isAbsolutePath(windowsDosAbsolutePath), is(true));
assertThat(isAbsolutePath(unixAbsolutePath), is(true));
assertThat(isAbsolutePath(unixStyleUncPath), is(true));
assertThat(isAbsolutePath(uncPath), is(true));
assertThat(isAbsolutePath(longPath), is(true));

assertThat(isAbsolutePath(relativePath), is(false));
assertThat(isAbsolutePath(headingSlashRelativePath), is(false));
assertThat(isAbsolutePath(""), is(false));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -60,12 +60,36 @@ public void testInvalidRelativeDirectory() {
assertThat(ex.getMessage(), is("invalid relative directory: bar, valid values: [config, data, home]"));
}

public void testFileDataRelativeWithEmptyDirectory() {
public void testFileDataRelativeWithAbsoluteDirectoryFails() {
var fileData = FileData.ofRelativePath(Path.of(""), FilesEntitlement.BaseDir.DATA, READ_WRITE);
var dataDirs = fileData.resolvePaths(TEST_PATH_LOOKUP);
assertThat(dataDirs.toList(), contains(Path.of("/data1/"), Path.of("/data2")));
}

public void testFileDataAbsoluteWithRelativeDirectoryFails() {
var ex = expectThrows(
PolicyValidationException.class,
() -> FilesEntitlement.build(List.of((Map.of("path", "foo", "mode", "read"))))
);

assertThat(ex.getMessage(), is("'path' [foo] must be absolute"));
}

public void testFileDataRelativeWithEmptyDirectory() {
var ex = expectThrows(
PolicyValidationException.class,
() -> FilesEntitlement.build(List.of((Map.of("relative_path", "/foo", "mode", "read", "relative_to", "config"))))
);

var ex2 = expectThrows(
PolicyValidationException.class,
() -> FilesEntitlement.build(List.of((Map.of("relative_path", "C:\\foo", "mode", "read", "relative_to", "config"))))
);

assertThat(ex.getMessage(), is("'relative_path' [/foo] must be relative"));
assertThat(ex2.getMessage(), is("'relative_path' [C:\\foo] must be relative"));
}

public void testPathSettingResolve() {
var entitlement = FilesEntitlement.build(List.of(Map.of("path_setting", "foo.bar", "mode", "read")));
var filesData = entitlement.filesData();
Expand Down