|
15 | 15 |
|
16 | 16 | import java.nio.file.Path; |
17 | 17 | import java.util.ArrayList; |
18 | | -import java.util.Arrays; |
19 | 18 | import java.util.HashMap; |
20 | 19 | import java.util.List; |
21 | 20 | import java.util.Map; |
@@ -53,33 +52,81 @@ static FileData ofPath(Path path, Mode mode) { |
53 | 52 | static FileData ofRelativePath(Path relativePath, BaseDir baseDir, Mode mode) { |
54 | 53 | return new RelativePathFileData(relativePath, baseDir, mode); |
55 | 54 | } |
56 | | - } |
57 | 55 |
|
58 | | - private record AbsolutePathFileData(Path path, Mode mode) implements FileData { |
59 | | - @Override |
60 | | - public Stream<Path> resolvePaths(PathLookup pathLookup) { |
61 | | - return Stream.of(path); |
| 56 | + static FileData ofPathSetting(String setting, Mode mode) { |
| 57 | + return new PathSettingFileData(setting, mode); |
| 58 | + } |
| 59 | + |
| 60 | + static FileData ofRelativePathSetting(String setting, BaseDir baseDir, Mode mode) { |
| 61 | + return new RelativePathSettingFileData(setting, baseDir, mode); |
62 | 62 | } |
63 | 63 | } |
64 | 64 |
|
65 | | - private record RelativePathFileData(Path relativePath, BaseDir baseDir, Mode mode) implements FileData { |
| 65 | + private sealed interface RelativeFileData extends FileData { |
| 66 | + BaseDir baseDir(); |
| 67 | + |
| 68 | + Stream<Path> resolveRelativePaths(PathLookup pathLookup); |
66 | 69 |
|
67 | 70 | @Override |
68 | | - public Stream<Path> resolvePaths(PathLookup pathLookup) { |
| 71 | + default Stream<Path> resolvePaths(PathLookup pathLookup) { |
69 | 72 | Objects.requireNonNull(pathLookup); |
70 | | - switch (baseDir) { |
| 73 | + var relativePaths = resolveRelativePaths(pathLookup); |
| 74 | + switch (baseDir()) { |
71 | 75 | case CONFIG: |
72 | | - return Stream.of(pathLookup.configDir().resolve(relativePath)); |
| 76 | + return relativePaths.map(relativePath -> pathLookup.configDir().resolve(relativePath)); |
73 | 77 | case DATA: |
74 | | - return Arrays.stream(pathLookup.dataDirs()).map(d -> d.resolve(relativePath)); |
| 78 | + // multiple data dirs are a pain...we need the combination of relative paths and data dirs |
| 79 | + List<Path> paths = new ArrayList<>(); |
| 80 | + for (var relativePath : relativePaths.toList()) { |
| 81 | + for (var dataDir : pathLookup.dataDirs()) { |
| 82 | + paths.add(dataDir.resolve(relativePath)); |
| 83 | + } |
| 84 | + } |
| 85 | + return paths.stream(); |
75 | 86 | case HOME: |
76 | | - return Stream.of(pathLookup.homeDir().resolve(relativePath)); |
| 87 | + return relativePaths.map(relativePath -> pathLookup.homeDir().resolve(relativePath)); |
77 | 88 | default: |
78 | 89 | throw new IllegalArgumentException(); |
79 | 90 | } |
80 | 91 | } |
81 | 92 | } |
82 | 93 |
|
| 94 | + private record AbsolutePathFileData(Path path, Mode mode) implements FileData { |
| 95 | + @Override |
| 96 | + public Stream<Path> resolvePaths(PathLookup pathLookup) { |
| 97 | + return Stream.of(path); |
| 98 | + } |
| 99 | + } |
| 100 | + |
| 101 | + private record RelativePathFileData(Path relativePath, BaseDir baseDir, Mode mode) implements FileData, RelativeFileData { |
| 102 | + @Override |
| 103 | + public Stream<Path> resolveRelativePaths(PathLookup pathLookup) { |
| 104 | + return Stream.of(relativePath); |
| 105 | + } |
| 106 | + } |
| 107 | + |
| 108 | + private record PathSettingFileData(String setting, Mode mode) implements FileData { |
| 109 | + @Override |
| 110 | + public Stream<Path> resolvePaths(PathLookup pathLookup) { |
| 111 | + return resolvePathSettings(pathLookup, setting); |
| 112 | + } |
| 113 | + } |
| 114 | + |
| 115 | + private record RelativePathSettingFileData(String setting, BaseDir baseDir, Mode mode) implements FileData, RelativeFileData { |
| 116 | + @Override |
| 117 | + public Stream<Path> resolveRelativePaths(PathLookup pathLookup) { |
| 118 | + return resolvePathSettings(pathLookup, setting); |
| 119 | + } |
| 120 | + } |
| 121 | + |
| 122 | + private static Stream<Path> resolvePathSettings(PathLookup pathLookup, String setting) { |
| 123 | + if (setting.contains("*")) { |
| 124 | + return pathLookup.settingGlobResolver().apply(setting).map(Path::of); |
| 125 | + } |
| 126 | + String path = pathLookup.settingResolver().apply(setting); |
| 127 | + return path == null ? Stream.of() : Stream.of(Path.of(path)); |
| 128 | + } |
| 129 | + |
83 | 130 | private static Mode parseMode(String mode) { |
84 | 131 | if (mode.equals("read")) { |
85 | 132 | return Mode.READ; |
@@ -113,37 +160,56 @@ public static FilesEntitlement build(List<Object> paths) { |
113 | 160 | String pathAsString = file.remove("path"); |
114 | 161 | String relativePathAsString = file.remove("relative_path"); |
115 | 162 | String relativeTo = file.remove("relative_to"); |
116 | | - String mode = file.remove("mode"); |
| 163 | + String pathSetting = file.remove("path_setting"); |
| 164 | + String relativePathSetting = file.remove("relative_path_setting"); |
| 165 | + String modeAsString = file.remove("mode"); |
117 | 166 |
|
118 | 167 | if (file.isEmpty() == false) { |
119 | 168 | throw new PolicyValidationException("unknown key(s) [" + file + "] in a listed file for files entitlement"); |
120 | 169 | } |
121 | | - if (mode == null) { |
| 170 | + int foundKeys = (pathAsString != null ? 1 : 0) + (relativePathAsString != null ? 1 : 0) + (pathSetting != null ? 1 : 0) |
| 171 | + + (relativePathSetting != null ? 1 : 0); |
| 172 | + if (foundKeys != 1) { |
| 173 | + throw new PolicyValidationException( |
| 174 | + "a files entitlement entry must contain one of " + "[path, relative_path, path_setting, relative_path_setting]" |
| 175 | + ); |
| 176 | + } |
| 177 | + |
| 178 | + if (modeAsString == null) { |
122 | 179 | throw new PolicyValidationException("files entitlement must contain 'mode' for every listed file"); |
123 | 180 | } |
124 | | - if (pathAsString != null && relativePathAsString != null) { |
125 | | - throw new PolicyValidationException("a files entitlement entry cannot contain both 'path' and 'relative_path'"); |
| 181 | + Mode mode = parseMode(modeAsString); |
| 182 | + |
| 183 | + BaseDir baseDir = null; |
| 184 | + if (relativeTo != null) { |
| 185 | + baseDir = parseBaseDir(relativeTo); |
126 | 186 | } |
127 | 187 |
|
128 | 188 | if (relativePathAsString != null) { |
129 | | - if (relativeTo == null) { |
| 189 | + if (baseDir == null) { |
130 | 190 | throw new PolicyValidationException("files entitlement with a 'relative_path' must specify 'relative_to'"); |
131 | 191 | } |
132 | | - final BaseDir baseDir = parseBaseDir(relativeTo); |
133 | 192 |
|
134 | 193 | Path relativePath = Path.of(relativePathAsString); |
135 | 194 | if (relativePath.isAbsolute()) { |
136 | 195 | throw new PolicyValidationException("'relative_path' [" + relativePathAsString + "] must be relative"); |
137 | 196 | } |
138 | | - filesData.add(FileData.ofRelativePath(relativePath, baseDir, parseMode(mode))); |
| 197 | + filesData.add(FileData.ofRelativePath(relativePath, baseDir, mode)); |
139 | 198 | } else if (pathAsString != null) { |
140 | 199 | Path path = Path.of(pathAsString); |
141 | 200 | if (path.isAbsolute() == false) { |
142 | 201 | throw new PolicyValidationException("'path' [" + pathAsString + "] must be absolute"); |
143 | 202 | } |
144 | | - filesData.add(FileData.ofPath(path, parseMode(mode))); |
| 203 | + filesData.add(FileData.ofPath(path, mode)); |
| 204 | + } else if (pathSetting != null) { |
| 205 | + filesData.add(FileData.ofPathSetting(pathSetting, mode)); |
| 206 | + } else if (relativePathSetting != null) { |
| 207 | + if (baseDir == null) { |
| 208 | + throw new PolicyValidationException("files entitlement with a 'relative_path_setting' must specify 'relative_to'"); |
| 209 | + } |
| 210 | + filesData.add(FileData.ofRelativePathSetting(relativePathSetting, baseDir, mode)); |
145 | 211 | } else { |
146 | | - throw new PolicyValidationException("files entitlement must contain either 'path' or 'relative_path' for every entry"); |
| 212 | + throw new AssertionError("File entry validation error"); |
147 | 213 | } |
148 | 214 | } |
149 | 215 | return new FilesEntitlement(filesData); |
|
0 commit comments