diff --git a/src/Database/Database.php b/src/Database/Database.php index 5484d0693..74a9e4958 100644 --- a/src/Database/Database.php +++ b/src/Database/Database.php @@ -13,6 +13,7 @@ use Utopia\Database\Exception\Restricted as RestrictedException; use Utopia\Database\Exception\Structure as StructureException; use Utopia\Database\Exception\Query as QueryException; +use Utopia\Database\Exception\Validation; use Utopia\Database\Helpers\ID; use Utopia\Database\Helpers\Permission; use Utopia\Database\Helpers\Role; @@ -2255,7 +2256,6 @@ public function getDocument(string $collection, string $id, array $queries = []) $queries = \array_values($queries); - $validator = $this->authorization->setAction(self::PERMISSION_READ); $documentSecurity = $collection->getAttribute('documentSecurity', false); $cacheKey = 'cache-' . $this->getNamespace() . ':' . $collection->getId() . ':' . $id; @@ -2269,10 +2269,12 @@ public function getDocument(string $collection, string $id, array $queries = []) $document = new Document($cache); if ($collection->getId() !== self::METADATA) { - if (!$validator->isValid([ - ...$collection->getRead(), - ...($documentSecurity ? $document->getRead() : []) - ])) { + try { + $this->authorization->validate(self::PERMISSION_READ, [ + ...$collection->getRead(), + ...($documentSecurity ? $document->getRead() : []) + ]); + } catch(Validation $err) { return new Document(); } } @@ -2291,10 +2293,12 @@ public function getDocument(string $collection, string $id, array $queries = []) $document->setAttribute('$collection', $collection->getId()); if ($collection->getId() !== self::METADATA) { - if (!$validator->isValid([ - ...$collection->getRead(), - ...($documentSecurity ? $document->getRead() : []) - ])) { + try { + $this->authorization->validate(self::PERMISSION_READ, [ + ...$collection->getRead(), + ...($documentSecurity ? $document->getRead() : []) + ]); + } catch(Validation $err) { return new Document(); } } @@ -2613,9 +2617,10 @@ public function createDocument(string $collection, Document $document): Document $collection = $this->silent(fn () => $this->getCollection($collection)); if ($collection->getId() !== self::METADATA) { - $authorization = $this->authorization->setAction(self::PERMISSION_CREATE); - if (!$authorization->isValid($collection->getCreate())) { - throw new AuthorizationException($authorization->getDescription()); + try { + $this->authorization->validate(self::PERMISSION_CREATE, $collection->getCreate()); + } catch(Validation $err) { + throw new AuthorizationException($err->getMessage()); } } @@ -2963,7 +2968,6 @@ public function updateDocument(string $collection, string $id, Document $documen return $attribute['type'] === Database::VAR_RELATIONSHIP; }); - $validator = $this->authorization->setAction(self::PERMISSION_UPDATE); $shouldUpdate = false; if ($collection->getId() !== self::METADATA) { @@ -3050,11 +3054,15 @@ public function updateDocument(string $collection, string $id, Document $documen } } - if ($shouldUpdate && !$validator->isValid([ - ...$collection->getUpdate(), - ...($documentSecurity ? $old->getUpdate() : []) - ])) { - throw new AuthorizationException($validator->getDescription()); + if ($shouldUpdate) { + try { + $this->authorization->validate(self::PERMISSION_UPDATE, [ + ...$collection->getUpdate(), + ...($documentSecurity ? $old->getUpdate() : []) + ]); + } catch(Validation $err) { + throw new AuthorizationException($err->getMessage()); + } } } @@ -3496,19 +3504,19 @@ public function increaseDocumentAttribute(string $collection, string $id, string throw new DatabaseException('Value must be numeric and greater than 0'); } - $validator = $this->authorization->setAction(self::PERMISSION_UPDATE); - $document = $this->authorization->skip(fn () => $this->silent(fn () => $this->getDocument($collection, $id))); // Skip ensures user does not need read permission for this $collection = $this->silent(fn () => $this->getCollection($collection)); if ($collection->getId() !== self::METADATA) { $documentSecurity = $collection->getAttribute('documentSecurity', false); - if (!$validator->isValid([ - ...$collection->getUpdate(), - ...($documentSecurity ? $document->getUpdate() : []) - ])) { - throw new AuthorizationException($validator->getDescription()); + try { + $this->authorization->validate(self::PERMISSION_UPDATE, [ + ...$collection->getUpdate(), + ...($documentSecurity ? $document->getUpdate() : []) + ]); + } catch(Validation $err) { + throw new AuthorizationException($err->getMessage()); } } @@ -3563,19 +3571,19 @@ public function decreaseDocumentAttribute(string $collection, string $id, string throw new DatabaseException('Value must be numeric and greater than 0'); } - $validator = $this->authorization->setAction(self::PERMISSION_UPDATE); - $document = $this->authorization->skip(fn () => $this->silent(fn () => $this->getDocument($collection, $id))); // Skip ensures user does not need read permission for this $collection = $this->silent(fn () => $this->getCollection($collection)); if ($collection->getId() !== self::METADATA) { $documentSecurity = $collection->getAttribute('documentSecurity', false); - if (!$validator->isValid([ - ...$collection->getUpdate(), - ...($documentSecurity ? $document->getUpdate() : []) - ])) { - throw new AuthorizationException($validator->getDescription()); + try { + $this->authorization->validate(self::PERMISSION_UPDATE, [ + ...$collection->getUpdate(), + ...($documentSecurity ? $document->getUpdate() : []) + ]); + } catch(Validation $err) { + throw new AuthorizationException($err->getMessage()); } } @@ -3628,15 +3636,15 @@ public function deleteDocument(string $collection, string $id): bool $document = $this->authorization->skip(fn () => $this->silent(fn () => $this->getDocument($collection, $id))); // Skip ensures user does not need read permission for this $collection = $this->silent(fn () => $this->getCollection($collection)); - $validator = $this->authorization->setAction(self::PERMISSION_DELETE); - if ($collection->getId() !== self::METADATA) { $documentSecurity = $collection->getAttribute('documentSecurity', false); - if (!$validator->isValid([ - ...$collection->getDelete(), - ...($documentSecurity ? $document->getDelete() : []) - ])) { - throw new AuthorizationException($validator->getDescription()); + try { + $this->authorization->validate(self::PERMISSION_DELETE, [ + ...$collection->getDelete(), + ...($documentSecurity ? $document->getDelete() : []) + ]); + } catch(Validation $err) { + throw new AuthorizationException($err->getMessage()); } } @@ -4089,9 +4097,14 @@ public function find(string $collection, array $queries = [], ?int $timeout = nu throw new QueryException($validator->getDescription()); } - $authorization = $this->authorization->setAction(self::PERMISSION_READ); $documentSecurity = $collection->getAttribute('documentSecurity', false); - $skipAuth = $authorization->isValid($collection->getRead()); + + try { + $this->authorization->validate(self::PERMISSION_READ, $collection->getRead()); + $skipAuth = true; + } catch(Validation $err) { + $skipAuth = false; + } if (!$skipAuth && !$documentSecurity) { throw new AuthorizationException($validator->getDescription()); @@ -4261,16 +4274,18 @@ public function count(string $collection, array $queries = [], ?int $max = null) throw new QueryException($validator->getDescription()); } - $authorization = $this->authorization->setAction(self::PERMISSION_READ); - if ($authorization->isValid($collection->getRead())) { + try { + $this->authorization->validate(self::PERMISSION_READ, $collection->getRead()); $skipAuth = true; + } catch(Validation $err) { + $skipAuth = false; } $queries = Query::groupByType($queries)['filters']; $queries = self::convertQueries($collection, $queries); $getCount = fn () => $this->adapter->count($collection->getId(), $queries, $max); - $count = $skipAuth ?? false ? $this->authorization->skip($getCount) : $getCount(); + $count = $skipAuth ? $this->authorization->skip($getCount) : $getCount(); $this->trigger(self::EVENT_DOCUMENT_COUNT, $count); diff --git a/src/Database/Exception/Validation.php b/src/Database/Exception/Validation.php new file mode 100644 index 000000000..9e554b443 --- /dev/null +++ b/src/Database/Exception/Validation.php @@ -0,0 +1,7 @@ + true ]; - /** - * @var string - */ - protected string $action = ''; - /** * @var string */ protected string $message = 'Authorization Error'; - /** - * @param string $action - */ - public function __construct(string $action = '') - { - $this->action = $action; - } - - /** - * @param string $action - * @return self - */ - public function setAction(string $action): self - { - $this->action = $action; - - return $this; - } - /** * Get Description. * @@ -54,37 +31,50 @@ public function getDescription(): string return $this->message; } + /* + * Deprecated for this specific validator. Use validate() instead + */ + public function isValid(mixed $permissions): bool + { + try { + $this->validate('unknownAction', $permissions); + return true; + } catch(Validation $error) { + $this->message = $error->getMessage(); + return false; + } + } + /** - * Is valid. + * Validation. Replacement of isValid() which throws exception instead * * Returns true if valid or false if not. * + * @param string $action * @param mixed $permissions * - * @return bool + * @return void + * @throws Validation */ - public function isValid($permissions): bool + public function validate(string $action, mixed $permissions): void { if (!$this->status) { - return true; + return; } if (empty($permissions)) { - $this->message = 'No permissions provided for action \''.$this->action.'\''; - return false; + throw new Validation('No permissions provided for action \''.$action.'\''); } $permission = '-'; foreach ($permissions as $permission) { if (\array_key_exists($permission, $this->roles)) { - return true; + return; } } - $this->message = 'Missing "'.$this->action.'" permission for role "'.$permission.'". Only "'.\json_encode($this->getRoles()).'" scopes are allowed and "'.\json_encode($permissions).'" was given.'; - - return false; + throw new Validation('Missing "'.$action.'" permission for role "'.$permission.'". Only "'.\json_encode($this->getRoles()).'" scopes are allowed and "'.\json_encode($permissions).'" was given.'); } /** diff --git a/tests/Database/Validator/AuthorizationTest.php b/tests/Database/Validator/AuthorizationTest.php index 30249a873..edcea2bac 100644 --- a/tests/Database/Validator/AuthorizationTest.php +++ b/tests/Database/Validator/AuthorizationTest.php @@ -2,7 +2,6 @@ namespace Utopia\Tests\Validator; -use Utopia\Database\Database; use Utopia\Database\Document; use Utopia\Database\Helpers\ID; use Utopia\Database\Helpers\Permission; @@ -39,12 +38,12 @@ public function testValues(): void ], ]); - $object = $this->authorization->setAction(Database::PERMISSION_READ); + $object = $this->authorization; $this->assertEquals($object->isValid($document->getRead()), false); $this->assertEquals($object->isValid(''), false); $this->assertEquals($object->isValid([]), false); - $this->assertEquals($object->getDescription(), 'No permissions provided for action \'read\''); + $this->assertEquals($object->getDescription(), 'No permissions provided for action \'unknownAction\''); $this->authorization->setRole(Role::user('456')->toString()); $this->authorization->setRole(Role::user('123')->toString());