diff --git a/CHANGELOG.md b/CHANGELOG.md index acc821c29..4379beafd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -32,6 +32,13 @@ The major version bump is due to upping the required PHP version from `8.1` to ` - Time series - Top-k * Added `Redis::executeCommand()` helper method that makes it possible to call commands not yet supported by the client. +* Added `Permission` enum with the following methods to make it easier to interact with file permissions: + - `Permission::calculate()` + - `Permission::hasPermissions()` +* Added `FileSystem::setPermissions()` method that accepts `Permission` enum values. +* Added `FileSystem::hasPermissions()` method. +* Added `FileInfo::hasPermissions()` method. + #### Changes * The gatekeeper `Session::login()` and `Session::forceLogin()` methods will now return a `LoginStatus` enum instance instead of a mix of boolean and integer values. diff --git a/src/mako/file/FileInfo.php b/src/mako/file/FileInfo.php index 371efb0f0..74c321112 100644 --- a/src/mako/file/FileInfo.php +++ b/src/mako/file/FileInfo.php @@ -81,4 +81,12 @@ public function validateHmac(string $hmac, #[SensitiveParameter] string $key, st { return hash_equals($hmac, $this->getHmac($key, $algorithm, $raw)); } + + /** + * Returns TRUE if the file permissions contain the specified permissions and FALSE if not. + */ + public function hasPermissions(Permission ...$permission): bool + { + return Permission::hasPermissions($this->getPerms() & Permission::FULL->value, ...$permission); + } } diff --git a/src/mako/file/FileSystem.php b/src/mako/file/FileSystem.php index 538c18463..03bbdd51c 100644 --- a/src/mako/file/FileSystem.php +++ b/src/mako/file/FileSystem.php @@ -10,6 +10,7 @@ use FilesystemIterator; use SplFileObject; +use function chmod; use function copy; use function disk_free_space; use function disk_total_space; @@ -17,6 +18,7 @@ use function file_get_contents; use function file_put_contents; use function filemtime; +use function fileperms; use function filesize; use function getcwd; use function glob; @@ -75,6 +77,22 @@ public function isEmpty(string $path): bool return filesize($path) === 0; } + /** + * Sets the file permissions. + */ + public function setPermissions(string $path, Permission ...$permission): bool + { + return chmod($path, Permission::calculate(...$permission)); + } + + /** + * Returns TRUE if the file permissions contain the specified permissions and FALSE if not. + */ + public function hasPermission(string $path, Permission ...$permission): bool + { + return Permission::hasPermissions(fileperms($path) & Permission::FULL->value, ...$permission); + } + /** * Returns TRUE if the file is readable and FALSE if not. */ diff --git a/src/mako/file/Permission.php b/src/mako/file/Permission.php new file mode 100644 index 000000000..dc75739ed --- /dev/null +++ b/src/mako/file/Permission.php @@ -0,0 +1,77 @@ +value; + + foreach ($permission as $_permission) { + $permissions |= $_permission->value; + } + + return $permissions; + } + + /** + * Returns TRUE if the permissions contain the specified permissions and FALSE if not. + */ + public static function hasPermissions(int $permissions, Permission ...$permission): bool + { + if ($permissions < 0o000 || $permissions > 0o777) { + throw new InvalidArgumentException(vsprintf('The integer %s does not represent a valid octal between 0o000 and 0o777.', [$permissions])); + } + + $permission = static::calculate(...$permission); + + if ($permission === 0o000) { + return $permissions === 0o000; + } + + return ($permissions & $permission) === $permission; + } +} diff --git a/tests/unit/file/PermissionTest.php b/tests/unit/file/PermissionTest.php new file mode 100644 index 000000000..80fb366da --- /dev/null +++ b/tests/unit/file/PermissionTest.php @@ -0,0 +1,114 @@ +assertSame(0o000, Permission::calculate(Permission::NONE)); + + $this->assertSame(0o400, Permission::calculate(Permission::OWNER_READ)); + + $this->assertSame(0o200, Permission::calculate(Permission::OWNER_WRITE)); + + $this->assertSame(0o100, Permission::calculate(Permission::OWNER_EXECUTE)); + + $this->assertSame(0o700, Permission::calculate(Permission::OWNER_FULL)); + + $this->assertSame(0o040, Permission::calculate(Permission::GROUP_READ)); + + $this->assertSame(0o020, Permission::calculate(Permission::GROUP_WRITE)); + + $this->assertSame(0o010, Permission::calculate(Permission::GROUP_EXECUTE)); + + $this->assertSame(0o070, Permission::calculate(Permission::GROUP_FULL)); + + $this->assertSame(0o004, Permission::calculate(Permission::PUBLIC_READ)); + + $this->assertSame(0o002, Permission::calculate(Permission::PUBLIC_WRITE)); + + $this->assertSame(0o001, Permission::calculate(Permission::PUBLIC_EXECUTE)); + + $this->assertSame(0o007, Permission::calculate(Permission::PUBLIC_FULL)); + + // Test combinations + + $this->assertSame(0o777, Permission::calculate( + Permission::OWNER_FULL, + Permission::GROUP_FULL, + Permission::PUBLIC_FULL + )); + + $this->assertSame(0o744, Permission::calculate( + Permission::OWNER_FULL, + Permission::GROUP_READ, + Permission::PUBLIC_READ) + ); + + $this->assertSame(0o755, Permission::calculate( + Permission::OWNER_FULL, + Permission::GROUP_READ, + Permission::GROUP_EXECUTE, + Permission::PUBLIC_READ, + Permission::PUBLIC_EXECUTE) + ); + } + + /** + * + */ + public function testHasPermissionsWithInvalidPermissions(): void + { + $this->expectException(InvalidArgumentException::class); + + $this->expectExceptionMessage('The integer 1337 does not represent a valid octal between 0o000 and 0o777.'); + + Permission::hasPermissions(1337, Permission::NONE); + } + + /** + * + */ + public function testHasPermissions(): void + { + $this->assertTrue(Permission::hasPermissions(0o777, Permission::OWNER_FULL)); + + $this->assertTrue(Permission::hasPermissions(0o777, Permission::OWNER_FULL, Permission::GROUP_FULL)); + + $this->assertTrue(Permission::hasPermissions(0o777, Permission::OWNER_FULL, Permission::GROUP_FULL, Permission::PUBLIC_FULL)); + + $this->assertTrue(Permission::hasPermissions(0o755, Permission::OWNER_FULL)); + + $this->assertFalse(Permission::hasPermissions(0o755, Permission::GROUP_WRITE)); + + $this->assertFalse(Permission::hasPermissions(0o755, Permission::PUBLIC_WRITE)); + } + + /** + * + */ + public function testHasPermissionsWithNoPermissions(): void + { + $this->assertTrue(Permission::hasPermissions(0o000, Permission::NONE)); + + $this->assertFalse(Permission::hasPermissions(0o777, Permission::NONE)); + } +}