Skip to content

Commit

Permalink
add permission get/set feature interfaces, implement for file system
Browse files Browse the repository at this point in the history
  • Loading branch information
matthi4s committed Dec 6, 2024
1 parent e749a23 commit 1ced66d
Show file tree
Hide file tree
Showing 13 changed files with 311 additions and 27 deletions.
15 changes: 15 additions & 0 deletions src/Exception/ChmodException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php

namespace Aternos\IO\Exception;

/**
* Class ChmodException
*
* Exception thrown when a chmod operation fails
*
* @package Aternos\IO\Exception
*/
class ChmodException extends IOException
{

}
25 changes: 25 additions & 0 deletions src/Interfaces/Features/GetPermissionsInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?php

namespace Aternos\IO\Interfaces\Features;

use Aternos\IO\Exception\IOException;
use Aternos\IO\Interfaces\IOElementInterface;
use Aternos\IO\Interfaces\Util\PermissionsInterface;

/**
* Interface GetPermissionsInterface
*
* Allows getting the permissions of an element
*
* @package Aternos\IO\Interfaces\Features
*/
interface GetPermissionsInterface extends IOElementInterface
{
/**
* Get the permissions of this element
*
* @throws IOException
* @return PermissionsInterface
*/
public function getPermissions(): PermissionsInterface;
}
26 changes: 26 additions & 0 deletions src/Interfaces/Features/SetPermissionsInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php

namespace Aternos\IO\Interfaces\Features;

use Aternos\IO\Exception\IOException;
use Aternos\IO\Interfaces\IOElementInterface;
use Aternos\IO\Interfaces\Util\PermissionsInterface;

/**
* Interface SetPermissionsInterface
*
* Allows setting the permissions of an element
*
* @package Aternos\IO\Interfaces\Features
*/
interface SetPermissionsInterface extends IOElementInterface
{
/**
* Set the permissions of this element
*
* @throws IOException
* @param PermissionsInterface $permissions
* @return $this
*/
public function setPermissions(PermissionsInterface $permissions): static;
}
7 changes: 7 additions & 0 deletions src/Interfaces/Util/PermissionsClassInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -55,4 +55,11 @@ public function canExecute(): bool;
* @return $this
*/
public function setExecute(bool $execute): static;

/**
* Get the numeric representation of the class
*
* @return int
*/
public function toNumeric(): int;
}
7 changes: 7 additions & 0 deletions src/Interfaces/Util/PermissionsInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -55,4 +55,11 @@ public function getOther(): PermissionsClassInterface;
* @return $this
*/
public function setOther(PermissionsClassInterface $other): static;

/**
* Get the numeric representation of the permissions
*
* @return int
*/
public function toNumeric(): int;
}
30 changes: 30 additions & 0 deletions src/System/FilesystemElement.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,19 @@

namespace Aternos\IO\System;

use Aternos\IO\Exception\ChmodException;
use Aternos\IO\Exception\MoveException;
use Aternos\IO\Exception\PathOutsideElementException;
use Aternos\IO\Exception\StatException;
use Aternos\IO\Exception\TouchException;
use Aternos\IO\Interfaces\Features\GetPathInterface;
use Aternos\IO\Interfaces\Util\PermissionsInterface;
use Aternos\IO\System\Directory\Directory;
use Aternos\IO\System\File\File;
use Aternos\IO\System\Link\DirectoryLink;
use Aternos\IO\System\Link\FileLink;
use Aternos\IO\System\Link\Link;
use Aternos\IO\System\Util\Permissions;

/**
* Class FilesystemElement
Expand Down Expand Up @@ -214,6 +217,33 @@ public function setModificationTimestamp(int $timestamp): static
return $this;
}

/**
* @inheritDoc
* @throws StatException
*/
public function getPermissions(): PermissionsInterface
{
$numericPermissions = @fileperms($this->path);
if ($numericPermissions === false) {
$error = error_get_last();
throw new StatException("Could not get permissions (" . $this->path . ")" . ($error ? ": " . $error["message"] : ""), $this);
}
return Permissions::fromNumeric($numericPermissions);
}

/**
* @inheritDoc
* @throws ChmodException
*/
public function setPermissions(PermissionsInterface $permissions): static
{
if (!@chmod($this->path, $permissions->toNumeric())) {
$error = error_get_last();
throw new ChmodException("Could not set permissions (" . $this->path . ")" . ($error ? ": " . $error["message"] : ""), $this);
}
return $this;
}

/**
* @return string[]
*/
Expand Down
6 changes: 5 additions & 1 deletion src/System/FilesystemInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,12 @@
use Aternos\IO\Interfaces\Features\ExistsInterface;
use Aternos\IO\Interfaces\Features\GetAccessTimestampInterface;
use Aternos\IO\Interfaces\Features\GetModificationTimestampInterface;
use Aternos\IO\Interfaces\Features\GetPermissionsInterface;
use Aternos\IO\Interfaces\Features\GetStatusChangeTimestampInterface;
use Aternos\IO\Interfaces\Features\MovePathInterface;
use Aternos\IO\Interfaces\Features\SetAccessTimestampInterface;
use Aternos\IO\Interfaces\Features\SetModificationTimestampInterface;
use Aternos\IO\Interfaces\Features\SetPermissionsInterface;

/**
* Interface FilesystemInterface
Expand All @@ -26,7 +28,9 @@ interface FilesystemInterface extends
GetModificationTimestampInterface,
GetStatusChangeTimestampInterface,
SetAccessTimestampInterface,
SetModificationTimestampInterface
SetModificationTimestampInterface,
GetPermissionsInterface,
SetPermissionsInterface
{

}
33 changes: 21 additions & 12 deletions src/System/Util/Permissions.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,27 +14,26 @@
*/
class Permissions implements PermissionsInterface
{
protected const int CLASS_MASK = 0b111;
protected const int READ_SHIFT = 6;
protected const int WRITE_SHIFT = 3;
protected const int EXECUTE_SHIFT = 0;

/**
* @param int $permissions
* @return static
*/
public static function fromNumeric(int $permissions): static
{
return new static(
new PermissionsClass(
($permissions & 0o100) === 0o100,
($permissions & 0o200) === 0o200,
($permissions & 0o400) === 0o400
PermissionsClass::fromNumeric(
($permissions >> static::READ_SHIFT) & static::CLASS_MASK
),
new PermissionsClass(
($permissions & 0o010) === 0o010,
($permissions & 0o020) === 0o020,
($permissions & 0o040) === 0o040
PermissionsClass::fromNumeric(
($permissions >> static::WRITE_SHIFT) & static::CLASS_MASK
),
new PermissionsClass(
($permissions & 0o001) === 0o001,
($permissions & 0o002) === 0o002,
($permissions & 0o004) === 0o004
PermissionsClass::fromNumeric(
($permissions >> static::EXECUTE_SHIFT) & static::CLASS_MASK
)
);
}
Expand Down Expand Up @@ -103,4 +102,14 @@ public function setOther(PermissionsClassInterface $other): static
$this->other = $other;
return $this;
}

/**
* @inheritDoc
*/
public function toNumeric(): int
{
return ($this->user->toNumeric() << static::READ_SHIFT)
+ ($this->group->toNumeric() << static::WRITE_SHIFT)
+ ($this->other->toNumeric() << static::EXECUTE_SHIFT);
}
}
27 changes: 27 additions & 0 deletions src/System/Util/PermissionsClass.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,23 @@
*/
class PermissionsClass implements PermissionsClassInterface
{
protected const int READ_MASK = 0b100;
protected const int WRITE_MASK = 0b010;
protected const int EXECUTE_MASK = 0b001;

/**
* @param int $permissions
* @return static
*/
public static function fromNumeric(int $permissions): static
{
return new static(
($permissions & static::READ_MASK) === static::READ_MASK,
($permissions & static::WRITE_MASK) === static::WRITE_MASK,
($permissions & static::EXECUTE_MASK) === static::EXECUTE_MASK
);
}

/**
* @param bool $read
* @param bool $write
Expand Down Expand Up @@ -76,4 +93,14 @@ public function setExecute(bool $execute): static
$this->execute = $execute;
return $this;
}

/**
* @inheritDoc
*/
public function toNumeric(): int
{
return ($this->read ? static::READ_MASK : 0)
+ ($this->write ? static::WRITE_MASK : 0)
+ ($this->execute ? static::EXECUTE_MASK : 0);
}
}
61 changes: 61 additions & 0 deletions test/Unit/System/FilesystemTestCase.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace Aternos\IO\Test\Unit\System;

use Aternos\IO\Exception\ChmodException;
use Aternos\IO\Exception\IOException;
use Aternos\IO\Exception\MoveException;
use Aternos\IO\Exception\PathOutsideElementException;
Expand All @@ -12,6 +13,7 @@
use Aternos\IO\System\Directory\Directory;
use Aternos\IO\System\FilesystemElement;
use Aternos\IO\System\FilesystemInterface;
use Aternos\IO\System\Util\Permissions;

abstract class FilesystemTestCase extends TmpDirTestCase
{
Expand Down Expand Up @@ -426,6 +428,65 @@ public function testSetModificationTimestampThrowsExceptionOnImpossibleSet(): vo
$element->setModificationTimestamp(1234567890);
}

/**
* @return void
* @throws IOException
* @throws StatException
*/
public function testGetPermissions(): void
{
$path = $this->getTmpPath() . "/test";
$element = $this->createElement($path);
$this->create($element);
chmod($path, 0o755);
$this->assertEquals(0o755, $element->getPermissions()->toNumeric());
}

/**
* @return void
* @throws IOException
* @throws StatException
*/
public function testThrowsExceptionOnGetPermissions(): void
{
$path = $this->getTmpPath() . "/test";
$element = $this->createElement($path);
$this->expectException(StatException::class);
/** @noinspection SpellCheckingInspection */
$this->expectExceptionMessage("Could not get permissions (" . $path . ")");
$element->getPermissions();
}

/**
* @return void
* @throws IOException
* @throws ChmodException
*/
public function testSetPermissions(): void
{
$path = $this->getTmpPath() . "/test";
$element = $this->createElement($path);
$this->create($element);
chmod($path, 0o777);
$element->setPermissions(Permissions::fromNumeric(0o755));
$this->assertEquals(0o755, fileperms($path) & 0o777);
}

/**
* @return void
* @throws IOException
* @throws ChmodException
*/
public function testThrowsExceptionOnSetPermissions(): void
{
$path = $this->getTmpPath() . "/test";
$element = $this->createElement($path);
$this->expectException(ChmodException::class);
/** @noinspection SpellCheckingInspection */
$this->expectExceptionMessage("Could not set permissions (" . $path . ")");
$element->setPermissions(Permissions::fromNumeric(0o755));
}

/**
* @return void
*/
Expand Down
24 changes: 24 additions & 0 deletions test/Unit/System/Link/DeadLinkTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@

namespace Aternos\IO\Test\Unit\System\Link;

use Aternos\IO\Exception\ChmodException;
use Aternos\IO\Exception\GetTargetException;
use Aternos\IO\Exception\IOException;
use Aternos\IO\Exception\StatException;
use Aternos\IO\Exception\TouchException;
use Aternos\IO\System\Util\Permissions;

class DeadLinkTest extends LinkTest
{
Expand Down Expand Up @@ -169,4 +171,26 @@ public function testSetModificationTimestamp(): void
$this->expectExceptionMessage("Could not set modification timestamp because element does not exist (" . $path . ")");
$element->setModificationTimestamp(0);
}

public function testGetPermissions(): void
{
$path = $this->getTmpPath() . "/test";
$element = $this->createElement($path);
$this->create($element);
$this->expectException(StatException::class);
/** @noinspection SpellCheckingInspection */
$this->expectExceptionMessage("Could not get permissions (" . $path . "): fileperms(): stat failed for " . $path);
$element->getPermissions();
}

public function testSetPermissions(): void
{
$path = $this->getTmpPath() . "/test";
$element = $this->createElement($path);
$this->create($element);
$this->expectException(ChmodException::class);
/** @noinspection SpellCheckingInspection */
$this->expectExceptionMessage("Could not set permissions (" . $path . "): chmod(): No such file or directory");
$element->setPermissions(Permissions::fromNumeric(0o777));
}
}
Loading

0 comments on commit 1ced66d

Please sign in to comment.