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
7 changes: 7 additions & 0 deletions src/Database/Validator/Operator.php
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,13 @@ private function validateOperatorForAttribute(
$type = $attribute->type;
$isArray = $attribute->array;

// Operators run on the stored value, which for an encrypted attribute is the ciphertext
if (\in_array('encrypt', $attribute->filters, true)) {
$this->message = "Cannot apply {$methodName} operator to encrypted attribute '{$operator->getAttribute()}'";

return false;
}

// Array operators that carry a caller-supplied value list are capped to guard against
// memory exhaustion. Enforced here so every adapter rejects an oversized list the same way.
// The payload may be spread across $values or wrapped in $values[0] (the same shape the
Expand Down
38 changes: 38 additions & 0 deletions tests/e2e/Adapter/Scopes/OperatorTests.php
Original file line number Diff line number Diff line change
Expand Up @@ -694,6 +694,44 @@ public function testOperatorArrayErrorHandling(): void
$database->deleteCollection($collectionId);
}

public function testOperatorEncryptedAttribute(): void
{
/** @var Database $database */
$database = $this->getDatabase();

if (!$database->getAdapter()->supports(Capability::Operators)) {
$this->expectNotToPerformAssertions();
return;
}

$database->addFilter(
'encrypt',
fn (mixed $value): ?string => \is_string($value) ? (string) \json_encode(['data' => \base64_encode($value)]) : null,
function (mixed $value): ?string {
$decoded = \is_string($value) ? \json_decode($value, true) : null;

return \is_array($decoded) && \is_string($decoded['data'] ?? null) ? (string) \base64_decode($decoded['data']) : null;
},
);

$collectionId = 'test_operator_encrypted';
$database->createCollection(Collection::create(id: $collectionId));
$database->createAttribute($collectionId, Attribute::string(key: 'secret', size: 128, filters: ['encrypt']));

$database->createDocument($collectionId, new Document([
'$id' => 'doc1',
'$permissions' => [Permission::read(Role::any()), Permission::update(Role::any())],
'secret' => 'banana',
]));

$this->expectException(StructureException::class);
$this->expectExceptionMessage("Cannot apply stringReplace operator to encrypted attribute 'secret'");

$database->updateDocument($collectionId, 'doc1', new Document([
'secret' => Operator::stringReplace('a', 'o'),
]));
}

public function testOperatorInsertErrorHandling(): void
{
/** @var Database $database */
Expand Down
Loading