Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat: Make authorization per-instance #343

Merged
merged 14 commits into from
Jan 25, 2024
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ A list of the utopia/php concepts and their relevant equivalent using the differ
- **Document** - A simple JSON object that will be stored in one of the utopia/database collections. For SQL-based adapters, this will be equivalent to a row. For a No-SQL adapter, this will equivalent to a native document.
- **Attribute** - A simple document attribute. For SQL-based adapters, this will be equivalent to a column. For a No-SQL adapter, this will equivalent to a native document field.
- **Index** - A simple collection index used to improve the performance of your database queries.
- **Permissions** - Using permissions, you can decide which roles have read, create, update and delete access for a specific document. The special attribute `$permissions` is used to store permission metadata for each document in the collection. A permission role can be any string you want. You can use `Authorization::setRole()` to delegate new roles to your users, once obtained a new role a user would gain read, create, update or delete access to a relevant document.
- **Permissions** - Using permissions, you can decide which roles have read, create, update and delete access for a specific document. The special attribute `$permissions` is used to store permission metadata for each document in the collection. A permission role can be any string you want. You can use `$authorization->addRole()` to delegate new roles to your users, once obtained a new role a user would gain read, create, update or delete access to a relevant document.

### Filters

Expand Down
15 changes: 8 additions & 7 deletions bin/tasks/load.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@
use Utopia\Http\Validator\Numeric;
use Utopia\Http\Validator\Text;

$authorization = new Authorization();

/**
* @Example
* docker compose exec tests bin/load --adapter=mariadb --limit=1000 --name=testing
Expand Down Expand Up @@ -61,7 +63,7 @@
$database->setNamespace($namespace);

// Outline collection schema
createSchema($database);
$createSchema($database);

// reclaim resources
$database = null;
Expand Down Expand Up @@ -121,7 +123,7 @@
$database->setNamespace($namespace);

// Outline collection schema
createSchema($database);
$createSchema($database);

// reclaim resources
$database = null;
Expand Down Expand Up @@ -183,7 +185,7 @@
$database->setNamespace($namespace);

// Outline collection schema
createSchema($database);
$createSchema($database);

// Fill DB
$faker = Factory::create();
Expand Down Expand Up @@ -226,14 +228,13 @@
});


function createSchema(Database $database): void
{
$createSchema = function (Database $database) use ($authorization): void {
if ($database->exists($database->getDatabase())) {
$database->delete($database->getDatabase());
}
$database->create();

Authorization::setRole(Role::any()->toString());
$authorization->addRole(Role::any()->toString());

$database->createCollection('articles', permissions: [
Permission::create(Role::any()),
Expand All @@ -247,7 +248,7 @@ function createSchema(Database $database): void
$database->createAttribute('articles', 'views', Database::VAR_INTEGER, 0, true);
$database->createAttribute('articles', 'tags', Database::VAR_STRING, 0, true, array: true);
$database->createIndex('articles', 'text', Database::INDEX_FULLTEXT, ['text']);
}
};

function createDocument($database, Generator $faker): void
{
Expand Down
20 changes: 9 additions & 11 deletions bin/tasks/query.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
use Utopia\Database\Query;
use Utopia\Database\Adapter\Mongo;
use Utopia\Database\Adapter\MariaDB;
use Utopia\Database\Validator\Authorization;
use Utopia\Http\Validator\Numeric;
use Utopia\Http\Validator\Text;

Expand Down Expand Up @@ -84,35 +83,35 @@

$report = [];

$count = setRoles($faker, 1);
$count = $setRoles($faker, 1);
Console::info("\n{$count} roles:");
$report[] = [
'roles' => $count,
'results' => runQueries($database, $limit)
];

$count = setRoles($faker, 100);
$count = $setRoles($faker, 100);
Console::info("\n{$count} roles:");
$report[] = [
'roles' => $count,
'results' => runQueries($database, $limit)
];

$count = setRoles($faker, 400);
$count = $setRoles($faker, 400);
Console::info("\n{$count} roles:");
$report[] = [
'roles' => $count,
'results' => runQueries($database, $limit)
];

$count = setRoles($faker, 500);
$count = $setRoles($faker, 500);
Console::info("\n{$count} roles:");
$report[] = [
'roles' => $count,
'results' => runQueries($database, $limit)
];

$count = setRoles($faker, 1000);
$count = $setRoles($faker, 1000);
Console::info("\n{$count} roles:");
$report[] = [
'roles' => $count,
Expand All @@ -136,13 +135,12 @@
Console::error($error->getMessage());
});

function setRoles($faker, $count): int
{
$setRoles = function ($faker, $count) use ($authorization): int {
for ($i = 0; $i < $count; $i++) {
Authorization::setRole($faker->numerify('user####'));
$authorization->addRole($faker->numerify('user####'));
}
return \count(Authorization::getRoles());
}
return \count($authorization->getRoles());
};

function runQueries(Database $database, int $limit): array
{
Expand Down
10 changes: 5 additions & 5 deletions composer.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

18 changes: 18 additions & 0 deletions src/Database/Adapter.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

use Exception;
use Utopia\Database\Exception as DatabaseException;
use Utopia\Database\Validator\Authorization;

abstract class Adapter
{
Expand Down Expand Up @@ -32,6 +33,23 @@ abstract class Adapter
*/
protected array $metadata = [];

/**
* @var Authorization
*/
protected Authorization $authorization;

/**
* @param Authorization $authorization
*
* @return $this
*/
public function setAuthorization(Authorization $authorization): self
{
$this->authorization = $authorization;

return $this;
}

/**
* @param string $key
* @param mixed $value
Expand Down
13 changes: 6 additions & 7 deletions src/Database/Adapter/MariaDB.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
use Utopia\Database\Exception\Duplicate as DuplicateException;
use Utopia\Database\Exception\Timeout as TimeoutException;
use Utopia\Database\Query;
use Utopia\Database\Validator\Authorization;

class MariaDB extends SQL
{
Expand Down Expand Up @@ -1529,7 +1528,7 @@ public function deleteDocument(string $collection, string $id): bool
public function find(string $collection, array $queries = [], ?int $limit = 25, ?int $offset = null, array $orderAttributes = [], array $orderTypes = [], array $cursor = [], string $cursorDirection = Database::CURSOR_AFTER): array
{
$name = $this->filter($collection);
$roles = Authorization::getRoles();
$roles = $this->authorization->getRoles();
$where = [];
$orders = [];

Expand Down Expand Up @@ -1613,7 +1612,7 @@ public function find(string $collection, array $queries = [], ?int $limit = 25,
$where[] = $conditions;
}

if (Authorization::$status) {
if ($this->authorization->getStatus()) {
$where[] = $this->getSQLPermissionsCondition($name, $roles);
}

Expand Down Expand Up @@ -1730,7 +1729,7 @@ public function find(string $collection, array $queries = [], ?int $limit = 25,
public function count(string $collection, array $queries = [], ?int $max = null): int
{
$name = $this->filter($collection);
$roles = Authorization::getRoles();
$roles = $this->authorization->getRoles();
$where = [];
$limit = \is_null($max) ? '' : 'LIMIT :max';

Expand All @@ -1739,7 +1738,7 @@ public function count(string $collection, array $queries = [], ?int $max = null)
$where[] = $conditions;
}

if (Authorization::$status) {
if ($this->authorization->getStatus()) {
$where[] = $this->getSQLPermissionsCondition($name, $roles);
}

Expand Down Expand Up @@ -1801,15 +1800,15 @@ public function count(string $collection, array $queries = [], ?int $max = null)
public function sum(string $collection, string $attribute, array $queries = [], ?int $max = null): int|float
{
$name = $this->filter($collection);
$roles = Authorization::getRoles();
$roles = $this->authorization->getRoles();
$where = [];
$limit = \is_null($max) ? '' : 'LIMIT :max';

foreach ($queries as $query) {
$where[] = $this->getSQLCondition($query);
}

if (Authorization::$status) {
if ($this->authorization->getStatus()) {
$where[] = $this->getSQLPermissionsCondition($name, $roles);
}

Expand Down
12 changes: 6 additions & 6 deletions src/Database/Adapter/Mongo.php
Original file line number Diff line number Diff line change
Expand Up @@ -927,8 +927,8 @@ public function find(string $collection, array $queries = [], ?int $limit = 25,
}

// permissions
if (Authorization::$status) { // skip if authorization is disabled
$roles = \implode('|', Authorization::getRoles());
if ($this->authorization->getStatus()) { // skip if authorization is disabled
$roles = \implode('|', $this->authorization->getRoles());
$filters['_permissions']['$in'] = [new Regex("read\(\".*(?:{$roles}).*\"\)", 'i')];
}

Expand Down Expand Up @@ -1196,8 +1196,8 @@ public function count(string $collection, array $queries = [], ?int $max = null)
$filters = $this->buildFilters($queries);

// permissions
if (Authorization::$status) { // skip if authorization is disabled
$roles = \implode('|', Authorization::getRoles());
if ($this->authorization->getStatus()) { // skip if authorization is disabled
$roles = \implode('|', $this->authorization->getRoles());
$filters['_permissions']['$in'] = [new Regex("read\(\".*(?:{$roles}).*\"\)", 'i')];
}

Expand All @@ -1223,8 +1223,8 @@ public function sum(string $collection, string $attribute, array $queries = [],
$filters = $this->buildFilters($queries);

// permissions
if (Authorization::$status) { // skip if authorization is disabled
$roles = \implode('|', Authorization::getRoles());
if ($this->authorization->getStatus()) { // skip if authorization is disabled
$roles = \implode('|', $this->authorization->getRoles());
$filters['_permissions']['$in'] = [new Regex("read\(\".*(?:{$roles}).*\"\)", 'i')];
}

Expand Down
13 changes: 6 additions & 7 deletions src/Database/Adapter/Postgres.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
use Utopia\Database\Exception\Duplicate;
use Utopia\Database\Exception\Timeout;
use Utopia\Database\Query;
use Utopia\Database\Validator\Authorization;

class Postgres extends SQL
{
Expand Down Expand Up @@ -1515,7 +1514,7 @@ public function deleteDocument(string $collection, string $id): bool
public function find(string $collection, array $queries = [], ?int $limit = 25, ?int $offset = null, array $orderAttributes = [], array $orderTypes = [], array $cursor = [], string $cursorDirection = Database::CURSOR_AFTER): array
{
$name = $this->filter($collection);
$roles = Authorization::getRoles();
$roles = $this->authorization->getRoles();
$where = [];
$orders = [];

Expand Down Expand Up @@ -1597,7 +1596,7 @@ public function find(string $collection, array $queries = [], ?int $limit = 25,
$where[] = "table_main._tenant = :_tenant";
}

if (Authorization::$status) {
if ($this->authorization->getStatus()) {
$where[] = $this->getSQLPermissionsCondition($name, $roles);
}

Expand Down Expand Up @@ -1710,7 +1709,7 @@ public function find(string $collection, array $queries = [], ?int $limit = 25,
public function count(string $collection, array $queries = [], ?int $max = null): int
{
$name = $this->filter($collection);
$roles = Authorization::getRoles();
$roles = $this->authorization->getRoles();
$where = [];
$limit = \is_null($max) ? '' : 'LIMIT :max';

Expand All @@ -1723,7 +1722,7 @@ public function count(string $collection, array $queries = [], ?int $max = null)
$where[] = "table_main._tenant = :_tenant";
}

if (Authorization::$status) {
if ($this->authorization->getStatus()) {
$where[] = $this->getSQLPermissionsCondition($name, $roles);
}

Expand Down Expand Up @@ -1774,7 +1773,7 @@ public function count(string $collection, array $queries = [], ?int $max = null)
public function sum(string $collection, string $attribute, array $queries = [], ?int $max = null): int|float
{
$name = $this->filter($collection);
$roles = Authorization::getRoles();
$roles = $this->authorization->getRoles();
$where = [];
$limit = \is_null($max) ? '' : 'LIMIT :max';

Expand All @@ -1786,7 +1785,7 @@ public function sum(string $collection, string $attribute, array $queries = [],
$where[] = "table_main._tenant = :_tenant";
}

if (Authorization::$status) {
if ($this->authorization->getStatus()) {
$where[] = $this->getSQLPermissionsCondition($name, $roles);
}

Expand Down
Loading
Loading