Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
31 changes: 31 additions & 0 deletions src/Database/Validator/Label.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?php

namespace Utopia\Database\Validator;

class Label extends Key
{
protected string $message = 'Value must be a valid string between 1 and 36 chars containing only alphanumeric chars';

/**
* Is valid.
*
* Returns true if valid or false if not.
*
* @param $value
*
* @return bool
*/
public function isValid($value): bool
{
if (!parent::isValid($value)) {
return false;
}

// Valid chars: A-Z, a-z, 0-9
if (\preg_match('/[^A-Za-z0-9]/', $value)) {
return false;
}

return true;
}
}
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
67 changes: 67 additions & 0 deletions tests/Database/Validator/LabelTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
<?php

namespace Utopia\Tests\Validator;

use Utopia\Database\Validator\Label;
use PHPUnit\Framework\TestCase;

class LabelTest extends TestCase
{
/**
* @var Label
*/
protected ?Label $object = null;

public function setUp(): void
{
$this->object = new Label();
}

public function tearDown(): void
{
}

public function testValues(): void
{
// Must be strings
$this->assertEquals(false, $this->object->isValid(false));
$this->assertEquals(false, $this->object->isValid(null));
$this->assertEquals(false, $this->object->isValid(['value']));
$this->assertEquals(false, $this->object->isValid(0));
$this->assertEquals(false, $this->object->isValid(1.5));
$this->assertEquals(true, $this->object->isValid('asdas7as9as'));
$this->assertEquals(true, $this->object->isValid('5f058a8925807'));

// No special chars
$this->assertEquals(false, $this->object->isValid('_asdasdasdas'));
$this->assertEquals(false, $this->object->isValid('.as5dasdasdas'));
$this->assertEquals(false, $this->object->isValid('-as5dasdasdas'));
$this->assertEquals(false, $this->object->isValid('as5dadasdas_'));
$this->assertEquals(false, $this->object->isValid('as_5dasdasdas'));
$this->assertEquals(false, $this->object->isValid('as5dasdasdas.'));
$this->assertEquals(false, $this->object->isValid('as.5dasdasdas'));
$this->assertEquals(false, $this->object->isValid('as5dasdasdas-'));
$this->assertEquals(false, $this->object->isValid('as-5dasdasdas'));
$this->assertEquals(false, $this->object->isValid('dasda asdasd'));
$this->assertEquals(false, $this->object->isValid('asd"asd6sdas'));
$this->assertEquals(false, $this->object->isValid('asd\'as0asdas'));
$this->assertEquals(false, $this->object->isValid('as!5dasdasdas'));
$this->assertEquals(false, $this->object->isValid('as@5dasdasdas'));
$this->assertEquals(false, $this->object->isValid('as#5dasdasdas'));
$this->assertEquals(false, $this->object->isValid('as$5dasdasdas'));
$this->assertEquals(false, $this->object->isValid('as%5dasdasdas'));
$this->assertEquals(false, $this->object->isValid('as^5dasdasdas'));
$this->assertEquals(false, $this->object->isValid('as&5dasdasdas'));
$this->assertEquals(false, $this->object->isValid('as*5dasdasdas'));
$this->assertEquals(false, $this->object->isValid('as(5dasdasdas'));
$this->assertEquals(false, $this->object->isValid('as)5dasdasdas'));
$this->assertEquals(false, $this->object->isValid('as+5dasdasdas'));
$this->assertEquals(false, $this->object->isValid('as=5dasdasdas'));

// At most 36 chars
$this->assertEquals(true, $this->object->isValid('socialAccountForYoutubeSubscribersss'));
$this->assertEquals(false, $this->object->isValid('socialAccountForYoutubeSubscriberssss'));
$this->assertEquals(true, $this->object->isValid('5f058a89258075f058a89258075f058t9214'));
$this->assertEquals(false, $this->object->isValid('5f058a89258075f058a89258075f058tx9214'));
}
}
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']));
}
}