-
Type: Bug
-
Resolution: Unresolved
-
Priority: Minor - P4
-
None
-
Affects Version/s: None
-
Component/s: Serialization
-
None
-
(copied to CRM)
Summary
C# driver doesn't work with IReadOnlyCollection<s> types.
Customer is implementing CSFLE with C# driver, It is working with fields and generic IEnumerable<> types but recently a new feature tried to use IReadOnlyCollection<s> which should have been encrypted, but is not. They think the issue is somewhere in the C# driver as they are not getting called back into PropertyEncrypterBsonSerializer. Adding a .ToList() to the update operation ( after the Select()) fixes the issue.
Please provide the version of the driver. If applicable, please provide the MongoDB server version and topology (standalone, replica set, or sharded cluster).
MongoDB 7.0.2 (Replica Set)
net6.0 (also LINQv2)
C# Driver 2.18.0
How to Reproduce
Steps to reproduce. If possible, please include a Short, Self Contained, Correct (Compilable), Example.
Here is the sample code customer is using:
__
public class EncryptedPropertiesConvention : ConventionBase, IPostProcessingConvention { public const string ConventionName = "Encryption"; private readonly IKeyResolver _keyResolver; private readonly ConcurrentDictionary<SecretId, AsyncLazy<SymmetricKeyDto[]>> _keyCache; private readonly ConcurrentDictionary<Type, IBsonSerializer> _serializerCache; private EncryptedPropertiesConvention(IKeyResolver keyResolver) { this._keyResolver = keyResolver; this._keyCache = new ConcurrentDictionary<SecretId, AsyncLazy<SymmetricKeyDto[]>>(); this._serializerCache = new ConcurrentDictionary<Type, IBsonSerializer>(); } /// <summary> /// Applies a post processing modification to the class map. /// </summary> /// <param name="classMap">The class map.</param> public void PostProcess(BsonClassMap classMap) { foreach (var memberMap in classMap.DeclaredMemberMaps.Where(x => x.MemberInfo.GetCustomAttribute<SensitiveInformationPropertyAttribute>() != null)) { var serializer = memberMap.GetSerializer(); memberMap.SetSerializer(this.CreateSerializer(serializer)); } } public static void Register(IKeyResolver keyResolver) { // Clear pre-existing encryption before activating new encryption ConventionRegistry.Remove(ConventionName); ConventionRegistry.Register( ConventionName, new ConventionPack { new EncryptedPropertiesConvention(keyResolver), }, EncryptionHelper.HasPropertiesToEncrypt); } private IBsonSerializer CreateSerializer(IBsonSerializer serializer) { IBsonSerializer Create(Type t) { var serializerType = typeof(PropertyEncrypterBsonSerializer<>).MakeGenericType(serializer.ValueType); return (IBsonSerializer)Activator.CreateInstance(serializerType, serializer, this._keyResolver, this._keyCache); } return this._serializerCache.GetOrAdd(serializer.ValueType, Create); } }`
For exemple, a document that has the following properties will only encrypt the IEnumerable<s> type:
__
public class MyMongoCollection { public Guid PrimaryKey { get; set; } [SensitiveInformationProperty] public IEnumerable<string> UserEnumerable { get; set; } = new List<string>(); [SensitiveInformationProperty] public IReadOnlyCollection<string> UserReadOnly { get; set; } = new List<string>(); [SensitiveInformationProperty] public IList<string> UserList { get; set; } = new List<string>(); } public class User { public User(string name, string email) { this.Name = name; this.Email = email; } public string Name { get; set; } public string Email { get; set; } } private void SimpleUpsert(Guid primaryKey) { var filter = Builders<MyMongoCollection>.Filter.Eq(x => x.PrimaryKey, primaryKey); User[] usersData = { new User("George", "george@email.com") }; var update = Builders<MyMongoCollection>.Update .SetOnInsert(d => d.PrimaryKey, primaryKey) .Set(d => d.UserEnumerable, usersData?.Select(x => x.Email)) .Set(d => d.UserReadOnly, usersData?.Select(x => x.Email)) .Set(d => d.UserList, usersData?.Select(x => x.Email)); // Fetch the collection with the driver // then callls // await collection.UpdateOneAsync(filter, update, new UpdateOptions { IsUpsert = true }); }