From 9be8e29385c9d9262206a60a1c3f0a81f41b06e5 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Tue, 21 Oct 2025 01:27:30 +1300 Subject: [PATCH 1/4] Passthru max UID --- src/Database/Database.php | 5 +++++ src/Database/Validator/Key.php | 23 +++++--------------- src/Database/Validator/Queries/Documents.php | 12 +++++----- src/Database/Validator/Query/Cursor.php | 6 ++++- src/Database/Validator/UID.php | 2 +- 5 files changed, 24 insertions(+), 24 deletions(-) diff --git a/src/Database/Database.php b/src/Database/Database.php index a5af5996e..c8579a83e 100644 --- a/src/Database/Database.php +++ b/src/Database/Database.php @@ -5151,6 +5151,7 @@ public function updateDocuments( $indexes, $this->adapter->getIdAttributeType(), $this->maxQueryValues, + $this->adapter->getMaxUIDLength(), $this->adapter->getMinDateTime(), $this->adapter->getMaxDateTime(), $this->adapter->getSupportForAttributes() @@ -6710,6 +6711,7 @@ public function deleteDocuments( $indexes, $this->adapter->getIdAttributeType(), $this->maxQueryValues, + $this->adapter->getMaxUIDLength(), $this->adapter->getMinDateTime(), $this->adapter->getMaxDateTime(), $this->adapter->getSupportForAttributes() @@ -6908,6 +6910,7 @@ public function find(string $collection, array $queries = [], string $forPermiss $indexes, $this->adapter->getIdAttributeType(), $this->maxQueryValues, + $this->adapter->getMaxUIDLength(), $this->adapter->getMinDateTime(), $this->adapter->getMaxDateTime(), $this->adapter->getSupportForAttributes() @@ -7139,6 +7142,7 @@ public function count(string $collection, array $queries = [], ?int $max = null) $indexes, $this->adapter->getIdAttributeType(), $this->maxQueryValues, + $this->adapter->getMaxUIDLength(), $this->adapter->getMinDateTime(), $this->adapter->getMaxDateTime(), $this->adapter->getSupportForAttributes() @@ -7204,6 +7208,7 @@ public function sum(string $collection, string $attribute, array $queries = [], $indexes, $this->adapter->getIdAttributeType(), $this->maxQueryValues, + $this->adapter->getMaxUIDLength(), $this->adapter->getMinDateTime(), $this->adapter->getMaxDateTime(), $this->adapter->getSupportForAttributes() diff --git a/src/Database/Validator/Key.php b/src/Database/Validator/Key.php index a8041222c..ed560fd9f 100644 --- a/src/Database/Validator/Key.php +++ b/src/Database/Validator/Key.php @@ -6,16 +6,6 @@ class Key extends Validator { - protected bool $allowInternal = false; // If true, you keys starting with $ are allowed - - /** - * Maximum length for Key validation - */ - protected int $maxLength; - - /** - * @var string - */ protected string $message; /** @@ -33,10 +23,10 @@ public function getDescription(): string /** * Expression constructor */ - public function __construct(bool $allowInternal = false, int $maxLength = 255) - { - $this->allowInternal = $allowInternal; - $this->maxLength = $maxLength; + public function __construct( + private readonly bool $allowInternal = false, + private readonly int $maxLength = 36, + ) { $this->message = 'Parameter must contain at most ' . $this->maxLength . ' chars. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char'; } @@ -46,7 +36,6 @@ public function __construct(bool $allowInternal = false, int $maxLength = 255) * Returns true if valid or false if not. * * @param $value - * * @return bool */ public function isValid($value): bool @@ -59,7 +48,7 @@ public function isValid($value): bool return false; } - // no leading special characters + // No leading special characters $leading = \mb_substr($value, 0, 1); if ($leading === '_' || $leading === '.' || $leading === '-') { return false; @@ -67,7 +56,6 @@ public function isValid($value): bool $isInternal = $leading === '$'; - if ($isInternal && !$this->allowInternal) { return false; } @@ -83,6 +71,7 @@ public function isValid($value): bool if (\preg_match('/[^A-Za-z0-9\_\-\.]/', $value)) { return false; } + // At most maxLength chars if (\mb_strlen($value) > $this->maxLength) { return false; diff --git a/src/Database/Validator/Queries/Documents.php b/src/Database/Validator/Queries/Documents.php index ebdfdf05a..e55852bb8 100644 --- a/src/Database/Validator/Queries/Documents.php +++ b/src/Database/Validator/Queries/Documents.php @@ -2,7 +2,6 @@ namespace Utopia\Database\Validator\Queries; -use Exception; use Utopia\Database\Database; use Utopia\Database\Document; use Utopia\Database\Validator\IndexedQueries; @@ -16,18 +15,21 @@ class Documents extends IndexedQueries { /** - * Expression constructor - * * @param array $attributes * @param array $indexes * @param string $idAttributeType - * @throws Exception + * @param int $maxValuesCount + * @param \DateTime $minAllowedDate + * @param \DateTime $maxAllowedDate + * @param bool $supportForAttributes + * @throws \Utopia\Database\Exception */ public function __construct( array $attributes, array $indexes, string $idAttributeType, int $maxValuesCount = 5000, + int $maxUIDLength = 36, \DateTime $minAllowedDate = new \DateTime('0000-01-01'), \DateTime $maxAllowedDate = new \DateTime('9999-12-31'), bool $supportForAttributes = true @@ -60,7 +62,7 @@ public function __construct( $validators = [ new Limit(), new Offset(), - new Cursor(), + new Cursor($maxUIDLength), new Filter( $attributes, $idAttributeType, diff --git a/src/Database/Validator/Query/Cursor.php b/src/Database/Validator/Query/Cursor.php index 46020d7b4..5dc2a81c6 100644 --- a/src/Database/Validator/Query/Cursor.php +++ b/src/Database/Validator/Query/Cursor.php @@ -8,6 +8,10 @@ class Cursor extends Base { + public function __construct(private readonly int $maxLength = 36) + { + } + /** * Is valid. * @@ -33,7 +37,7 @@ public function isValid($value): bool $cursor = $cursor->getId(); } - $validator = new UID(); + $validator = new UID($this->maxLength); if ($validator->isValid($cursor)) { return true; } diff --git a/src/Database/Validator/UID.php b/src/Database/Validator/UID.php index 4dab38185..e54a5932b 100644 --- a/src/Database/Validator/UID.php +++ b/src/Database/Validator/UID.php @@ -7,7 +7,7 @@ class UID extends Key /** * Expression constructor */ - public function __construct(int $maxLength = 255) + public function __construct(int $maxLength = 36) { parent::__construct(false, $maxLength); } From e487b554015a8a8d23dcf892b34ea11e80527b15 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Tue, 21 Oct 2025 01:32:49 +1300 Subject: [PATCH 2/4] Const default --- src/Database/Database.php | 1 + src/Database/Validator/Key.php | 5 +++-- src/Database/Validator/Label.php | 8 ++++++-- src/Database/Validator/Query/Cursor.php | 3 ++- src/Database/Validator/UID.php | 4 +++- 5 files changed, 15 insertions(+), 6 deletions(-) diff --git a/src/Database/Database.php b/src/Database/Database.php index c8579a83e..f012a3462 100644 --- a/src/Database/Database.php +++ b/src/Database/Database.php @@ -80,6 +80,7 @@ class Database public const MAX_DOUBLE = PHP_FLOAT_MAX; public const MAX_VECTOR_DIMENSIONS = 16000; public const MAX_ARRAY_INDEX_LENGTH = 255; + public const MAX_UID_DEFAULT_LENGTH = 36; // Global SRID for geographic coordinates (WGS84) public const DEFAULT_SRID = 4326; diff --git a/src/Database/Validator/Key.php b/src/Database/Validator/Key.php index ed560fd9f..843444677 100644 --- a/src/Database/Validator/Key.php +++ b/src/Database/Validator/Key.php @@ -2,6 +2,7 @@ namespace Utopia\Database\Validator; +use Utopia\Database\Database; use Utopia\Validator; class Key extends Validator @@ -24,8 +25,8 @@ public function getDescription(): string * Expression constructor */ public function __construct( - private readonly bool $allowInternal = false, - private readonly int $maxLength = 36, + protected readonly bool $allowInternal = false, + protected readonly int $maxLength = Database::MAX_UID_DEFAULT_LENGTH, ) { $this->message = 'Parameter must contain at most ' . $this->maxLength . ' chars. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char'; } diff --git a/src/Database/Validator/Label.php b/src/Database/Validator/Label.php index 79b9e56c6..cf09be0b1 100644 --- a/src/Database/Validator/Label.php +++ b/src/Database/Validator/Label.php @@ -2,10 +2,14 @@ namespace Utopia\Database\Validator; +use Utopia\Database\Database; + class Label extends Key { - public function __construct(bool $allowInternal = false, int $maxLength = 255) - { + public function __construct( + bool $allowInternal = false, + int $maxLength = Database::MAX_UID_DEFAULT_LENGTH + ) { parent::__construct($allowInternal, $maxLength); $this->message = 'Value must be a valid string between 1 and ' . $this->maxLength . ' chars containing only alphanumeric chars'; } diff --git a/src/Database/Validator/Query/Cursor.php b/src/Database/Validator/Query/Cursor.php index 5dc2a81c6..58053fe60 100644 --- a/src/Database/Validator/Query/Cursor.php +++ b/src/Database/Validator/Query/Cursor.php @@ -2,13 +2,14 @@ namespace Utopia\Database\Validator\Query; +use Utopia\Database\Database; use Utopia\Database\Document; use Utopia\Database\Query; use Utopia\Database\Validator\UID; class Cursor extends Base { - public function __construct(private readonly int $maxLength = 36) + public function __construct(private readonly int $maxLength = Database::MAX_UID_DEFAULT_LENGTH) { } diff --git a/src/Database/Validator/UID.php b/src/Database/Validator/UID.php index e54a5932b..743adbcde 100644 --- a/src/Database/Validator/UID.php +++ b/src/Database/Validator/UID.php @@ -2,12 +2,14 @@ namespace Utopia\Database\Validator; +use Utopia\Database\Database; + class UID extends Key { /** * Expression constructor */ - public function __construct(int $maxLength = 36) + public function __construct(int $maxLength = Database::MAX_UID_DEFAULT_LENGTH) { parent::__construct(false, $maxLength); } From ffdc2b3fb15e27277bc9412bf190b2d248222fb2 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Tue, 21 Oct 2025 01:59:13 +1300 Subject: [PATCH 3/4] Fix unit size check --- tests/unit/Validator/PermissionsTest.php | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/tests/unit/Validator/PermissionsTest.php b/tests/unit/Validator/PermissionsTest.php index 505e69dec..70f2df0f1 100644 --- a/tests/unit/Validator/PermissionsTest.php +++ b/tests/unit/Validator/PermissionsTest.php @@ -248,25 +248,25 @@ public function testInvalidPermissions(): void // team:$value, member:$value and user:$value must have valid Key for $value // No leading special chars $this->assertFalse($object->isValid([Permission::read(Role::user('_1234'))])); - $this->assertEquals('Role "user" identifier value is invalid: Parameter must contain at most 255 chars. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char', $object->getDescription()); + $this->assertEquals('Role "user" identifier value is invalid: Parameter must contain at most 36 chars. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char', $object->getDescription()); $this->assertFalse($object->isValid([Permission::read(Role::team('-1234'))])); - $this->assertEquals('Role "team" identifier value is invalid: Parameter must contain at most 255 chars. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char', $object->getDescription()); + $this->assertEquals('Role "team" identifier value is invalid: Parameter must contain at most 36 chars. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char', $object->getDescription()); $this->assertFalse($object->isValid([Permission::read(Role::member('.1234'))])); - $this->assertEquals('Role "member" identifier value is invalid: Parameter must contain at most 255 chars. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char', $object->getDescription()); + $this->assertEquals('Role "member" identifier value is invalid: Parameter must contain at most 36 chars. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char', $object->getDescription()); // No unsupported special characters $this->assertFalse($object->isValid([Permission::read(Role::user('12$4'))])); - $this->assertEquals('Role "user" identifier value is invalid: Parameter must contain at most 255 chars. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char', $object->getDescription()); + $this->assertEquals('Role "user" identifier value is invalid: Parameter must contain at most 36 chars. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char', $object->getDescription()); $this->assertFalse($object->isValid([Permission::read(Role::user('12&4'))])); - $this->assertEquals('Role "user" identifier value is invalid: Parameter must contain at most 255 chars. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char', $object->getDescription()); + $this->assertEquals('Role "user" identifier value is invalid: Parameter must contain at most 36 chars. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char', $object->getDescription()); $this->assertFalse($object->isValid([Permission::read(Role::user('ab(124'))])); - $this->assertEquals('Role "user" identifier value is invalid: Parameter must contain at most 255 chars. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char', $object->getDescription()); + $this->assertEquals('Role "user" identifier value is invalid: Parameter must contain at most 36 chars. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char', $object->getDescription()); - // Shorter than 255 chars + // Shorter than 36 chars - $this->assertTrue($object->isValid([Permission::read(Role::user(ID::custom(str_repeat('a', 255))))])); + $this->assertTrue($object->isValid([Permission::read(Role::user(ID::custom(str_repeat('a', 36))))])); $this->assertFalse($object->isValid([Permission::read(Role::user(ID::custom(str_repeat('a', 256))))])); - $this->assertEquals('Role "user" identifier value is invalid: Parameter must contain at most 255 chars. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char', $object->getDescription()); + $this->assertEquals('Role "user" identifier value is invalid: Parameter must contain at most 36 chars. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char', $object->getDescription()); // Permission role must begin with one of: member, role, team, user $this->assertFalse($object->isValid(['update("memmber:1234")'])); @@ -278,7 +278,7 @@ public function testInvalidPermissions(): void // Team permission $this->assertFalse($object->isValid([Permission::read(Role::team(ID::custom('_abcd')))])); - $this->assertEquals('Role "team" identifier value is invalid: Parameter must contain at most 255 chars. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char', $object->getDescription()); + $this->assertEquals('Role "team" identifier value is invalid: Parameter must contain at most 36 chars. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char', $object->getDescription()); $this->assertFalse($object->isValid([Permission::read(Role::team(ID::custom('abcd/')))])); $this->assertEquals('Dimension must not be empty', $object->getDescription()); $this->assertFalse($object->isValid([Permission::read(Role::team(ID::custom(''), 'abcd'))])); @@ -288,9 +288,9 @@ public function testInvalidPermissions(): void $this->assertFalse($object->isValid([Permission::read(Role::team(ID::custom('abcd'), 'e/fgh'))])); $this->assertEquals('Only one dimension can be provided', $object->getDescription()); $this->assertFalse($object->isValid([Permission::read(Role::team(ID::custom('ab&cd3'), 'efgh'))])); - $this->assertEquals('Role "team" identifier value is invalid: Parameter must contain at most 255 chars. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char', $object->getDescription()); + $this->assertEquals('Role "team" identifier value is invalid: Parameter must contain at most 36 chars. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char', $object->getDescription()); $this->assertFalse($object->isValid([Permission::read(Role::team(ID::custom('abcd'), 'ef*gh'))])); - $this->assertEquals('Role "team" dimension value is invalid: Parameter must contain at most 255 chars. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char', $object->getDescription()); + $this->assertEquals('Role "team" dimension value is invalid: Parameter must contain at most 36 chars. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char', $object->getDescription()); // Permission-list length must be valid $object = new Permissions(100); From 4a0aea1224e0bdf348e12357e7be240bcc1e17ae Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Tue, 21 Oct 2025 02:06:04 +1300 Subject: [PATCH 4/4] Fix max --- tests/unit/Validator/KeyTest.php | 4 ++-- tests/unit/Validator/LabelTest.php | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/unit/Validator/KeyTest.php b/tests/unit/Validator/KeyTest.php index e09ef402e..3c19346d8 100644 --- a/tests/unit/Validator/KeyTest.php +++ b/tests/unit/Validator/KeyTest.php @@ -66,8 +66,8 @@ public function testValues(): void $this->assertEquals(false, $this->object->isValid('as+5dasdasdas')); $this->assertEquals(false, $this->object->isValid('as=5dasdasdas')); - // At most 255 chars - $this->assertEquals(true, $this->object->isValid(str_repeat('a', 255))); + // At most 36 chars + $this->assertEquals(true, $this->object->isValid(str_repeat('a', 36))); $this->assertEquals(false, $this->object->isValid(str_repeat('a', 256))); // Internal keys diff --git a/tests/unit/Validator/LabelTest.php b/tests/unit/Validator/LabelTest.php index 3d9bf7576..a6dd50bef 100644 --- a/tests/unit/Validator/LabelTest.php +++ b/tests/unit/Validator/LabelTest.php @@ -59,7 +59,7 @@ public function testValues(): void $this->assertEquals(false, $this->object->isValid('as=5dasdasdas')); // At most 255 chars - $this->assertEquals(true, $this->object->isValid(str_repeat('a', 255))); + $this->assertEquals(true, $this->object->isValid(str_repeat('a', 36))); $this->assertEquals(false, $this->object->isValid(str_repeat('a', 256))); } }