19declare(strict_types=1);
26use PHPUnit\Framework\TestCase;
37 private const string MODULE =
'stest';
44 protected function setUp(): void
50 if (!defined(
'ILIAS_ABSOLUTE_PATH')) {
51 define(
'ILIAS_ABSOLUTE_PATH', realpath(__DIR__ .
'/../../../../../'));
53 if (!function_exists(
'posix_getpwuid') || !function_exists(
'posix_getgrgid')) {
54 $this->markTestSkipped(
'Requires the POSIX extension (posix_getpwuid()/posix_getgrgid()).');
57 $this->fixture_directory = __DIR__ .
'/tmp-ownership-fixtures-' . bin2hex(random_bytes(4));
58 mkdir($this->fixture_directory, 0775,
true);
59 $this->client_data_dir = sys_get_temp_dir() .
'/ilias_mlfs_ownership_test_' . bin2hex(random_bytes(4));
60 mkdir($this->client_data_dir, 0775,
true);
64 'components/ILIAS/Language/tests/ComponentTranslation/' . basename($this->fixture_directory) .
'/'
68 $this->fixture_directory .
'/' . self::MODULE .
'_de.po',
94 return MigratedLanguageFileSync::findUnwritableOverlayDirectories(
98 $this->client_data_dir,
110 if (!is_dir(dirname($overlay_directory))) {
111 mkdir(dirname($overlay_directory), 0775,
true);
113 if (!is_dir($overlay_directory)) {
114 mkdir($overlay_directory, 0775);
116 chmod($overlay_directory, $mode);
117 clearstatcache(
true, $overlay_directory);
140 $this->
assertNotReported(0000, 0,
'root (uid 0) must always be able to create/replace files');
150 if (posix_getuid() === 0) {
151 $this->markTestSkipped(
152 'The current process is root, so its own uid would only hit the for_user_id===0 short-circuit '
153 .
'(see testRootMayAlwaysWriteRegardlessOfMode()) instead of the owner branch under test; '
154 .
'testAnArbitraryChownedOwnerNeedsTheOwnerWriteAndSearchBits() covers the owner branch as root.'
158 $owner = posix_getuid();
163 'the owner lacking the write bit must be reported even though group/other have it'
168 'the owner having the write+search bits must be enough on its own'
178 if (!function_exists(
'posix_geteuid') || posix_geteuid() !== 0) {
179 $this->markTestSkipped(
'Requires root to chown() the overlay directory to an arbitrary owner.');
189 'the chown()ed owner lacking the write bit must be reported'
198 'the chown()ed owner having the write+search bits must not be reported'
214 'other lacking the write bit must be reported even though owner/group have it'
219 'other having the write+search bits must be enough for a stranger'
229 if (!function_exists(
'posix_geteuid') || posix_geteuid() !== 0) {
230 $this->markTestSkipped(
'Requires root to chown()/chgrp() the overlay directory to an arbitrary owner/group.');
233 $group_member_uid = 0;
235 foreach (array_merge([33], range(1, 1000)) as $uid) {
236 $user = posix_getpwuid($uid);
237 if ($user !==
false && $user[
'gid'] !== 0) {
238 [$group_member_uid, $group_gid] = [$uid, $user[
'gid']];
242 if ($group_member_uid === 0) {
243 $this->markTestSkipped(
'No unprivileged user with an own primary group found.');
253 'the group lacking the write bit must be reported even though the (different) owner has it'
262 'the group having the write+search bits must be enough for a group member who is not the owner'
trait RestoresProcessState
Whatever a test does to the process (locale, LANGUAGE/LC_*, LOCPATH, the static state of the runtime ...
The single place that knows where the files of a module migrated to PO/MO live:
Reads the shipped .po of a migrated module and maintains its per-installation overlay: the ....
static directory(string $prefix, string $path, bool $local=false)
static catalog(string $module, array $entries)
static writePo(string $file, TranslationCatalog $catalog)
static removeDirectory(string $directory)
static overlayBase(string $client_data_dir, LanguageFileDirectory $directory, string $lang_key)
The overlay of $directory's module for $lang_key without the .po extension.
static resetRuntime(?string $artifact_directory=null)
Forgets the static state of the runtime read path and points build and runtime at $artifact_directory...
MigratedLanguageFileSync::findUnwritableOverlayDirectories() with $for_user_id (private isWritableDir...
string $fixture_directory
testAGroupMemberNeedsTheGroupWriteAndSearchBitsRegardlessOfOwnerOrOtherBits()
Group branch: a uid that is not the owner but shares the directory's group (chgrp()'d to $for_user_id...
testRootMayAlwaysWriteRegardlessOfMode()
Mutation: the $for_user_id === 0 short-circuit being removed - root must be considered able to write ...
testTheOwnerNeedsTheOwnerWriteAndSearchBitsRegardlessOfGroupOrOtherBits()
Mutation: the owner branch using the wrong mask (e.g.
seedOverlayDirectory(int $mode)
Only the leaf overlay directory gets $mode; parents stay 0775, since a restrictive intermediate mode ...
testAnArbitraryChownedOwnerNeedsTheOwnerWriteAndSearchBits()
As above, with a directory chown()ed to a non-current uid - proves $stat['uid'] is evaluated against ...
assertReported(int $mode, int $for_user_id, string $message)
LanguageFileDirectoryManager $manager
findUnwritable(?int $for_user_id)
assertNotReported(int $mode, int $for_user_id, string $message)
LanguageFileDirectory $directory
testAForeignUidNeedsTheOtherWriteAndSearchBitsEvenWhenOwnerAndGroupHaveThem()
Mutation: the "other" branch being skipped, or using the owner/group mask instead - a uid that is nei...
A directory of language files with a path and a prefix for them.