Skip to content

Commit 2d770e6

Browse files
committed
Add support for label permissions
A user can be given a particular label and access to resources can be restricted to only users with that label.
1 parent d821d1f commit 2d770e6

File tree

9 files changed

+147
-5
lines changed

9 files changed

+147
-5
lines changed

src/Database/Helpers/Role.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,17 @@ public static function team(string $identifier, string $dimension = ''): self
140140
return new self('team', $identifier, $dimension);
141141
}
142142

143+
/**
144+
* Create a label role from the given ID
145+
*
146+
* @param string $identifier
147+
* @return Role
148+
*/
149+
public static function label(string $identifier): self
150+
{
151+
return new self('label', $identifier, '');
152+
}
153+
143154
/**
144155
* Create an any satisfy role
145156
*
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
<?php
2+
3+
namespace Utopia\Database\Validator;
4+
5+
use Utopia\Validator\Text;
6+
7+
class Alphanumeric extends Text
8+
{
9+
/**
10+
* Alphanumeric constructor.
11+
*
12+
* Validate text with maximum length $length. Use $length = 0 for unlimited length.
13+
*
14+
* @param int $length
15+
* @param int $min
16+
*/
17+
public function __construct(int $length, int $min = 1)
18+
{
19+
parent::__construct($length, $min, \array_merge(Text::ALPHABET_UPPER, Text::ALPHABET_LOWER, Text::NUMBERS));
20+
}
21+
22+
/**
23+
* Get Description
24+
*
25+
* Returns validator description
26+
*
27+
* @return string
28+
*/
29+
public function getDescription(): string
30+
{
31+
$message = 'Value must be a valid string';
32+
33+
if ($this->min === $this->length) {
34+
$message .= ' and exactly '.$this->length.' chars';
35+
} else {
36+
if ($this->min) {
37+
$message .= ' and at least '.$this->min.' chars';
38+
}
39+
40+
if ($this->length) {
41+
$message .= ' and no longer than '.$this->length.' chars';
42+
}
43+
}
44+
45+
$message .= ' and only consist of alphanumeric chars';
46+
47+
return $message;
48+
}
49+
}

src/Database/Validator/Label.php

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<?php
2+
3+
namespace Utopia\Database\Validator;
4+
5+
class Label extends Alphanumeric
6+
{
7+
/**
8+
* Label constructor.
9+
*
10+
* Validate text is a valid label
11+
*
12+
*/
13+
public function __construct()
14+
{
15+
parent::__construct(36, 1);
16+
}
17+
}

src/Database/Validator/Roles.php

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ class Roles extends Validator
1414
public const ROLE_USER = 'user';
1515
public const ROLE_TEAM = 'team';
1616
public const ROLE_MEMBER = 'member';
17+
public const ROLE_LABEL = 'label';
1718

1819
public const ROLES = [
1920
self::ROLE_ANY,
@@ -22,6 +23,7 @@ class Roles extends Validator
2223
self::ROLE_USER,
2324
self::ROLE_TEAM,
2425
self::ROLE_MEMBER,
26+
self::ROLE_LABEL,
2527
];
2628

2729
protected string $message = 'Roles Error';
@@ -96,6 +98,16 @@ class Roles extends Validator
9698
'required' => false,
9799
],
98100
],
101+
self::ROLE_LABEL => [
102+
'identifier' => [
103+
'allowed' => true,
104+
'required' => true,
105+
],
106+
'dimension' =>[
107+
'allowed' => false,
108+
'required' => false,
109+
],
110+
],
99111
];
100112

101113
// Dimensions
@@ -226,6 +238,7 @@ protected function isValidRole(
226238
string $dimension
227239
): bool {
228240
$key = new Key();
241+
$label = new Label();
229242

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

@@ -251,11 +264,14 @@ protected function isValidRole(
251264
}
252265

253266
// Allowed and has an invalid identifier
254-
if ($allowed
255-
&& !empty($identifier)
256-
&& !$key->isValid($identifier)) {
257-
$this->message = 'Role "' . $role . '"' . ' identifier value is invalid: ' . $key->getDescription();
258-
return false;
267+
if ($allowed && !empty($identifier)) {
268+
if ($role === self::ROLE_LABEL && !$label->isValid($identifier)) {
269+
$this->message = 'Role "' . $role . '"' . ' identifier value is invalid: ' . $label->getDescription();
270+
return false;
271+
} elseif ($role !== self::ROLE_LABEL && !$key->isValid($identifier)) {
272+
$this->message = 'Role "' . $role . '"' . ' identifier value is invalid: ' . $key->getDescription();
273+
return false;
274+
}
259275
}
260276

261277
// Process dimension configuration

tests/Database/Base.php

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10987,6 +10987,32 @@ public function testCollectionPermissionsRelationshipsDeleteWorks(array $data):
1098710987
));
1098810988
}
1098910989

10990+
public function testLabels(): void
10991+
{
10992+
$this->assertInstanceOf('Utopia\Database\Document', static::getDatabase()->createCollection(
10993+
'labels_test',
10994+
));
10995+
static::getDatabase()->createAttribute('labels_test', 'attr1', Database::VAR_STRING, 10, false);
10996+
10997+
static::getDatabase()->createDocument('labels_test', new Document([
10998+
'$id' => 'doc1',
10999+
'attr1' => 'value1',
11000+
'$permissions' => [
11001+
Permission::read(Role::label('reader')),
11002+
],
11003+
]));
11004+
11005+
$documents = static::getDatabase()->find('labels_test');
11006+
11007+
$this->assertEmpty($documents);
11008+
11009+
Authorization::setRole(Role::label('reader')->toString());
11010+
11011+
$documents = static::getDatabase()->find('labels_test');
11012+
11013+
$this->assertCount(1, $documents);
11014+
}
11015+
1099011016
public function testEvents(): void
1099111017
{
1099211018
Authorization::skip(function () {

tests/Database/PermissionTest.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -271,6 +271,9 @@ public function testInvalidFormats(): void
271271

272272
$this->expectException(\Exception::class);
273273
Permission::parse('read("users/")');
274+
275+
$this->expectException(\Exception::class);
276+
Permission::parse('read("label:alphanumeric-only")');
274277
}
275278

276279
/**

tests/Database/RoleTest.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,11 @@ public function testOutputFromString(): void
4949
$this->assertEquals('users', $role->getRole());
5050
$this->assertEmpty($role->getIdentifier());
5151
$this->assertEquals('verified', $role->getDimension());
52+
53+
$role = Role::parse('label:vip');
54+
$this->assertEquals('label', $role->getRole());
55+
$this->assertEquals('vip', $role->getIdentifier());
56+
$this->assertEmpty($role->getDimension());
5257
}
5358

5459
public function testInputFromParameters(): void
@@ -70,6 +75,9 @@ public function testInputFromParameters(): void
7075

7176
$role = new Role('team', '123', '456');
7277
$this->assertEquals('team:123/456', $role->toString());
78+
79+
$role = new Role('label', 'vip');
80+
$this->assertEquals('label:vip', $role->toString());
7381
}
7482

7583
public function testInputFromRoles(): void
@@ -91,6 +99,9 @@ public function testInputFromRoles(): void
9199

92100
$role = Role::team(ID::custom('123'), '456');
93101
$this->assertEquals('team:123/456', $role->toString());
102+
103+
$role = Role::label('vip');
104+
$this->assertEquals('label:vip', $role->toString());
94105
}
95106

96107
public function testInputFromID(): void

tests/Database/Validator/PermissionsTest.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,8 @@ public function testSingleMethodSingleValue(): void
9191
$this->assertTrue($object->isValid($document->getPermissions()));
9292
$document['$permissions'] = [Permission::delete(Role::users('verified'))];
9393
$this->assertTrue($object->isValid($document->getPermissions()));
94+
$document['$permissions'] = [Permission::delete(Role::label('vip'))];
95+
$this->assertTrue($object->isValid($document->getPermissions()));
9496
}
9597

9698
public function testMultipleMethodSingleValue(): void

tests/Database/Validator/RolesTest.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,4 +73,11 @@ public function testDisallowedRoles(): void
7373
$this->assertFalse($object->isValid([Role::any()->toString()]));
7474
$this->assertEquals('Role "any" is not allowed. Must be one of: users.', $object->getDescription());
7575
}
76+
77+
public function testLabels(): void
78+
{
79+
$object = new Roles();
80+
$this->assertTrue($object->isValid(['label:123']));
81+
$this->assertFalse($object->isValid(['label:not-alphanumeric']));
82+
}
7683
}

0 commit comments

Comments
 (0)