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); } }