| 
11 | 11 | 
 
  | 
12 | 12 | import org.elasticsearch.common.settings.Settings;  | 
13 | 13 | import org.elasticsearch.core.SuppressForbidden;  | 
 | 14 | +import org.elasticsearch.entitlement.runtime.policy.FileAccessTree.ExclusiveFileEntitlement;  | 
14 | 15 | import org.elasticsearch.entitlement.runtime.policy.FileAccessTree.ExclusivePath;  | 
15 | 16 | import org.elasticsearch.entitlement.runtime.policy.entitlements.FilesEntitlement;  | 
 | 17 | +import org.elasticsearch.entitlement.runtime.policy.entitlements.FilesEntitlement.FileData;  | 
16 | 18 | import org.elasticsearch.test.ESTestCase;  | 
17 | 19 | import org.junit.BeforeClass;  | 
18 | 20 | 
 
  | 
 | 
26 | 28 | import java.util.Map;  | 
27 | 29 | 
 
  | 
28 | 30 | import static org.elasticsearch.core.PathUtils.getDefaultFileSystem;  | 
 | 31 | +import static org.elasticsearch.entitlement.runtime.policy.FileAccessTree.buildExclusivePathList;  | 
 | 32 | +import static org.elasticsearch.entitlement.runtime.policy.FileAccessTree.normalizePath;  | 
 | 33 | +import static org.elasticsearch.entitlement.runtime.policy.Platform.WINDOWS;  | 
 | 34 | +import static org.elasticsearch.entitlement.runtime.policy.entitlements.FilesEntitlement.Mode.READ;  | 
 | 35 | +import static org.elasticsearch.entitlement.runtime.policy.entitlements.FilesEntitlement.Mode.READ_WRITE;  | 
29 | 36 | import static org.hamcrest.Matchers.equalTo;  | 
30 | 37 | import static org.hamcrest.Matchers.is;  | 
31 | 38 | 
 
  | 
@@ -195,7 +202,7 @@ public void testNormalizePath() {  | 
195 | 202 |     }  | 
196 | 203 | 
 
  | 
197 | 204 |     public void testNormalizeDirectorySeparatorWindows() {  | 
198 |  | -        assumeTrue("normalization of windows paths", Platform.WINDOWS.isCurrent());  | 
 | 205 | +        assumeTrue("normalization of windows paths", WINDOWS.isCurrent());  | 
199 | 206 | 
 
  | 
200 | 207 |         assertThat(FileAccessTree.normalizePath(Path.of("C:\\a\\b")), equalTo("C:\\a\\b"));  | 
201 | 208 |         assertThat(FileAccessTree.normalizePath(Path.of("C:/a.xml")), equalTo("C:\\a.xml"));  | 
@@ -254,7 +261,7 @@ public void testJdkAccess() {  | 
254 | 261 | 
 
  | 
255 | 262 |     @SuppressForbidden(reason = "don't care about the directory location in tests")  | 
256 | 263 |     public void testFollowLinks() throws IOException {  | 
257 |  | -        assumeFalse("Windows requires admin right to create symbolic links", Platform.WINDOWS.isCurrent());  | 
 | 264 | +        assumeFalse("Windows requires admin right to create symbolic links", WINDOWS.isCurrent());  | 
258 | 265 | 
 
  | 
259 | 266 |         Path baseSourceDir = Files.createTempDirectory("fileaccess_source");  | 
260 | 267 |         Path source1Dir = baseSourceDir.resolve("source1");  | 
@@ -347,18 +354,102 @@ public void testInvalidExclusiveAccess() {  | 
347 | 354 |         assertThat(tree.canWrite(path("a")), is(false));  | 
348 | 355 |     }  | 
349 | 356 | 
 
  | 
 | 357 | +    public void testDuplicatePrunedPaths() {  | 
 | 358 | +        List<String> inputPaths = List.of("/a", "/a", "/a/b", "/a/b", "/b/c", "b/c/d", "b/c/d", "b/c/d", "e/f", "e/f");  | 
 | 359 | +        List<String> outputPaths = List.of("/a", "/b/c", "b/c/d", "e/f");  | 
 | 360 | +        var actual = FileAccessTree.pruneSortedPaths(inputPaths.stream().map(p -> normalizePath(path(p))).toList());  | 
 | 361 | +        var expected = outputPaths.stream().map(p -> normalizePath(path(p))).toList();  | 
 | 362 | +        assertEquals(expected, actual);  | 
 | 363 | +    }  | 
 | 364 | + | 
 | 365 | +    public void testDuplicateExclusivePaths() {  | 
 | 366 | +        // Bunch o' handy definitions  | 
 | 367 | +        var originalFileData = FileData.ofPath(path("/a/b"), READ).withExclusive(true);  | 
 | 368 | +        var fileDataWithWriteMode = FileData.ofPath(path("/a/b"), READ_WRITE).withExclusive(true);  | 
 | 369 | +        var original = new ExclusiveFileEntitlement("component1", "module1", new FilesEntitlement(List.of(originalFileData)));  | 
 | 370 | +        var differentComponent = new ExclusiveFileEntitlement("component2", original.moduleName(), original.filesEntitlement());  | 
 | 371 | +        var differentModule = new ExclusiveFileEntitlement(original.componentName(), "module2", original.filesEntitlement());  | 
 | 372 | +        var differentPath = new ExclusiveFileEntitlement(  | 
 | 373 | +            original.componentName(),  | 
 | 374 | +            original.moduleName(),  | 
 | 375 | +            new FilesEntitlement(  | 
 | 376 | +                List.of(FileData.ofPath(path("/c/d"), originalFileData.mode()).withExclusive(originalFileData.exclusive()))  | 
 | 377 | +            )  | 
 | 378 | +        );  | 
 | 379 | +        var differentMode = new ExclusiveFileEntitlement(  | 
 | 380 | +            original.componentName(),  | 
 | 381 | +            original.moduleName(),  | 
 | 382 | +            new FilesEntitlement(List.of(fileDataWithWriteMode))  | 
 | 383 | +        );  | 
 | 384 | +        var differentPlatform = new ExclusiveFileEntitlement(  | 
 | 385 | +            original.componentName(),  | 
 | 386 | +            original.moduleName(),  | 
 | 387 | +            new FilesEntitlement(List.of(originalFileData.withPlatform(WINDOWS)))  | 
 | 388 | +        );  | 
 | 389 | +        var originalExclusivePath = new ExclusivePath("component1", "module1", normalizePath(path("/a/b")));  | 
 | 390 | + | 
 | 391 | +        // Some basic tests  | 
 | 392 | + | 
 | 393 | +        assertEquals(  | 
 | 394 | +            "Single element should trivially work",  | 
 | 395 | +            List.of(originalExclusivePath),  | 
 | 396 | +            buildExclusivePathList(List.of(original), TEST_PATH_LOOKUP)  | 
 | 397 | +        );  | 
 | 398 | +        assertEquals(  | 
 | 399 | +            "Two identical elements should be combined",  | 
 | 400 | +            List.of(originalExclusivePath),  | 
 | 401 | +            buildExclusivePathList(List.of(original, original), TEST_PATH_LOOKUP)  | 
 | 402 | +        );  | 
 | 403 | + | 
 | 404 | +        // Don't merge things we shouldn't  | 
 | 405 | + | 
 | 406 | +        var distinctEntitlements = List.of(original, differentComponent, differentModule, differentPath);  | 
 | 407 | +        var distinctPaths = List.of(  | 
 | 408 | +            originalExclusivePath,  | 
 | 409 | +            new ExclusivePath("component2", original.moduleName(), originalExclusivePath.path()),  | 
 | 410 | +            new ExclusivePath(original.componentName(), "module2", originalExclusivePath.path()),  | 
 | 411 | +            new ExclusivePath(original.componentName(), original.moduleName(), normalizePath(path("/c/d")))  | 
 | 412 | +        );  | 
 | 413 | +        assertEquals(  | 
 | 414 | +            "Distinct elements should not be combined",  | 
 | 415 | +            distinctPaths,  | 
 | 416 | +            buildExclusivePathList(distinctEntitlements, TEST_PATH_LOOKUP)  | 
 | 417 | +        );  | 
 | 418 | + | 
 | 419 | +        // Do merge things we should  | 
 | 420 | + | 
 | 421 | +        List<ExclusiveFileEntitlement> interleavedEntitlements = new ArrayList<>();  | 
 | 422 | +        distinctEntitlements.forEach(e -> {  | 
 | 423 | +            interleavedEntitlements.add(e);  | 
 | 424 | +            interleavedEntitlements.add(original);  | 
 | 425 | +        });  | 
 | 426 | +        assertEquals(  | 
 | 427 | +            "Identical elements should be combined wherever they are in the list",  | 
 | 428 | +            distinctPaths,  | 
 | 429 | +            buildExclusivePathList(interleavedEntitlements, TEST_PATH_LOOKUP)  | 
 | 430 | +        );  | 
 | 431 | + | 
 | 432 | +        var equivalentEntitlements = List.of(original, differentMode, differentPlatform);  | 
 | 433 | +        var equivalentPaths = List.of(originalExclusivePath);  | 
 | 434 | +        assertEquals(  | 
 | 435 | +            "Exclusive paths should be combined even if the entitlements are different",  | 
 | 436 | +            equivalentPaths,  | 
 | 437 | +            buildExclusivePathList(equivalentEntitlements, TEST_PATH_LOOKUP)  | 
 | 438 | +        );  | 
 | 439 | +    }  | 
 | 440 | + | 
350 | 441 |     public void testWindowsAbsolutPathAccess() {  | 
351 |  | -        assumeTrue("Specific to windows for paths with a root (DOS or UNC)", Platform.WINDOWS.isCurrent());  | 
 | 442 | +        assumeTrue("Specific to windows for paths with a root (DOS or UNC)", WINDOWS.isCurrent());  | 
352 | 443 | 
 
  | 
353 | 444 |         var fileAccessTree = FileAccessTree.of(  | 
354 | 445 |             "test",  | 
355 | 446 |             "test",  | 
356 | 447 |             new FilesEntitlement(  | 
357 | 448 |                 List.of(  | 
358 |  | -                    FilesEntitlement.FileData.ofPath(Path.of("\\\\.\\pipe\\"), FilesEntitlement.Mode.READ),  | 
359 |  | -                    FilesEntitlement.FileData.ofPath(Path.of("D:\\.gradle"), FilesEntitlement.Mode.READ),  | 
360 |  | -                    FilesEntitlement.FileData.ofPath(Path.of("D:\\foo"), FilesEntitlement.Mode.READ),  | 
361 |  | -                    FilesEntitlement.FileData.ofPath(Path.of("C:\\foo"), FilesEntitlement.Mode.READ_WRITE)  | 
 | 449 | +                    FileData.ofPath(Path.of("\\\\.\\pipe\\"), READ),  | 
 | 450 | +                    FileData.ofPath(Path.of("D:\\.gradle"), READ),  | 
 | 451 | +                    FileData.ofPath(Path.of("D:\\foo"), READ),  | 
 | 452 | +                    FileData.ofPath(Path.of("C:\\foo"), FilesEntitlement.Mode.READ_WRITE)  | 
362 | 453 |                 )  | 
363 | 454 |             ),  | 
364 | 455 |             TEST_PATH_LOOKUP,  | 
@@ -394,7 +485,7 @@ static FilesEntitlement entitlement(Map<String, String> value) {  | 
394 | 485 |     static List<ExclusivePath> exclusivePaths(String componentName, String moduleName, String... paths) {  | 
395 | 486 |         List<ExclusivePath> exclusivePaths = new ArrayList<>();  | 
396 | 487 |         for (String path : paths) {  | 
397 |  | -            exclusivePaths.add(new ExclusivePath(componentName, moduleName, path(path).toString()));  | 
 | 488 | +            exclusivePaths.add(new ExclusivePath(componentName, moduleName, normalizePath(path(path))));  | 
398 | 489 |         }  | 
399 | 490 |         return exclusivePaths;  | 
400 | 491 |     }  | 
 | 
0 commit comments