Skip to content

Commit

Permalink
Add support for label permissions
Browse files Browse the repository at this point in the history
A user can be given a particular label and access to resources can
be restricted to only users with that label.
  • Loading branch information
stnguyen90 committed Jun 5, 2023
1 parent d821d1f commit 2d770e6
Show file tree
Hide file tree
Showing 9 changed files with 147 additions and 5 deletions.
11 changes: 11 additions & 0 deletions src/Database/Helpers/Role.php
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,17 @@ public static function team(string $identifier, string $dimension = ''): self
return new self('team', $identifier, $dimension);
}

/**
* Create a label role from the given ID
*
* @param string $identifier
* @return Role
*/
public static function label(string $identifier): self
{
return new self('label', $identifier, '');
}

/**
* Create an any satisfy role
*
Expand Down
49 changes: 49 additions & 0 deletions src/Database/Validator/Alphanumeric.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<?php

namespace Utopia\Database\Validator;

use Utopia\Validator\Text;

class Alphanumeric extends Text
{
/**
* Alphanumeric constructor.
*
* Validate text with maximum length $length. Use $length = 0 for unlimited length.
*
* @param int $length
* @param int $min
*/
public function __construct(int $length, int $min = 1)
{
parent::__construct($length, $min, \array_merge(Text::ALPHABET_UPPER, Text::ALPHABET_LOWER, Text::NUMBERS));
}

/**
* Get Description
*
* Returns validator description
*
* @return string
*/
public function getDescription(): string
{
$message = 'Value must be a valid string';

if ($this->min === $this->length) {
$message .= ' and exactly '.$this->length.' chars';
} else {
if ($this->min) {
$message .= ' and at least '.$this->min.' chars';
}

if ($this->length) {
$message .= ' and no longer than '.$this->length.' chars';
}
}

$message .= ' and only consist of alphanumeric chars';

return $message;
}
}
17 changes: 17 additions & 0 deletions src/Database/Validator/Label.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?php

namespace Utopia\Database\Validator;

class Label extends Alphanumeric
{
/**
* Label constructor.
*
* Validate text is a valid label
*
*/
public function __construct()
{
parent::__construct(36, 1);
}
}
26 changes: 21 additions & 5 deletions src/Database/Validator/Roles.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ class Roles extends Validator
public const ROLE_USER = 'user';
public const ROLE_TEAM = 'team';
public const ROLE_MEMBER = 'member';
public const ROLE_LABEL = 'label';

public const ROLES = [
self::ROLE_ANY,
Expand All @@ -22,6 +23,7 @@ class Roles extends Validator
self::ROLE_USER,
self::ROLE_TEAM,
self::ROLE_MEMBER,
self::ROLE_LABEL,
];

protected string $message = 'Roles Error';
Expand Down Expand Up @@ -96,6 +98,16 @@ class Roles extends Validator
'required' => false,
],
],
self::ROLE_LABEL => [
'identifier' => [
'allowed' => true,
'required' => true,
],
'dimension' =>[
'allowed' => false,
'required' => false,
],
],
];

// Dimensions
Expand Down Expand Up @@ -226,6 +238,7 @@ protected function isValidRole(
string $dimension
): bool {
$key = new Key();
$label = new Label();

$config = self::CONFIG[$role] ?? null;

Expand All @@ -251,11 +264,14 @@ protected function isValidRole(
}

// Allowed and has an invalid identifier
if ($allowed
&& !empty($identifier)
&& !$key->isValid($identifier)) {
$this->message = 'Role "' . $role . '"' . ' identifier value is invalid: ' . $key->getDescription();
return false;
if ($allowed && !empty($identifier)) {
if ($role === self::ROLE_LABEL && !$label->isValid($identifier)) {
$this->message = 'Role "' . $role . '"' . ' identifier value is invalid: ' . $label->getDescription();
return false;
} elseif ($role !== self::ROLE_LABEL && !$key->isValid($identifier)) {
$this->message = 'Role "' . $role . '"' . ' identifier value is invalid: ' . $key->getDescription();
return false;
}
}

// Process dimension configuration
Expand Down
26 changes: 26 additions & 0 deletions tests/Database/Base.php
Original file line number Diff line number Diff line change
Expand Up @@ -10987,6 +10987,32 @@ public function testCollectionPermissionsRelationshipsDeleteWorks(array $data):
));
}

public function testLabels(): void
{
$this->assertInstanceOf('Utopia\Database\Document', static::getDatabase()->createCollection(
'labels_test',
));
static::getDatabase()->createAttribute('labels_test', 'attr1', Database::VAR_STRING, 10, false);

static::getDatabase()->createDocument('labels_test', new Document([
'$id' => 'doc1',
'attr1' => 'value1',
'$permissions' => [
Permission::read(Role::label('reader')),
],
]));

$documents = static::getDatabase()->find('labels_test');

$this->assertEmpty($documents);

Authorization::setRole(Role::label('reader')->toString());

$documents = static::getDatabase()->find('labels_test');

$this->assertCount(1, $documents);
}

public function testEvents(): void
{
Authorization::skip(function () {
Expand Down
3 changes: 3 additions & 0 deletions tests/Database/PermissionTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,9 @@ public function testInvalidFormats(): void

$this->expectException(\Exception::class);
Permission::parse('read("users/")');

$this->expectException(\Exception::class);
Permission::parse('read("label:alphanumeric-only")');
}

/**
Expand Down
11 changes: 11 additions & 0 deletions tests/Database/RoleTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,11 @@ public function testOutputFromString(): void
$this->assertEquals('users', $role->getRole());
$this->assertEmpty($role->getIdentifier());
$this->assertEquals('verified', $role->getDimension());

$role = Role::parse('label:vip');
$this->assertEquals('label', $role->getRole());
$this->assertEquals('vip', $role->getIdentifier());
$this->assertEmpty($role->getDimension());
}

public function testInputFromParameters(): void
Expand All @@ -70,6 +75,9 @@ public function testInputFromParameters(): void

$role = new Role('team', '123', '456');
$this->assertEquals('team:123/456', $role->toString());

$role = new Role('label', 'vip');
$this->assertEquals('label:vip', $role->toString());
}

public function testInputFromRoles(): void
Expand All @@ -91,6 +99,9 @@ public function testInputFromRoles(): void

$role = Role::team(ID::custom('123'), '456');
$this->assertEquals('team:123/456', $role->toString());

$role = Role::label('vip');
$this->assertEquals('label:vip', $role->toString());
}

public function testInputFromID(): void
Expand Down
2 changes: 2 additions & 0 deletions tests/Database/Validator/PermissionsTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,8 @@ public function testSingleMethodSingleValue(): void
$this->assertTrue($object->isValid($document->getPermissions()));
$document['$permissions'] = [Permission::delete(Role::users('verified'))];
$this->assertTrue($object->isValid($document->getPermissions()));
$document['$permissions'] = [Permission::delete(Role::label('vip'))];
$this->assertTrue($object->isValid($document->getPermissions()));
}

public function testMultipleMethodSingleValue(): void
Expand Down
7 changes: 7 additions & 0 deletions tests/Database/Validator/RolesTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -73,4 +73,11 @@ public function testDisallowedRoles(): void
$this->assertFalse($object->isValid([Role::any()->toString()]));
$this->assertEquals('Role "any" is not allowed. Must be one of: users.', $object->getDescription());
}

public function testLabels(): void
{
$object = new Roles();
$this->assertTrue($object->isValid(['label:123']));
$this->assertFalse($object->isValid(['label:not-alphanumeric']));
}
}

0 comments on commit 2d770e6

Please sign in to comment.