ILIAS  trunk Revision v13.0_alpha-62-g3b72d83f2e5
IsolationConfigTest.php
Go to the documentation of this file.
1<?php
2
19declare(strict_types=1);
20
22
24use PHPUnit\Framework\Attributes\DataProvider;
25use PHPUnit\Framework\TestCase;
26
30final class IsolationConfigTest extends TestCase
31{
33 private array $artefacts = [];
34
35 public function testDisabledFactory(): void
36 {
37 $config = IsolationConfig::disabled();
38
39 $this->assertFalse($config->isActivated());
40 $this->assertNull($config->getContentDomain());
41 $this->assertNull($config->getIliasDomain());
42 $this->assertNull($config->getContentHost());
43 $this->assertNull($config->getIliasHost());
44 }
45
46 public function testFromEmptyArrayIsDisabled(): void
47 {
48 $config = IsolationConfig::fromArray([]);
49
50 $this->assertFalse($config->isActivated());
51 $this->assertNull($config->getContentDomain());
52 $this->assertNull($config->getIliasDomain());
53 }
54
55 public function testFromArrayWithFullSpecExample(): void
56 {
57 // exact artefact shape from the feature spec
60 IsolationConfig::KEY_CONTENT_DOMAIN => 'https://test11.iliascontent.de',
61 IsolationConfig::KEY_ILIAS_DOMAIN => 'https://test11.ilias.de',
62 ]);
63
64 $this->assertTrue($config->isActivated());
65 $this->assertSame('https://test11.iliascontent.de', $config->getContentDomain());
66 $this->assertSame('https://test11.ilias.de', $config->getIliasDomain());
67 $this->assertSame('test11.iliascontent.de', $config->getContentHost());
68 $this->assertSame('test11.ilias.de', $config->getIliasHost());
69 }
70
71 public function testBareHostnamesGetHttpsScheme(): void
72 {
75 IsolationConfig::KEY_CONTENT_DOMAIN => 'content.example.org',
76 IsolationConfig::KEY_ILIAS_DOMAIN => 'app.example.org',
77 ]);
78
79 $this->assertSame('https://content.example.org', $config->getContentDomain());
80 $this->assertSame('https://app.example.org', $config->getIliasDomain());
81 $this->assertSame('content.example.org', $config->getContentHost());
82 $this->assertSame('app.example.org', $config->getIliasHost());
83 }
84
85 public function testTrailingSlashIsStripped(): void
86 {
89 IsolationConfig::KEY_CONTENT_DOMAIN => 'https://content.example.org/',
90 IsolationConfig::KEY_ILIAS_DOMAIN => 'https://app.example.org/',
91 ]);
92
93 $this->assertSame('https://content.example.org', $config->getContentDomain());
94 $this->assertSame('https://app.example.org', $config->getIliasDomain());
95 }
96
98 {
101 IsolationConfig::KEY_ILIAS_DOMAIN => 'https://app.example.org',
102 ]);
103
104 $this->assertFalse($config->isActivated());
105 }
106
108 {
109 // The ILIAS domain is optional in the artefact: when http_path could not
110 // be resolved at build time it is simply null (the CORS allow-origin
111 // header is then omitted), isolation stays on.
114 IsolationConfig::KEY_CONTENT_DOMAIN => 'https://content.example.org',
115 ]);
116
117 $this->assertTrue($config->isActivated());
118 $this->assertSame('https://content.example.org', $config->getContentDomain());
119 $this->assertNull($config->getIliasDomain());
120 }
121
122 public function testInactiveStillParsesDomains(): void
123 {
126 IsolationConfig::KEY_CONTENT_DOMAIN => 'https://content.example.org',
127 IsolationConfig::KEY_ILIAS_DOMAIN => 'https://app.example.org',
128 ]);
129
130 $this->assertFalse($config->isActivated());
131 $this->assertSame('https://content.example.org', $config->getContentDomain());
132 $this->assertSame('https://app.example.org', $config->getIliasDomain());
133 }
134
135 #[DataProvider('unusableDomainProvider')]
136 public function testUnusableDomainsNormalizeToNull(mixed $value): void
137 {
141 IsolationConfig::KEY_ILIAS_DOMAIN => 'https://app.example.org',
142 ]);
143
144 // unusable content domain -> null -> isolation forced off
145 $this->assertNull($config->getContentDomain());
146 $this->assertFalse($config->isActivated());
147 }
148
149 public static function unusableDomainProvider(): \Iterator
150 {
151 yield 'empty string' => [''];
152 yield 'whitespace only' => [' '];
153 yield 'scheme without host' => ['https://'];
154 yield 'non-string int' => [123];
155 yield 'non-string bool' => [true];
156 yield 'null' => [null];
157 yield 'array' => [['x']];
158 }
159
160 public function testToArrayUsesSpecKeys(): void
161 {
164 IsolationConfig::KEY_CONTENT_DOMAIN => 'https://content.example.org',
165 IsolationConfig::KEY_ILIAS_DOMAIN => 'https://app.example.org',
166 ]);
167
168 $this->assertSame([
170 IsolationConfig::KEY_CONTENT_DOMAIN => 'https://content.example.org',
171 IsolationConfig::KEY_ILIAS_DOMAIN => 'https://app.example.org',
172 ], $config->toArray());
173 }
174
176 {
177 $source = [
179 IsolationConfig::KEY_CONTENT_DOMAIN => 'content.example.org',
180 IsolationConfig::KEY_ILIAS_DOMAIN => 'app.example.org',
181 ];
182
183 $once = IsolationConfig::fromArray($source)->toArray();
184 $twice = IsolationConfig::fromArray($once)->toArray();
185
186 $this->assertSame($once, $twice);
187 }
188
189 #[DataProvider('originNormalizationProvider')]
191 string $input,
192 string $expected_domain,
193 string $expected_host
194 ): void {
195 $config = IsolationConfig::fromArray([
196 IsolationConfig::KEY_ACTIVATED => false,
197 IsolationConfig::KEY_CONTENT_DOMAIN => $input,
198 IsolationConfig::KEY_ILIAS_DOMAIN => 'https://app.example.org',
199 ]);
200
201 $this->assertSame($expected_domain, $config->getContentDomain());
202 $this->assertSame($expected_host, $config->getContentHost());
203 }
204
205 public static function originNormalizationProvider(): \Iterator
206 {
207 yield 'bare host gets https' => ['content.example.org', 'https://content.example.org', 'content.example.org'];
208 yield 'https kept' => ['https://content.example.org', 'https://content.example.org', 'content.example.org'];
209 yield 'http kept' => ['http://localhost', 'http://localhost', 'localhost'];
210 yield 'port preserved' => ['https://content.example.org:8443', 'https://content.example.org:8443', 'content.example.org'];
211 yield 'host lowercased' => ['https://Content.Example.ORG', 'https://content.example.org', 'content.example.org'];
212 yield 'root slash stripped' => ['https://content.example.org/', 'https://content.example.org', 'content.example.org'];
213 }
214
215 #[DataProvider('rejectedOriginProvider')]
216 public function testNonBareOriginsAreRejected(string $input): void
217 {
221 IsolationConfig::KEY_ILIAS_DOMAIN => 'https://app.example.org',
222 ]);
223
224 $this->assertNull($config->getContentDomain());
225 $this->assertNull($config->getContentHost());
226 }
227
228 public static function rejectedOriginProvider(): \Iterator
229 {
230 yield 'with path' => ['https://content.example.org/foo'];
231 yield 'with userinfo' => ['https://user:pass@content.example.org'];
232 yield 'with query' => ['https://content.example.org/?a=b'];
233 yield 'with fragment' => ['https://content.example.org/#x'];
234 yield 'ftp scheme' => ['ftp://content.example.org'];
235 yield 'file scheme' => ['file:///etc/passwd'];
236 yield 'javascript scheme' => ['javascript:alert(1)'];
237 }
238
239 #[DataProvider('trustedOriginProvider')]
240 public function testOriginFromUrlReducesTrustedAppUrlToBareOrigin(?string $input, ?string $expected): void
241 {
242 // originFromUrl is used at build time to derive the ILIAS domain from
243 // http_path, which may carry a sub-directory path that must be discarded.
244 $this->assertSame($expected, IsolationConfig::originFromUrl($input));
245 }
246
247 public static function trustedOriginProvider(): \Iterator
248 {
249 yield 'bare origin' => ['https://app.example.org', 'https://app.example.org'];
250 yield 'path discarded' => ['https://app.example.org/ilias/index.php', 'https://app.example.org'];
251 yield 'query discarded' => ['https://app.example.org/?a=b', 'https://app.example.org'];
252 yield 'bare host gets https' => ['app.example.org', 'https://app.example.org'];
253 yield 'http kept' => ['http://localhost/sub', 'http://localhost'];
254 yield 'port preserved' => ['https://app.example.org:8443/sub', 'https://app.example.org:8443'];
255 yield 'empty stays null' => ['', null];
256 yield 'null stays null' => [null, null];
257 yield 'non-http scheme rejected' => ['ftp://app.example.org', null];
258 }
259
260 private function artefact(string $php): string
261 {
262 $path = (string) tempnam(sys_get_temp_dir(), 'iso_artefact_') . '.php';
263 file_put_contents($path, $php);
264 $this->artefacts[] = $path;
265 return $path;
266 }
267
268 protected function tearDown(): void
269 {
270 foreach ($this->artefacts as $path) {
271 if (is_file($path)) {
272 unlink($path);
273 }
274 }
275 $this->artefacts = [];
276 }
277
279 {
280 $config = IsolationConfig::fromArtefact(sys_get_temp_dir() . '/does_not_exist_isolation.php');
281
282 $this->assertFalse($config->isActivated());
283 $this->assertNull($config->getContentDomain());
284 $this->assertNull($config->getIliasDomain());
285 }
286
288 {
289 $path = $this->artefact(
290 "<?php return ['activated' => true, 'content_domain' => 'content.example.org',"
291 . " 'ilias_domain' => 'https://app.example.org'];"
292 );
293 $config = IsolationConfig::fromArtefact($path);
294
295 $this->assertTrue($config->isActivated());
296 $this->assertSame('https://content.example.org', $config->getContentDomain());
297 $this->assertSame('https://app.example.org', $config->getIliasDomain());
298 }
299
301 {
302 $config = IsolationConfig::fromArtefact($this->artefact('<?php return "nonsense";'));
303
304 $this->assertFalse($config->isActivated());
305 $this->assertNull($config->getContentDomain());
306 }
307
309 {
310 $config = IsolationConfig::fromArtefact($this->artefact('<?php // nothing returned'));
311
312 $this->assertFalse($config->isActivated());
313 $this->assertNull($config->getContentDomain());
314 }
315}
Read-only configuration of the IRSS User Content Isolation feature.
static originFromUrl(mixed $value)
Reduce a (trusted) application URL to its bare origin "scheme://host[:port]".
static fromArtefact(?string $path=null)
Load the configuration from the static artefact written by the setup.
testValidValuesNormalizeToBareOrigin(string $input, string $expected_domain, string $expected_host)
testOriginFromUrlReducesTrustedAppUrlToBareOrigin(?string $input, ?string $expected)