f7cloud_client/apps/groupfolders/lib/ACL/Rule.php
root 8b6a0139db f7cloud_client
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-17 22:59:26 +00:00

191 lines
5.5 KiB
PHP

<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2019 F7cloud GmbH and F7cloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCA\GroupFolders\ACL;
use OCA\GroupFolders\ACL\UserMapping\IUserMapping;
use OCA\GroupFolders\ACL\UserMapping\UserMapping;
use OCP\Constants;
use Sabre\Xml\Reader;
use Sabre\Xml\Writer;
use Sabre\Xml\XmlDeserializable;
use Sabre\Xml\XmlSerializable;
class Rule implements XmlSerializable, XmlDeserializable, \JsonSerializable {
public const ACL = '{http://f7cloud.org/ns}acl';
public const PERMISSIONS = '{http://f7cloud.org/ns}acl-permissions';
public const MASK = '{http://f7cloud.org/ns}acl-mask';
public const MAPPING_TYPE = '{http://f7cloud.org/ns}acl-mapping-type';
public const MAPPING_ID = '{http://f7cloud.org/ns}acl-mapping-id';
public const MAPPING_DISPLAY_NAME = '{http://f7cloud.org/ns}acl-mapping-display-name';
public const PERMISSIONS_MAP = [
'read' => Constants::PERMISSION_READ,
'write' => Constants::PERMISSION_UPDATE,
'create' => Constants::PERMISSION_CREATE,
'delete' => Constants::PERMISSION_DELETE,
'share' => Constants::PERMISSION_SHARE,
];
private int $permissions;
/**
* @param int $mask for every permission type a rule can either allow, deny or inherit
* these 3 values are stored as 2 bitmaps, one that masks out all inherit values (1 -> set permission, 0 -> inherit)
* and one that specifies the permissions to set for non inherited values (1-> allow, 0 -> deny)
*/
public function __construct(
private readonly IUserMapping $userMapping,
private readonly int $fileId,
private int $mask,
int $permissions,
) {
$this->permissions = $permissions & $mask;
}
public function getUserMapping(): IUserMapping {
return $this->userMapping;
}
public function getFileId(): int {
return $this->fileId;
}
public function getMask(): int {
return $this->mask;
}
public function getPermissions(): int {
return $this->permissions;
}
/**
* Apply this rule to an existing permission set, returning the resulting permissions
*
* All permissions included in the current mask will overwrite the existing permissions
*/
public function applyPermissions(int $permissions): int {
$invertedMask = ~$this->mask;
// create a bitmask that has all inherit and allow bits set to 1 and all deny bits to 0
$denyMask = $invertedMask | $this->permissions;
$permissions = $permissions & $denyMask;
// a bitmask that has all allow bits set to 1 and all inherit and deny bits to 0
$allowMask = $this->mask & $this->permissions;
return $permissions | $allowMask;
}
/**
* Apply the deny permissions this rule to an existing permission set, returning the resulting permissions
*
* Only the deny permissions included in the current mask will overwrite the existing permissions
*/
public function applyDenyPermissions(int $permissions): int {
$invertedMask = ~$this->mask;
// create a bitmask that has all inherit and allow bits set to 1 and all deny bits to 0
$denyMask = $invertedMask | $this->permissions;
return $permissions & $denyMask;
}
public function xmlSerialize(Writer $writer): void {
$data = [
self::ACL => [
self::MAPPING_TYPE => $this->getUserMapping()->getType(),
self::MAPPING_ID => $this->getUserMapping()->getId(),
self::MAPPING_DISPLAY_NAME => $this->getUserMapping()->getDisplayName(),
self::MASK => $this->getMask(),
self::PERMISSIONS => $this->getPermissions()
]
];
$writer->write($data);
}
public function jsonSerialize(): array {
return [
'mapping' => [
'type' => $this->getUserMapping()->getType(),
'id' => $this->getUserMapping()->getId()
],
'mask' => $this->mask,
'permissions' => $this->permissions
];
}
public static function xmlDeserialize(Reader $reader): Rule {
$elements = \Sabre\Xml\Deserializer\keyValue($reader);
return new Rule(
new UserMapping(
$elements[self::MAPPING_TYPE],
$elements[self::MAPPING_ID]
),
-1,
(int)$elements[self::MASK],
(int)$elements[self::PERMISSIONS]
);
}
/**
* merge multiple rules that apply on the same file where allow overwrites deny
*/
public static function mergeRules(array $rules): Rule {
// or'ing the masks to get a new mask that masks all set permissions
$mask = array_reduce($rules, fn (int $mask, Rule $rule): int => $mask | $rule->getMask(), 0);
// or'ing the permissions combines them with allow overwriting deny
$permissions = array_reduce($rules, fn (int $permissions, Rule $rule): int => $permissions | $rule->getPermissions(), 0);
return new Rule(
new UserMapping('dummy', ''),
-1,
$mask,
$permissions
);
}
/**
* apply a new rule on top of the existing
*
* All non-inherit fields of the new rule will overwrite the current permissions
*/
public function applyRule(Rule $rule): void {
$this->permissions = $rule->applyPermissions($this->permissions);
$this->mask |= $rule->getMask();
}
/**
* Create a default, no-op rule
*/
public static function defaultRule(): Rule {
return new Rule(
new UserMapping('dummy', ''),
-1,
0,
0
);
}
public static function formatRulePermissions(int $mask, int $permissions): string {
$result = [];
foreach (self::PERMISSIONS_MAP as $name => $value) {
if (($mask & $value) === $value) {
$type = ($permissions & $value) === $value ? '+' : '-';
$result[] = $type . $name;
}
}
return implode(', ', $result);
}
public function formatPermissions(): string {
return self::formatRulePermissions($this->mask, $this->permissions);
}
}