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
1 change: 1 addition & 0 deletions .github/workflows/continuous-integration.yml
Original file line number Diff line number Diff line change
Expand Up @@ -155,3 +155,4 @@ jobs:
env:
DOCTRINE_MONGODB_SERVER: ${{ steps.setup-mongodb.outputs.cluster-uri }}
USE_LAZY_GHOST_OBJECTS: ${{ matrix.proxy == 'lazy-ghost' && '1' || '0' }}"
CRYPT_SHARED_LIB_PATH: ${{ steps.setup-mongodb.outputs.crypt-shared-lib-path }}
4 changes: 2 additions & 2 deletions lib/Doctrine/ODM/MongoDB/Mapping/Annotations/Encrypt.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ final class Encrypt implements Annotation
/**
* @param EncryptQuery|null $queryType Set the query type for the field, null if not queryable.
* @param int<1, 4>|null $sparsity
* @param positive-int|null $prevision
* @param positive-int|null $precision
* @param positive-int|null $trimFactor
* @param positive-int|null $contention
*/
Expand All @@ -37,7 +37,7 @@ public function __construct(
int|float|Int64|Decimal128|UTCDateTime|DateTimeInterface|null $min = null,
int|float|Int64|Decimal128|UTCDateTime|DateTimeInterface|null $max = null,
public ?int $sparsity = null,
public ?int $prevision = null,
public ?int $precision = null,
public ?int $trimFactor = null,
public ?int $contention = null,
) {
Expand Down
4 changes: 2 additions & 2 deletions lib/Doctrine/ODM/MongoDB/Mapping/ClassMetadata.php
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@
* order?: int|string,
* background?: bool,
* enumType?: class-string<BackedEnum>,
* encrypt?: array{queryType?: ?string, min?: mixed, max?: mixed, sparsity?: int<1, 4>, prevision?: int, trimFactor?: int, contention?: int}
* encrypt?: array{queryType?: ?string, min?: mixed, max?: mixed, sparsity?: int<1, 4>, precision?: int, trimFactor?: int, contention?: int}
* }
* @phpstan-type FieldMapping array{
* type: string,
Expand Down Expand Up @@ -154,7 +154,7 @@
* alsoLoadFields?: list<string>,
* enumType?: class-string<BackedEnum>,
* storeEmptyArray?: bool,
* encrypt?: array{queryType?: ?string, min?: mixed, max?: mixed, sparsity?: int<1, 4>, prevision?: int, trimFactor?: int, contention?: int},
* encrypt?: array{queryType?: ?string, min?: mixed, max?: mixed, sparsity?: int<1, 4>, precision?: int, trimFactor?: int, contention?: int},
* }
* @phpstan-type AssociationFieldMapping array{
* type?: string,
Expand Down
13 changes: 2 additions & 11 deletions lib/Doctrine/ODM/MongoDB/Mapping/Driver/XmlDriver.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

namespace Doctrine\ODM\MongoDB\Mapping\Driver;

use DateTimeImmutable;
use Doctrine\ODM\MongoDB\Mapping\Annotations\TimeSeries;
use Doctrine\ODM\MongoDB\Mapping\ClassMetadata;
use Doctrine\ODM\MongoDB\Mapping\MappingException;
Expand All @@ -15,9 +14,7 @@
use DOMDocument;
use InvalidArgumentException;
use LibXMLError;
use MongoDB\BSON\Decimal128;
use MongoDB\BSON\Document;
use MongoDB\BSON\UTCDateTime;
use MongoDB\Driver\Exception\UnexpectedValueException;
use SimpleXMLElement;

Expand Down Expand Up @@ -319,14 +316,8 @@ public function loadMetadataForClass($className, \Doctrine\Persistence\Mapping\C
foreach ($field->encrypt->attributes() as $encryptKey => $encryptValue) {
$mapping['encrypt'][$encryptKey] = match ($encryptKey) {
'queryType' => (string) $encryptValue,
'min', 'max' => match ($mapping['type']) {
Type::INT => (int) $encryptValue,
Type::FLOAT => (float) $encryptValue,
Type::DECIMAL128 => new Decimal128((string) $encryptValue),
Type::DATE, Type::DATE_IMMUTABLE => new UTCDateTime(new DateTimeImmutable((string) $encryptValue)),
default => null, // Invalid
},
'sparsity', 'prevision', 'trimFactor', 'contention' => (int) $encryptValue,
'min', 'max' => Type::getType($mapping['type'])->convertToDatabaseValue((string) $encryptValue),
'sparsity', 'precision', 'trimFactor', 'contention' => (int) $encryptValue,
};
}
}
Expand Down
19 changes: 15 additions & 4 deletions lib/Doctrine/ODM/MongoDB/Utility/EncryptedFieldsMapGenerator.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,14 @@
use Doctrine\ODM\MongoDB\Mapping\ClassMetadata;
use Doctrine\ODM\MongoDB\Mapping\ClassMetadataFactoryInterface;
use Doctrine\ODM\MongoDB\Mapping\MappingException;
use Doctrine\ODM\MongoDB\Types\Type;
use Generator;
use LogicException;

use function array_filter;
use function assert;
use function iterator_to_array;
use function sprintf;

final class EncryptedFieldsMapGenerator
{
Expand Down Expand Up @@ -108,11 +111,19 @@ private function createEncryptedFieldsMapForClass(
$field = [
'path' => $path . $mapping['name'],
'bsonType' => match ($mapping['type']) {
'one' => 'object',
'many' => 'array',
default => $mapping['type'],
ClassMetadata::ONE, Type::HASH => 'object',
ClassMetadata::MANY, Type::COLLECTION => 'array',
Type::INT, Type::INTEGER => 'int',
Type::FLOAT => 'double',
Type::DECIMAL128 => 'decimal',
Type::DATE, Type::DATE_IMMUTABLE => 'date',
Type::TIMESTAMP => 'timestamp',
Type::OBJECTID => 'objectId',
Type::STRING => 'string',
Type::BINDATA, Type::BINDATABYTEARRAY, Type::BINDATAFUNC, Type::BINDATACUSTOM, Type::BINDATAUUID, Type::BINDATAMD5, Type::BINDATAUUIDRFC4122 => 'binData',
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it make sense to add a getBSONType method to the Type class and override it in the individual types? Happy to defer this work until later, but I think it may be more efficient especially if people use custom types that map to a given BSON type.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was thinking to add a bsonType parameter to the #[Encrypt] attribute, for custom needs. Adding Type::getBSONType() is a better option. PHPORM-358

Type::BOOL, Type::BOOLEAN => 'bool',
default => throw new LogicException(sprintf('Type "%s" is not supported in encrypted fields map.', $mapping['type'])),
},
// @todo allow setting a keyId in #[Encrypt] attribute
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@todo removed, we don't need to support custom keyId with queryable encryption auto-encryption. They are generated automatically.

'keyId' => null, // Generate the key automatically
];

Expand Down
22 changes: 8 additions & 14 deletions phpstan-baseline.neon
Original file line number Diff line number Diff line change
Expand Up @@ -961,7 +961,7 @@ parameters:
path: lib/Doctrine/ODM/MongoDB/Mapping/Driver/XmlDriver.php

-
message: '#^Parameter \#2 \$mapping of method Doctrine\\ODM\\MongoDB\\Mapping\\Driver\\XmlDriver\:\:addFieldMapping\(\) expects array\{type\?\: string, fieldName\?\: string, name\?\: string, strategy\?\: string, association\?\: int, id\?\: bool, isOwningSide\?\: bool, collectionClass\?\: class\-string, \.\.\.\}, array\<string, array\<int\<0, max\>\|string, float\|int\|MongoDB\\BSON\\Decimal128\|MongoDB\\BSON\\UTCDateTime\|string\|null\>\|bool\|string\> given\.$#'
message: '#^Parameter \#2 \$mapping of method Doctrine\\ODM\\MongoDB\\Mapping\\Driver\\XmlDriver\:\:addFieldMapping\(\) expects array\{type\?\: string, fieldName\?\: string, name\?\: string, strategy\?\: string, association\?\: int, id\?\: bool, isOwningSide\?\: bool, collectionClass\?\: class\-string, \.\.\.\}, array\<string, array\<int\<0, max\>\|string, mixed\>\|bool\|string\> given\.$#'
identifier: argument.type
count: 1
path: lib/Doctrine/ODM/MongoDB/Mapping/Driver/XmlDriver.php
Expand Down Expand Up @@ -1668,12 +1668,6 @@ parameters:
count: 3
path: tests/Doctrine/ODM/MongoDB/Tests/Functional/QueryableEncryptionTest.php

-
message: '#^Parameter \#1 \$options of method Doctrine\\ODM\\MongoDB\\Configuration\:\:setAutoEncryption\(\) expects array\{keyVaultNamespace\: string, kmsProviders\: array\<string, array\<string, string\>\>, tlsOptions\?\: array\{kmip\: array\{tlsCAFile\: string, tlsCertificateKeyFile\: string\}\}\}, array\{keyVaultNamespace\: non\-falsy\-string, kmsProviders\: array\{local\: array\{key\: MongoDB\\BSON\\Binary\}\}\} given\.$#'
identifier: argument.type
count: 1
path: tests/Doctrine/ODM/MongoDB/Tests/Functional/QueryableEncryptionTest.php

-
message: '#^Parameter \#1 \$value of function count expects array\|Countable, Iterator\<int, string\> given\.$#'
identifier: argument.type
Expand Down Expand Up @@ -2005,43 +1999,43 @@ parameters:
path: tests/Doctrine/ODM/MongoDB/Tests/Tools/EncryptedFieldsMapGeneratorTest.php

-
message: '#^Method Doctrine\\Persistence\\Mapping\\AbstractClassMetadataFactory@anonymous/tests/Doctrine/ODM/MongoDB/Tests/Tools/EncryptedFieldsMapGeneratorTest\.php\:165\:\:__construct\(\) has parameter \$classNames with no value type specified in iterable type array\.$#'
message: '#^Method Doctrine\\Persistence\\Mapping\\AbstractClassMetadataFactory@anonymous/tests/Doctrine/ODM/MongoDB/Tests/Tools/EncryptedFieldsMapGeneratorTest\.php\:168\:\:__construct\(\) has parameter \$classNames with no value type specified in iterable type array\.$#'
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The anonymous class name contains the line number it's defined. Each time the previous test is updated, this baseline entries need to be updated.

identifier: missingType.iterableValue
count: 1
path: tests/Doctrine/ODM/MongoDB/Tests/Tools/EncryptedFieldsMapGeneratorTest.php

-
message: '#^Method Doctrine\\Persistence\\Mapping\\AbstractClassMetadataFactory@anonymous/tests/Doctrine/ODM/MongoDB/Tests/Tools/EncryptedFieldsMapGeneratorTest\.php\:165\:\:doLoadMetadata\(\) has parameter \$class with generic interface Doctrine\\Persistence\\Mapping\\ClassMetadata but does not specify its types\: T$#'
message: '#^Method Doctrine\\Persistence\\Mapping\\AbstractClassMetadataFactory@anonymous/tests/Doctrine/ODM/MongoDB/Tests/Tools/EncryptedFieldsMapGeneratorTest\.php\:168\:\:doLoadMetadata\(\) has parameter \$class with generic interface Doctrine\\Persistence\\Mapping\\ClassMetadata but does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: tests/Doctrine/ODM/MongoDB/Tests/Tools/EncryptedFieldsMapGeneratorTest.php

-
message: '#^Method Doctrine\\Persistence\\Mapping\\AbstractClassMetadataFactory@anonymous/tests/Doctrine/ODM/MongoDB/Tests/Tools/EncryptedFieldsMapGeneratorTest\.php\:165\:\:doLoadMetadata\(\) has parameter \$parent with generic interface Doctrine\\Persistence\\Mapping\\ClassMetadata but does not specify its types\: T$#'
message: '#^Method Doctrine\\Persistence\\Mapping\\AbstractClassMetadataFactory@anonymous/tests/Doctrine/ODM/MongoDB/Tests/Tools/EncryptedFieldsMapGeneratorTest\.php\:168\:\:doLoadMetadata\(\) has parameter \$parent with generic interface Doctrine\\Persistence\\Mapping\\ClassMetadata but does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: tests/Doctrine/ODM/MongoDB/Tests/Tools/EncryptedFieldsMapGeneratorTest.php

-
message: '#^Method Doctrine\\Persistence\\Mapping\\AbstractClassMetadataFactory@anonymous/tests/Doctrine/ODM/MongoDB/Tests/Tools/EncryptedFieldsMapGeneratorTest\.php\:165\:\:getAllMetadata\(\) return type with generic interface Doctrine\\Persistence\\Mapping\\ClassMetadata does not specify its types\: T$#'
message: '#^Method Doctrine\\Persistence\\Mapping\\AbstractClassMetadataFactory@anonymous/tests/Doctrine/ODM/MongoDB/Tests/Tools/EncryptedFieldsMapGeneratorTest\.php\:168\:\:getAllMetadata\(\) return type with generic interface Doctrine\\Persistence\\Mapping\\ClassMetadata does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: tests/Doctrine/ODM/MongoDB/Tests/Tools/EncryptedFieldsMapGeneratorTest.php

-
message: '#^Method Doctrine\\Persistence\\Mapping\\AbstractClassMetadataFactory@anonymous/tests/Doctrine/ODM/MongoDB/Tests/Tools/EncryptedFieldsMapGeneratorTest\.php\:165\:\:initializeReflection\(\) has parameter \$class with generic interface Doctrine\\Persistence\\Mapping\\ClassMetadata but does not specify its types\: T$#'
message: '#^Method Doctrine\\Persistence\\Mapping\\AbstractClassMetadataFactory@anonymous/tests/Doctrine/ODM/MongoDB/Tests/Tools/EncryptedFieldsMapGeneratorTest\.php\:168\:\:initializeReflection\(\) has parameter \$class with generic interface Doctrine\\Persistence\\Mapping\\ClassMetadata but does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: tests/Doctrine/ODM/MongoDB/Tests/Tools/EncryptedFieldsMapGeneratorTest.php

-
message: '#^Method Doctrine\\Persistence\\Mapping\\AbstractClassMetadataFactory@anonymous/tests/Doctrine/ODM/MongoDB/Tests/Tools/EncryptedFieldsMapGeneratorTest\.php\:165\:\:isEntity\(\) has parameter \$class with generic interface Doctrine\\Persistence\\Mapping\\ClassMetadata but does not specify its types\: T$#'
message: '#^Method Doctrine\\Persistence\\Mapping\\AbstractClassMetadataFactory@anonymous/tests/Doctrine/ODM/MongoDB/Tests/Tools/EncryptedFieldsMapGeneratorTest\.php\:168\:\:isEntity\(\) has parameter \$class with generic interface Doctrine\\Persistence\\Mapping\\ClassMetadata but does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: tests/Doctrine/ODM/MongoDB/Tests/Tools/EncryptedFieldsMapGeneratorTest.php

-
message: '#^Method Doctrine\\Persistence\\Mapping\\AbstractClassMetadataFactory@anonymous/tests/Doctrine/ODM/MongoDB/Tests/Tools/EncryptedFieldsMapGeneratorTest\.php\:165\:\:wakeupReflection\(\) has parameter \$class with generic interface Doctrine\\Persistence\\Mapping\\ClassMetadata but does not specify its types\: T$#'
message: '#^Method Doctrine\\Persistence\\Mapping\\AbstractClassMetadataFactory@anonymous/tests/Doctrine/ODM/MongoDB/Tests/Tools/EncryptedFieldsMapGeneratorTest\.php\:168\:\:wakeupReflection\(\) has parameter \$class with generic interface Doctrine\\Persistence\\Mapping\\ClassMetadata but does not specify its types\: T$#'
identifier: missingType.generics
count: 1
path: tests/Doctrine/ODM/MongoDB/Tests/Tools/EncryptedFieldsMapGeneratorTest.php
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
use MongoDB\Model\BSONDocument;

use function count;
use function getenv;
use function iterator_to_array;
use function random_bytes;

Expand Down Expand Up @@ -99,6 +100,15 @@ protected static function createTestDocumentManager(): DocumentManager
'key' => new Binary(random_bytes(96)),
]);

$autoEncryptionOptions = [];

$cryptSharedLibPath = getenv('CRYPT_SHARED_LIB_PATH');
if ($cryptSharedLibPath) {
$autoEncryptionOptions['extraOptions']['cryptSharedLibPath'] = $cryptSharedLibPath;
}

$config->setAutoEncryption($autoEncryptionOptions);

$client = new Client(self::getUri(), [], $config->getDriverOptions());

return DocumentManager::create($client, $config);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,24 +90,27 @@ public function testVariousRangeTypes(): void
],
[
'path' => 'floatField',
'bsonType' => 'float',
'bsonType' => 'double',
'keyId' => null,
'queries' => ['queryType' => 'range', 'min' => 5.5, 'max' => 10.5],
'queries' => ['queryType' => 'range', 'min' => 5.5, 'max' => 10.5, 'precision' => 1],
],
[
'path' => 'decimalField',
'bsonType' => 'decimal128',
'bsonType' => 'decimal',
'keyId' => null,
'queries' => ['queryType' => 'range', 'min' => new Decimal128('0.1'), 'max' => new Decimal128('0.2')],
'queries' => ['queryType' => 'range', 'min' => new Decimal128('0.1'), 'max' => new Decimal128('0.2'), 'precision' => 2],
],
[
'path' => 'dateField',
'bsonType' => 'date_immutable',
'bsonType' => 'date',
'keyId' => null,
'queries' => [
'queryType' => 'range',
'min' => new UTCDateTime(new DateTimeImmutable('2000-01-01 00:00:00')),
'max' => new UTCDateTime(new DateTimeImmutable('2100-01-01 00:00:00')),
'sparsity' => 1,
'trimFactor' => 3,
'contention' => 4,
],
],
];
Expand Down
13 changes: 10 additions & 3 deletions tests/Documents/Encryption/RangeTypes.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,21 @@ class RangeTypes
public int $intField;

#[Field(type: Type::FLOAT)]
#[Encrypt(EncryptQuery::Range, min: 5.5, max: 10.5)]
#[Encrypt(EncryptQuery::Range, min: 5.5, max: 10.5, precision: 1)]
public float $floatField;

#[Field(type: Type::DECIMAL128)]
#[Encrypt(EncryptQuery::Range, min: new Decimal128('0.1'), max: new Decimal128('0.2'))]
#[Encrypt(EncryptQuery::Range, min: new Decimal128('0.1'), max: new Decimal128('0.2'), precision: 2)]
public Decimal128 $decimalField;

#[Field(type: Type::DATE_IMMUTABLE)]
#[Encrypt(EncryptQuery::Range, min: new DateTimeImmutable('2000-01-01 00:00:00'), max: new DateTimeImmutable('2100-01-01 00:00:00'))]
#[Encrypt(
queryType: EncryptQuery::Range,
min: new DateTimeImmutable('2000-01-01 00:00:00'),
max: new DateTimeImmutable('2100-01-01 00:00:00'),
sparsity: 1,
trimFactor: 3,
contention: 4,
)]
public DateTimeImmutable $dateField;
}