
committed by
GitHub

No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 1116 additions and 59 deletions
-
57src/MongoDB.Bson/ObjectModel/BsonTypeExtensions.cs
-
344src/MongoDB.Driver.Encryption/CsfleSchemaBuilder.cs
-
29src/MongoDB.Driver/Linq/Linq3Implementation/Ast/AstEnumExtensions.cs
-
33src/MongoDB.Driver/Linq/Linq3Implementation/Ast/Filters/AstTypeFilterOperation.cs
-
710tests/MongoDB.Driver.Tests/Encryption/CsfleSchemaBuilderTests.cs
-
1tests/MongoDB.Driver.Tests/Encryption/MongoEncryptionCreateCollectionExceptionTests.cs
-
1tests/MongoDB.Driver.Tests/Encryption/MongoEncryptionExceptionTests.cs
@ -0,0 +1,57 @@ |
|||
/* Copyright 2010-present MongoDB Inc. |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
|
|||
using System; |
|||
|
|||
namespace MongoDB.Bson |
|||
{ |
|||
/// <summary>
|
|||
/// A static class containing extension methods for <see cref="BsonType"/>.
|
|||
/// </summary>
|
|||
public static class BsonTypeExtensions |
|||
{ |
|||
/// <summary>
|
|||
/// Maps a <see cref="BsonType"/> to its corresponding server string representation.
|
|||
/// </summary>
|
|||
/// <param name="type">The input type to map.</param>
|
|||
public static string ToServerString(this BsonType type) |
|||
{ |
|||
return type switch |
|||
{ |
|||
BsonType.Array => "array", |
|||
BsonType.Binary => "binData", |
|||
BsonType.Boolean => "bool", |
|||
BsonType.DateTime => "date", |
|||
BsonType.Decimal128 => "decimal", |
|||
BsonType.Document => "object", |
|||
BsonType.Double => "double", |
|||
BsonType.Int32 => "int", |
|||
BsonType.Int64 => "long", |
|||
BsonType.JavaScript => "javascript", |
|||
BsonType.JavaScriptWithScope => "javascriptWithScope", |
|||
BsonType.MaxKey => "maxKey", |
|||
BsonType.MinKey => "minKey", |
|||
BsonType.Null => "null", |
|||
BsonType.ObjectId => "objectId", |
|||
BsonType.RegularExpression => "regex", |
|||
BsonType.String => "string", |
|||
BsonType.Symbol => "symbol", |
|||
BsonType.Timestamp => "timestamp", |
|||
BsonType.Undefined => "undefined", |
|||
_ => throw new ArgumentException($"Unexpected BSON type: {type}.", nameof(type)) |
|||
}; |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,344 @@ |
|||
/* Copyright 2010-present MongoDB Inc. |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Linq.Expressions; |
|||
using MongoDB.Bson; |
|||
using MongoDB.Bson.Serialization; |
|||
|
|||
namespace MongoDB.Driver.Encryption |
|||
{ |
|||
/// <summary>
|
|||
/// A builder class for creating Client-Side Field Level Encryption (CSFLE) schemas.
|
|||
/// </summary>
|
|||
public class CsfleSchemaBuilder |
|||
{ |
|||
private readonly Dictionary<string, BsonDocument> _schemas = new(); |
|||
|
|||
private CsfleSchemaBuilder() |
|||
{ |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Creates a new instance of the <see cref="CsfleSchemaBuilder"/> and configures it using the provided action.
|
|||
/// </summary>
|
|||
/// <param name="configure">An action to configure the schema builder.</param>
|
|||
public static CsfleSchemaBuilder Create(Action<CsfleSchemaBuilder> configure) |
|||
{ |
|||
var builder = new CsfleSchemaBuilder(); |
|||
configure(builder); |
|||
return builder; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Adds an encrypted collection schema for a specific collection namespace.
|
|||
/// </summary>
|
|||
/// <typeparam name="T">The type of the document in the collection.</typeparam>
|
|||
/// <param name="collectionNamespace">The namespace of the collection.</param>
|
|||
/// <param name="configure">An action to configure the encrypted collection builder.</param>
|
|||
/// <returns>The current <see cref="CsfleSchemaBuilder"/> instance.</returns>
|
|||
public CsfleSchemaBuilder Encrypt<T>(CollectionNamespace collectionNamespace, Action<EncryptedCollectionBuilder<T>> configure) |
|||
{ |
|||
var builder = new EncryptedCollectionBuilder<T>(); |
|||
configure(builder); |
|||
_schemas.Add(collectionNamespace.FullName, builder.Build()); |
|||
return this; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Builds and returns the resulting CSFLE schema.
|
|||
/// </summary>
|
|||
public Dictionary<string, BsonDocument> Build() |
|||
{ |
|||
if (!_schemas.Any()) |
|||
{ |
|||
throw new InvalidOperationException("No schemas were added. Use Encrypt<T> to add a schema."); |
|||
} |
|||
|
|||
return _schemas; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// A builder class for creating encrypted collection schemas.
|
|||
/// </summary>
|
|||
/// <typeparam name="TDocument">The type of the document in the collection.</typeparam>
|
|||
public class EncryptedCollectionBuilder<TDocument> |
|||
{ |
|||
private readonly BsonDocument _schema = new("bsonType", "object"); |
|||
private readonly RenderArgs<TDocument> _args = new(BsonSerializer.LookupSerializer<TDocument>(), BsonSerializer.SerializerRegistry); |
|||
|
|||
internal EncryptedCollectionBuilder() |
|||
{ |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Configures encryption metadata for the collection.
|
|||
/// </summary>
|
|||
/// <param name="keyId">The key ID to use for encryption.</param>
|
|||
/// <param name="algorithm">The encryption algorithm to use.</param>
|
|||
/// <returns>The current <see cref="EncryptedCollectionBuilder{TDocument}"/> instance.</returns>
|
|||
public EncryptedCollectionBuilder<TDocument> EncryptMetadata(Guid? keyId = null, EncryptionAlgorithm? algorithm = null) |
|||
{ |
|||
if (keyId is null && algorithm is null) |
|||
{ |
|||
throw new ArgumentException("At least one of keyId or algorithm must be specified."); |
|||
} |
|||
|
|||
_schema["encryptMetadata"] = new BsonDocument |
|||
{ |
|||
{ "keyId", () => new BsonArray { new BsonBinaryData(keyId!.Value, GuidRepresentation.Standard) }, keyId is not null }, |
|||
{ "algorithm", () => MapCsfleEncryptionAlgorithmToString(algorithm!.Value), algorithm is not null } |
|||
}; |
|||
return this; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Adds a pattern property to the schema with encryption settings.
|
|||
/// </summary>
|
|||
/// <param name="pattern">The regex pattern for the property.</param>
|
|||
/// <param name="bsonType">The BSON type of the property.</param>
|
|||
/// <param name="algorithm">The encryption algorithm to use.</param>
|
|||
/// <param name="keyId">The key ID to use for encryption.</param>
|
|||
/// <returns>The current <see cref="EncryptedCollectionBuilder{TDocument}"/> instance.</returns>
|
|||
public EncryptedCollectionBuilder<TDocument> PatternProperty( |
|||
string pattern, |
|||
BsonType bsonType, |
|||
EncryptionAlgorithm? algorithm = null, |
|||
Guid? keyId = null) |
|||
=> PatternProperty(pattern, [bsonType], algorithm, keyId); |
|||
|
|||
/// <summary>
|
|||
/// Adds a pattern property to the schema with encryption settings.
|
|||
/// </summary>
|
|||
/// <param name="pattern">The regex pattern for the property.</param>
|
|||
/// <param name="bsonTypes">The BSON types of the property.</param>
|
|||
/// <param name="algorithm">The encryption algorithm to use.</param>
|
|||
/// <param name="keyId">The key ID to use for encryption.</param>
|
|||
/// <returns>The current <see cref="EncryptedCollectionBuilder{TDocument}"/> instance.</returns>
|
|||
public EncryptedCollectionBuilder<TDocument> PatternProperty( |
|||
string pattern, |
|||
IEnumerable<BsonType> bsonTypes = null, |
|||
EncryptionAlgorithm? algorithm = null, |
|||
Guid? keyId = null) |
|||
{ |
|||
AddToPatternProperties(pattern, CreateEncryptDocument(bsonTypes, algorithm, keyId)); |
|||
return this; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Adds a nested pattern property to the schema.
|
|||
/// </summary>
|
|||
/// <typeparam name="TField">The type of the nested field.</typeparam>
|
|||
/// <param name="path">The field.</param>
|
|||
/// <param name="configure">An action to configure the nested builder.</param>
|
|||
/// <returns>The current <see cref="EncryptedCollectionBuilder{TDocument}"/> instance.</returns>
|
|||
public EncryptedCollectionBuilder<TDocument> PatternProperty<TField>( |
|||
Expression<Func<TDocument, TField>> path, |
|||
Action<EncryptedCollectionBuilder<TField>> configure) |
|||
=> PatternProperty(new ExpressionFieldDefinition<TDocument, TField>(path), configure); |
|||
|
|||
/// <summary>
|
|||
/// Adds a nested pattern property to the schema.
|
|||
/// </summary>
|
|||
/// <typeparam name="TField">The type of the nested field.</typeparam>
|
|||
/// <param name="path">The field.</param>
|
|||
/// <param name="configure">An action to configure the nested builder.</param>
|
|||
/// <returns>The current <see cref="EncryptedCollectionBuilder{TDocument}"/> instance.</returns>
|
|||
public EncryptedCollectionBuilder<TDocument> PatternProperty<TField>( |
|||
FieldDefinition<TDocument> path, |
|||
Action<EncryptedCollectionBuilder<TField>> configure) |
|||
{ |
|||
var nestedBuilder = new EncryptedCollectionBuilder<TField>(); |
|||
configure(nestedBuilder); |
|||
|
|||
var fieldName = path.Render(_args).FieldName; |
|||
|
|||
AddToPatternProperties(fieldName, nestedBuilder.Build()); |
|||
return this; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Adds a property to the schema with encryption settings.
|
|||
/// </summary>
|
|||
/// <typeparam name="TField">The type of the field.</typeparam>
|
|||
/// <param name="path">The field.</param>
|
|||
/// <param name="bsonType">The BSON type of the property.</param>
|
|||
/// <param name="algorithm">The encryption algorithm to use.</param>
|
|||
/// <param name="keyId">The key ID to use for encryption.</param>
|
|||
/// <returns>The current <see cref="EncryptedCollectionBuilder{TDocument}"/> instance.</returns>
|
|||
public EncryptedCollectionBuilder<TDocument> Property<TField>( |
|||
Expression<Func<TDocument, TField>> path, |
|||
BsonType bsonType, |
|||
EncryptionAlgorithm? algorithm = null, |
|||
Guid? keyId = null) |
|||
=> Property(path, [bsonType], algorithm, keyId); |
|||
|
|||
/// <summary>
|
|||
/// Adds a property to the schema with encryption settings.
|
|||
/// </summary>
|
|||
/// <typeparam name="TField">The type of the field.</typeparam>
|
|||
/// <param name="path">The field.</param>
|
|||
/// <param name="bsonTypes">The BSON types of the property.</param>
|
|||
/// <param name="algorithm">The encryption algorithm to use.</param>
|
|||
/// <param name="keyId">The key ID to use for encryption.</param>
|
|||
/// <returns>The current <see cref="EncryptedCollectionBuilder{TDocument}"/> instance.</returns>
|
|||
public EncryptedCollectionBuilder<TDocument> Property<TField>( |
|||
Expression<Func<TDocument, TField>> path, |
|||
IEnumerable<BsonType> bsonTypes = null, |
|||
EncryptionAlgorithm? algorithm = null, |
|||
Guid? keyId = null) |
|||
=> Property(new ExpressionFieldDefinition<TDocument, TField>(path), bsonTypes, algorithm, keyId); |
|||
|
|||
/// <summary>
|
|||
/// Adds a property to the schema with encryption settings.
|
|||
/// </summary>
|
|||
/// <param name="path">The field.</param>
|
|||
/// <param name="bsonType">The BSON type of the property.</param>
|
|||
/// <param name="algorithm">The encryption algorithm to use.</param>
|
|||
/// <param name="keyId">The key ID to use for encryption.</param>
|
|||
/// <returns>The current <see cref="EncryptedCollectionBuilder{TDocument}"/> instance.</returns>
|
|||
public EncryptedCollectionBuilder<TDocument> Property( |
|||
FieldDefinition<TDocument> path, |
|||
BsonType bsonType, |
|||
EncryptionAlgorithm? algorithm = null, |
|||
Guid? keyId = null) |
|||
=> Property(path, [bsonType], algorithm, keyId); |
|||
|
|||
/// <summary>
|
|||
/// Adds a property to the schema with encryption settings.
|
|||
/// </summary>
|
|||
/// <param name="path">The field.</param>
|
|||
/// <param name="bsonTypes">The BSON types of the property.</param>
|
|||
/// <param name="algorithm">The encryption algorithm to use.</param>
|
|||
/// <param name="keyId">The key ID to use for encryption.</param>
|
|||
/// <returns>The current <see cref="EncryptedCollectionBuilder{TDocument}"/> instance.</returns>
|
|||
public EncryptedCollectionBuilder<TDocument> Property( |
|||
FieldDefinition<TDocument> path, |
|||
IEnumerable<BsonType> bsonTypes = null, |
|||
EncryptionAlgorithm? algorithm = null, |
|||
Guid? keyId = null) |
|||
{ |
|||
var fieldName = path.Render(_args).FieldName; |
|||
AddToProperties(fieldName, CreateEncryptDocument(bsonTypes, algorithm, keyId)); |
|||
return this; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Adds a nested property to the schema.
|
|||
/// </summary>
|
|||
/// <typeparam name="TField">The type of the nested field.</typeparam>
|
|||
/// <param name="path">The field.</param>
|
|||
/// <param name="configure">An action to configure the nested builder.</param>
|
|||
/// <returns>The current <see cref="EncryptedCollectionBuilder{TDocument}"/> instance.</returns>
|
|||
public EncryptedCollectionBuilder<TDocument> Property<TField>( |
|||
Expression<Func<TDocument, TField>> path, |
|||
Action<EncryptedCollectionBuilder<TField>> configure) |
|||
=> Property(new ExpressionFieldDefinition<TDocument, TField>(path), configure); |
|||
|
|||
|
|||
/// <summary>
|
|||
/// Adds a nested property to the schema.
|
|||
/// </summary>
|
|||
/// <typeparam name="TField">The type of the nested field.</typeparam>
|
|||
/// <param name="path">The field.</param>
|
|||
/// <param name="configure">An action to configure the nested builder.</param>
|
|||
/// <returns>The current <see cref="EncryptedCollectionBuilder{TDocument}"/> instance.</returns>
|
|||
public EncryptedCollectionBuilder<TDocument> Property<TField>( |
|||
FieldDefinition<TDocument> path, |
|||
Action<EncryptedCollectionBuilder<TField>> configure) |
|||
{ |
|||
var nestedBuilder = new EncryptedCollectionBuilder<TField>(); |
|||
configure(nestedBuilder); |
|||
|
|||
var fieldName = path.Render(_args).FieldName; |
|||
AddToProperties(fieldName, nestedBuilder.Build()); |
|||
return this; |
|||
} |
|||
|
|||
internal BsonDocument Build() => _schema; |
|||
|
|||
private static BsonDocument CreateEncryptDocument( |
|||
IEnumerable<BsonType> bsonTypes = null, |
|||
EncryptionAlgorithm? algorithm = null, |
|||
Guid? keyId = null) |
|||
{ |
|||
BsonValue bsonTypeVal = null; |
|||
|
|||
if (bsonTypes != null) |
|||
{ |
|||
var convertedBsonTypes = bsonTypes.Select(type => type.ToServerString()).ToList(); |
|||
|
|||
if (convertedBsonTypes.Count == 0) |
|||
{ |
|||
throw new ArgumentException("At least one BSON type must be specified.", nameof(bsonTypes)); |
|||
} |
|||
|
|||
bsonTypeVal = convertedBsonTypes.Count == 1 |
|||
? convertedBsonTypes[0] |
|||
: new BsonArray(convertedBsonTypes); |
|||
} |
|||
|
|||
return new BsonDocument |
|||
{ |
|||
{ "encrypt", new BsonDocument |
|||
{ |
|||
{ "bsonType", () => bsonTypeVal, bsonTypeVal is not null }, |
|||
{ "algorithm", () => MapCsfleEncryptionAlgorithmToString(algorithm!.Value), algorithm is not null }, |
|||
{ |
|||
"keyId", |
|||
() => new BsonArray(new[] { new BsonBinaryData(keyId!.Value, GuidRepresentation.Standard) }), |
|||
keyId is not null |
|||
}, |
|||
} |
|||
} |
|||
}; |
|||
} |
|||
|
|||
private void AddToPatternProperties(string field, BsonDocument document) |
|||
{ |
|||
if (!_schema.TryGetValue("patternProperties", out var value)) |
|||
{ |
|||
value = new BsonDocument(); |
|||
_schema["patternProperties"] = value; |
|||
} |
|||
var patternProperties = value.AsBsonDocument; |
|||
patternProperties[field] = document; |
|||
} |
|||
|
|||
private void AddToProperties(string field, BsonDocument document) |
|||
{ |
|||
if (!_schema.TryGetValue("properties", out var value)) |
|||
{ |
|||
value = new BsonDocument(); |
|||
_schema["properties"] = value; |
|||
} |
|||
var properties = value.AsBsonDocument; |
|||
properties[field] = document; |
|||
} |
|||
|
|||
private static string MapCsfleEncryptionAlgorithmToString(EncryptionAlgorithm algorithm) |
|||
{ |
|||
return algorithm switch |
|||
{ |
|||
EncryptionAlgorithm.AEAD_AES_256_CBC_HMAC_SHA_512_Random => "AEAD_AES_256_CBC_HMAC_SHA_512-Random", |
|||
EncryptionAlgorithm.AEAD_AES_256_CBC_HMAC_SHA_512_Deterministic => "AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic", |
|||
_ => throw new ArgumentException($"Unexpected algorithm type: {algorithm}.", nameof(algorithm)) |
|||
}; |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,710 @@ |
|||
/* Copyright 2010-present MongoDB Inc. |
|||
* |
|||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|||
* you may not use this file except in compliance with the License. |
|||
* You may obtain a copy of the License at |
|||
* |
|||
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
* |
|||
* Unless required by applicable law or agreed to in writing, software |
|||
* distributed under the License is distributed on an "AS IS" BASIS, |
|||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|||
* See the License for the specific language governing permissions and |
|||
* limitations under the License. |
|||
*/ |
|||
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using FluentAssertions; |
|||
using MongoDB.Bson; |
|||
using MongoDB.Bson.Serialization.Attributes; |
|||
using MongoDB.Driver.Encryption; |
|||
using Xunit; |
|||
|
|||
namespace MongoDB.Driver.Tests.Encryption |
|||
{ |
|||
public class CsfleSchemaBuilderTests |
|||
{ |
|||
private readonly CollectionNamespace _collectionNamespace = CollectionNamespace.FromFullName("medicalRecords.patients"); |
|||
private const string _keyIdString = "6f4af470-00d1-401f-ac39-f45902a0c0c8"; |
|||
private static Guid _keyId = Guid.Parse(_keyIdString); |
|||
|
|||
[Fact] |
|||
public void CsfleSchemaBuilder_works_as_expected() |
|||
{ |
|||
var builder = CsfleSchemaBuilder.Create(schemaBuilder => |
|||
{ |
|||
schemaBuilder.Encrypt<Patient>(_collectionNamespace, builder => |
|||
{ |
|||
builder |
|||
.EncryptMetadata(keyId: _keyId) |
|||
.Property(p => p.MedicalRecords, BsonType.Array, |
|||
EncryptionAlgorithm.AEAD_AES_256_CBC_HMAC_SHA_512_Random) |
|||
.Property("bloodType", BsonType.String, |
|||
algorithm: EncryptionAlgorithm.AEAD_AES_256_CBC_HMAC_SHA_512_Random) |
|||
.Property(p => p.Ssn, BsonType.Int32, |
|||
EncryptionAlgorithm.AEAD_AES_256_CBC_HMAC_SHA_512_Deterministic) |
|||
.Property(p => p.Insurance, innerBuilder => |
|||
{ |
|||
innerBuilder |
|||
.Property(i => i.PolicyNumber, BsonType.Int32, |
|||
EncryptionAlgorithm.AEAD_AES_256_CBC_HMAC_SHA_512_Deterministic); |
|||
}) |
|||
.PatternProperty("_PIIString$", BsonType.String, EncryptionAlgorithm.AEAD_AES_256_CBC_HMAC_SHA_512_Deterministic) |
|||
.PatternProperty("_PIIArray$", BsonType.Array, EncryptionAlgorithm.AEAD_AES_256_CBC_HMAC_SHA_512_Random) |
|||
.PatternProperty(p => p.Insurance, innerBuilder => |
|||
{ |
|||
innerBuilder |
|||
.PatternProperty("_PIIString$", BsonType.String, |
|||
EncryptionAlgorithm.AEAD_AES_256_CBC_HMAC_SHA_512_Deterministic) |
|||
.PatternProperty("_PIINumber$", BsonType.Int32, |
|||
algorithm: EncryptionAlgorithm.AEAD_AES_256_CBC_HMAC_SHA_512_Deterministic); |
|||
}); |
|||
|
|||
} ); |
|||
}); |
|||
|
|||
var expected = new Dictionary<string, string> |
|||
{ |
|||
[_collectionNamespace.FullName] = """
|
|||
{ |
|||
"bsonType": "object", |
|||
"encryptMetadata": { |
|||
"keyId": [{ "$binary" : { "base64" : "b0r0cADRQB+sOfRZAqDAyA==", "subType" : "04" } }] |
|||
}, |
|||
"properties": { |
|||
"insurance": { |
|||
"bsonType": "object", |
|||
"properties": { |
|||
"policyNumber": { |
|||
"encrypt": { |
|||
"bsonType": "int", |
|||
"algorithm": "AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic" |
|||
} |
|||
} |
|||
} |
|||
}, |
|||
"medicalRecords": { |
|||
"encrypt": { |
|||
"bsonType": "array", |
|||
"algorithm": "AEAD_AES_256_CBC_HMAC_SHA_512-Random" |
|||
} |
|||
}, |
|||
"bloodType": { |
|||
"encrypt": { |
|||
"bsonType": "string", |
|||
"algorithm": "AEAD_AES_256_CBC_HMAC_SHA_512-Random" |
|||
} |
|||
}, |
|||
"ssn": { |
|||
"encrypt": { |
|||
"bsonType": "int", |
|||
"algorithm": "AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic" |
|||
} |
|||
} |
|||
}, |
|||
"patternProperties": { |
|||
"_PIIString$": { |
|||
"encrypt": { |
|||
"bsonType": "string", |
|||
"algorithm": "AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic", |
|||
}, |
|||
}, |
|||
"_PIIArray$": { |
|||
"encrypt": { |
|||
"bsonType": "array", |
|||
"algorithm": "AEAD_AES_256_CBC_HMAC_SHA_512-Random", |
|||
}, |
|||
}, |
|||
"insurance": { |
|||
"bsonType": "object", |
|||
"patternProperties": { |
|||
"_PIINumber$": { |
|||
"encrypt": { |
|||
"bsonType": "int", |
|||
"algorithm": "AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic", |
|||
}, |
|||
}, |
|||
"_PIIString$": { |
|||
"encrypt": { |
|||
"bsonType": "string", |
|||
"algorithm": "AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic", |
|||
}, |
|||
}, |
|||
}, |
|||
}, |
|||
}, |
|||
} |
|||
"""
|
|||
}; |
|||
|
|||
AssertOutcomeCsfleSchemaBuilder(builder, expected); |
|||
} |
|||
|
|||
[Fact] |
|||
public void CsfleSchemaBuilder_with_multiple_types_works_as_expected() |
|||
{ |
|||
var testCollectionNamespace = CollectionNamespace.FromFullName("test.class"); |
|||
|
|||
var builder = CsfleSchemaBuilder.Create(schemaBuilder => |
|||
{ |
|||
schemaBuilder.Encrypt<Patient>(_collectionNamespace, builder => |
|||
{ |
|||
builder |
|||
.EncryptMetadata(keyId: _keyId) |
|||
.Property(p => p.MedicalRecords, BsonType.Array, |
|||
EncryptionAlgorithm.AEAD_AES_256_CBC_HMAC_SHA_512_Random); |
|||
}); |
|||
|
|||
schemaBuilder.Encrypt<TestClass>(testCollectionNamespace, builder => |
|||
{ |
|||
builder.Property(t => t.TestString, BsonType.String); |
|||
}); |
|||
}); |
|||
|
|||
var expected = new Dictionary<string, string> |
|||
{ |
|||
[_collectionNamespace.FullName] = """
|
|||
{ |
|||
"bsonType": "object", |
|||
"encryptMetadata": { |
|||
"keyId": [{ "$binary" : { "base64" : "b0r0cADRQB+sOfRZAqDAyA==", "subType" : "04" } }] |
|||
}, |
|||
"properties": { |
|||
"medicalRecords": { |
|||
"encrypt": { |
|||
"bsonType": "array", |
|||
"algorithm": "AEAD_AES_256_CBC_HMAC_SHA_512-Random" |
|||
} |
|||
}, |
|||
}, |
|||
} |
|||
""",
|
|||
[testCollectionNamespace.FullName] = """
|
|||
{ |
|||
"bsonType": "object", |
|||
"properties": { |
|||
"TestString": { |
|||
"encrypt": { |
|||
"bsonType": "string", |
|||
} |
|||
}, |
|||
} |
|||
} |
|||
"""
|
|||
}; |
|||
|
|||
AssertOutcomeCsfleSchemaBuilder(builder, expected); |
|||
} |
|||
|
|||
[Fact] |
|||
public void CsfleSchemaBuilder_with_no_schemas_throws() |
|||
{ |
|||
var builder = CsfleSchemaBuilder.Create(_ => |
|||
{ |
|||
// No schemas added
|
|||
}); |
|||
|
|||
var exception = Record.Exception(() => builder.Build()); |
|||
|
|||
exception.Should().NotBeNull(); |
|||
exception.Should().BeOfType<InvalidOperationException>(); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineData( |
|||
EncryptionAlgorithm.AEAD_AES_256_CBC_HMAC_SHA_512_Random, |
|||
null, |
|||
""" "algorithm": "AEAD_AES_256_CBC_HMAC_SHA_512-Random" """)] |
|||
[InlineData( |
|||
null, |
|||
_keyIdString, |
|||
""" "keyId": [{ "$binary" : { "base64" : "b0r0cADRQB+sOfRZAqDAyA==", "subType" : "04" } }] """)] |
|||
public void EncryptedCollection_Metadata_works_as_expected(EncryptionAlgorithm? algorithm, string keyString, string expectedContent) |
|||
{ |
|||
Guid? keyId = keyString is null ? null : Guid.Parse(keyString); |
|||
var builder = new EncryptedCollectionBuilder<Patient>(); |
|||
|
|||
builder.EncryptMetadata(keyId, algorithm); |
|||
|
|||
var expected = $$"""
|
|||
{ |
|||
"bsonType": "object", |
|||
"encryptMetadata": { |
|||
{{expectedContent}} |
|||
} |
|||
} |
|||
""";
|
|||
|
|||
AssertOutcomeCollectionBuilder(builder, expected); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineData(BsonType.Array, |
|||
EncryptionAlgorithm.AEAD_AES_256_CBC_HMAC_SHA_512_Random, |
|||
null, |
|||
""" "bsonType": "array", "algorithm": "AEAD_AES_256_CBC_HMAC_SHA_512-Random" """)] |
|||
[InlineData(BsonType.Array, |
|||
null, |
|||
_keyIdString, |
|||
""" "bsonType": "array", "keyId": [{ "$binary" : { "base64" : "b0r0cADRQB+sOfRZAqDAyA==", "subType" : "04" } }] """)] |
|||
[InlineData(BsonType.Array, |
|||
EncryptionAlgorithm.AEAD_AES_256_CBC_HMAC_SHA_512_Random, |
|||
_keyIdString, |
|||
""" "bsonType": "array", "algorithm": "AEAD_AES_256_CBC_HMAC_SHA_512-Random", "keyId": [{ "$binary" : { "base64" : "b0r0cADRQB+sOfRZAqDAyA==", "subType" : "04" } }] """)] |
|||
public void EncryptedCollection_PatternProperty_works_as_expected(BsonType bsonType, EncryptionAlgorithm? algorithm, string keyString, string expectedContent) |
|||
{ |
|||
Guid? keyId = keyString is null ? null : Guid.Parse(keyString); |
|||
var builder = new EncryptedCollectionBuilder<Patient>(); |
|||
|
|||
builder.PatternProperty("randomRegex*", bsonType, algorithm, keyId); |
|||
|
|||
var expected = $$"""
|
|||
{ |
|||
"bsonType": "object", |
|||
"patternProperties": { |
|||
"randomRegex*": { |
|||
"encrypt": { |
|||
{{expectedContent}} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
""";
|
|||
|
|||
AssertOutcomeCollectionBuilder(builder, expected); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineData(null, |
|||
null, |
|||
null, |
|||
"")] |
|||
[InlineData(new[] {BsonType.Array, BsonType.String}, |
|||
EncryptionAlgorithm.AEAD_AES_256_CBC_HMAC_SHA_512_Random, |
|||
null, |
|||
""" "bsonType": ["array", "string"], "algorithm": "AEAD_AES_256_CBC_HMAC_SHA_512-Random" """)] |
|||
[InlineData(new[] {BsonType.Array, BsonType.String}, |
|||
null, |
|||
_keyIdString, |
|||
""" "bsonType": ["array", "string"], "keyId": [{ "$binary" : { "base64" : "b0r0cADRQB+sOfRZAqDAyA==", "subType" : "04" } }] """)] |
|||
[InlineData(new[] {BsonType.Array, BsonType.String}, |
|||
EncryptionAlgorithm.AEAD_AES_256_CBC_HMAC_SHA_512_Random, |
|||
_keyIdString, |
|||
""" "bsonType": ["array", "string"], "algorithm": "AEAD_AES_256_CBC_HMAC_SHA_512-Random", "keyId": [{ "$binary" : { "base64" : "b0r0cADRQB+sOfRZAqDAyA==", "subType" : "04" } }] """)] |
|||
public void EncryptedCollection_PatternProperty_with_multiple_bson_types_works_as_expected(IEnumerable<BsonType> bsonTypes, EncryptionAlgorithm? algorithm, string keyString, string expectedContent) |
|||
{ |
|||
Guid? keyId = keyString is null ? null : Guid.Parse(keyString); |
|||
var builder = new EncryptedCollectionBuilder<Patient>(); |
|||
|
|||
builder.PatternProperty("randomRegex*", bsonTypes, algorithm, keyId); |
|||
|
|||
var expected = $$"""
|
|||
{ |
|||
"bsonType": "object", |
|||
"patternProperties": { |
|||
"randomRegex*": { |
|||
"encrypt": { |
|||
{{expectedContent}} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
""";
|
|||
|
|||
AssertOutcomeCollectionBuilder(builder, expected); |
|||
} |
|||
|
|||
[Fact] |
|||
public void EncryptedCollection_PatternProperty_nested_works_as_expected() |
|||
{ |
|||
Guid? keyId = Guid.Parse(_keyIdString); |
|||
var builder = new EncryptedCollectionBuilder<Patient>(); |
|||
|
|||
builder.PatternProperty(p => p.Insurance, innerBuilder => |
|||
{ |
|||
innerBuilder |
|||
.EncryptMetadata(keyId) |
|||
.Property("policyNumber", BsonType.Int32, |
|||
EncryptionAlgorithm.AEAD_AES_256_CBC_HMAC_SHA_512_Deterministic) |
|||
.PatternProperty("randomRegex*", BsonType.String, |
|||
EncryptionAlgorithm.AEAD_AES_256_CBC_HMAC_SHA_512_Random); |
|||
}); |
|||
|
|||
var expected = """
|
|||
{ |
|||
"bsonType": "object", |
|||
"patternProperties": { |
|||
"insurance": { |
|||
"bsonType": "object", |
|||
"encryptMetadata": { |
|||
"keyId": [{ "$binary" : { "base64" : "b0r0cADRQB+sOfRZAqDAyA==", "subType" : "04" } }] |
|||
}, |
|||
"properties": { |
|||
"policyNumber": { |
|||
"encrypt": { |
|||
"bsonType": "int", |
|||
"algorithm": "AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic" |
|||
} |
|||
} |
|||
}, |
|||
"patternProperties": { |
|||
"randomRegex*": { |
|||
"encrypt": { |
|||
"bsonType": "string", |
|||
"algorithm": "AEAD_AES_256_CBC_HMAC_SHA_512-Random" |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
""";
|
|||
|
|||
AssertOutcomeCollectionBuilder(builder, expected); |
|||
} |
|||
|
|||
[Fact] |
|||
public void EncryptedCollection_PatternProperty_nested_with_string_works_as_expected() |
|||
{ |
|||
Guid? keyId = Guid.Parse(_keyIdString); |
|||
var builder = new EncryptedCollectionBuilder<Patient>(); |
|||
|
|||
builder.PatternProperty<Insurance>("insurance", innerBuilder => |
|||
{ |
|||
innerBuilder |
|||
.EncryptMetadata(keyId) |
|||
.Property("policyNumber", BsonType.Int32, |
|||
EncryptionAlgorithm.AEAD_AES_256_CBC_HMAC_SHA_512_Deterministic) |
|||
.PatternProperty("randomRegex*", BsonType.String, |
|||
EncryptionAlgorithm.AEAD_AES_256_CBC_HMAC_SHA_512_Random); |
|||
}); |
|||
|
|||
var expected = """
|
|||
{ |
|||
"bsonType": "object", |
|||
"patternProperties": { |
|||
"insurance": { |
|||
"bsonType": "object", |
|||
"encryptMetadata": { |
|||
"keyId": [{ "$binary" : { "base64" : "b0r0cADRQB+sOfRZAqDAyA==", "subType" : "04" } }] |
|||
}, |
|||
"properties": { |
|||
"policyNumber": { |
|||
"encrypt": { |
|||
"bsonType": "int", |
|||
"algorithm": "AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic" |
|||
} |
|||
} |
|||
}, |
|||
"patternProperties": { |
|||
"randomRegex*": { |
|||
"encrypt": { |
|||
"bsonType": "string", |
|||
"algorithm": "AEAD_AES_256_CBC_HMAC_SHA_512-Random" |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
""";
|
|||
|
|||
AssertOutcomeCollectionBuilder(builder, expected); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineData(BsonType.Array, |
|||
EncryptionAlgorithm.AEAD_AES_256_CBC_HMAC_SHA_512_Random, |
|||
null, |
|||
""" "bsonType": "array", "algorithm": "AEAD_AES_256_CBC_HMAC_SHA_512-Random" """)] |
|||
[InlineData(BsonType.Array, |
|||
null, |
|||
_keyIdString, |
|||
""" "bsonType": "array", "keyId": [{ "$binary" : { "base64" : "b0r0cADRQB+sOfRZAqDAyA==", "subType" : "04" } }] """)] |
|||
[InlineData(BsonType.Array, |
|||
EncryptionAlgorithm.AEAD_AES_256_CBC_HMAC_SHA_512_Random, |
|||
_keyIdString, |
|||
""" "bsonType": "array", "algorithm": "AEAD_AES_256_CBC_HMAC_SHA_512-Random", "keyId": [{ "$binary" : { "base64" : "b0r0cADRQB+sOfRZAqDAyA==", "subType" : "04" } }] """)] |
|||
public void EncryptedCollection_Property_with_expression_works_as_expected(BsonType bsonType, EncryptionAlgorithm? algorithm, string keyString, string expectedContent) |
|||
{ |
|||
Guid? keyId = keyString is null ? null : Guid.Parse(keyString); |
|||
var builder = new EncryptedCollectionBuilder<Patient>(); |
|||
|
|||
builder.Property(p => p.MedicalRecords, bsonType, algorithm, keyId); |
|||
|
|||
var expected = $$"""
|
|||
{ |
|||
"bsonType": "object", |
|||
"properties": { |
|||
"medicalRecords": { |
|||
"encrypt": { |
|||
{{expectedContent}} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
""";
|
|||
|
|||
AssertOutcomeCollectionBuilder(builder, expected); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineData(null, |
|||
null, |
|||
null, |
|||
"")] |
|||
[InlineData(new[] {BsonType.Array, BsonType.String}, |
|||
EncryptionAlgorithm.AEAD_AES_256_CBC_HMAC_SHA_512_Random, |
|||
null, |
|||
""" "bsonType": ["array", "string"], "algorithm": "AEAD_AES_256_CBC_HMAC_SHA_512-Random" """)] |
|||
[InlineData(new[] {BsonType.Array, BsonType.String}, |
|||
null, |
|||
_keyIdString, |
|||
""" "bsonType": ["array", "string"], "keyId": [{ "$binary" : { "base64" : "b0r0cADRQB+sOfRZAqDAyA==", "subType" : "04" } }] """)] |
|||
[InlineData(new[] {BsonType.Array, BsonType.String}, |
|||
EncryptionAlgorithm.AEAD_AES_256_CBC_HMAC_SHA_512_Random, |
|||
_keyIdString, |
|||
""" "bsonType": ["array", "string"], "algorithm": "AEAD_AES_256_CBC_HMAC_SHA_512-Random", "keyId": [{ "$binary" : { "base64" : "b0r0cADRQB+sOfRZAqDAyA==", "subType" : "04" } }] """)] |
|||
public void EncryptedCollection_Property_with_multiple_bson_types_works_as_expected(IEnumerable<BsonType> bsonTypes, EncryptionAlgorithm? algorithm, string keyString, string expectedContent) |
|||
{ |
|||
Guid? keyId = keyString is null ? null : Guid.Parse(keyString); |
|||
var builder = new EncryptedCollectionBuilder<Patient>(); |
|||
|
|||
builder.Property(p => p.MedicalRecords, bsonTypes, algorithm, keyId); |
|||
|
|||
var expected = $$"""
|
|||
{ |
|||
"bsonType": "object", |
|||
"properties": { |
|||
"medicalRecords": { |
|||
"encrypt": { |
|||
{{expectedContent}} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
""";
|
|||
|
|||
AssertOutcomeCollectionBuilder(builder, expected); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineData(BsonType.Array, |
|||
EncryptionAlgorithm.AEAD_AES_256_CBC_HMAC_SHA_512_Random, |
|||
null, |
|||
""" "bsonType": "array", "algorithm": "AEAD_AES_256_CBC_HMAC_SHA_512-Random" """)] |
|||
[InlineData(BsonType.Array, |
|||
null, |
|||
_keyIdString, |
|||
""" "bsonType": "array", "keyId": [{ "$binary" : { "base64" : "b0r0cADRQB+sOfRZAqDAyA==", "subType" : "04" } }] """)] |
|||
[InlineData(BsonType.Array, |
|||
EncryptionAlgorithm.AEAD_AES_256_CBC_HMAC_SHA_512_Random, |
|||
_keyIdString, |
|||
""" "bsonType": "array", "algorithm": "AEAD_AES_256_CBC_HMAC_SHA_512-Random", "keyId": [{ "$binary" : { "base64" : "b0r0cADRQB+sOfRZAqDAyA==", "subType" : "04" } }] """)] |
|||
public void EncryptedCollection_Property_with_string_works_as_expected(BsonType bsonType, EncryptionAlgorithm? algorithm, string keyString, string expectedContent) |
|||
{ |
|||
Guid? keyId = keyString is null ? null : Guid.Parse(keyString); |
|||
var builder = new EncryptedCollectionBuilder<Patient>(); |
|||
|
|||
builder.Property("medicalRecords", bsonType, algorithm, keyId); |
|||
|
|||
var expected = $$"""
|
|||
{ |
|||
"bsonType": "object", |
|||
"properties": { |
|||
"medicalRecords": { |
|||
"encrypt": { |
|||
{{expectedContent}} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
""";
|
|||
|
|||
AssertOutcomeCollectionBuilder(builder, expected); |
|||
} |
|||
|
|||
[Fact] |
|||
public void EncryptedCollection_Property_nested_works_as_expected() |
|||
{ |
|||
Guid? keyId = Guid.Parse(_keyIdString); |
|||
var builder = new EncryptedCollectionBuilder<Patient>(); |
|||
|
|||
builder.Property(p => p.Insurance, innerBuilder => |
|||
{ |
|||
innerBuilder |
|||
.EncryptMetadata(keyId) |
|||
.Property("policyNumber", BsonType.Int32, |
|||
EncryptionAlgorithm.AEAD_AES_256_CBC_HMAC_SHA_512_Deterministic) |
|||
.PatternProperty("randomRegex*", BsonType.String, |
|||
EncryptionAlgorithm.AEAD_AES_256_CBC_HMAC_SHA_512_Random); |
|||
}); |
|||
|
|||
var expected = """
|
|||
{ |
|||
"bsonType": "object", |
|||
"properties": { |
|||
"insurance": { |
|||
"bsonType": "object", |
|||
"encryptMetadata": { |
|||
"keyId": [{ "$binary" : { "base64" : "b0r0cADRQB+sOfRZAqDAyA==", "subType" : "04" } }] |
|||
}, |
|||
"properties": { |
|||
"policyNumber": { |
|||
"encrypt": { |
|||
"bsonType": "int", |
|||
"algorithm": "AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic" |
|||
} |
|||
} |
|||
}, |
|||
"patternProperties": { |
|||
"randomRegex*": { |
|||
"encrypt": { |
|||
"bsonType": "string", |
|||
"algorithm": "AEAD_AES_256_CBC_HMAC_SHA_512-Random" |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
""";
|
|||
|
|||
AssertOutcomeCollectionBuilder(builder, expected); |
|||
} |
|||
|
|||
[Fact] |
|||
public void EncryptedCollection_Property_nested_with_string_works_as_expected() |
|||
{ |
|||
Guid? keyId = Guid.Parse(_keyIdString); |
|||
var builder = new EncryptedCollectionBuilder<Patient>(); |
|||
|
|||
builder.Property<Insurance>("insurance", innerBuilder => |
|||
{ |
|||
innerBuilder |
|||
.EncryptMetadata(keyId) |
|||
.Property("policyNumber", BsonType.Int32, |
|||
EncryptionAlgorithm.AEAD_AES_256_CBC_HMAC_SHA_512_Deterministic) |
|||
.PatternProperty("randomRegex*", BsonType.String, |
|||
EncryptionAlgorithm.AEAD_AES_256_CBC_HMAC_SHA_512_Random); |
|||
}); |
|||
|
|||
var expected = """
|
|||
{ |
|||
"bsonType": "object", |
|||
"properties": { |
|||
"insurance": { |
|||
"bsonType": "object", |
|||
"encryptMetadata": { |
|||
"keyId": [{ "$binary" : { "base64" : "b0r0cADRQB+sOfRZAqDAyA==", "subType" : "04" } }] |
|||
}, |
|||
"properties": { |
|||
"policyNumber": { |
|||
"encrypt": { |
|||
"bsonType": "int", |
|||
"algorithm": "AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic" |
|||
} |
|||
} |
|||
}, |
|||
"patternProperties": { |
|||
"randomRegex*": { |
|||
"encrypt": { |
|||
"bsonType": "string", |
|||
"algorithm": "AEAD_AES_256_CBC_HMAC_SHA_512-Random" |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
""";
|
|||
|
|||
AssertOutcomeCollectionBuilder(builder, expected); |
|||
} |
|||
|
|||
[Fact] |
|||
public void EncryptedCollection_Property_with_empty_bson_types_throws() |
|||
{ |
|||
var builder = new EncryptedCollectionBuilder<Patient>(); |
|||
|
|||
var recordedException = Record.Exception(() => builder.Property("test", [])); |
|||
recordedException.Should().NotBeNull(); |
|||
recordedException.Should().BeOfType<ArgumentException>(); |
|||
} |
|||
|
|||
[Fact] |
|||
public void EncryptedCollection_Metadata_with_empty_algorithm_and_key_throws() |
|||
{ |
|||
var builder = new EncryptedCollectionBuilder<Patient>(); |
|||
|
|||
var recordedException = Record.Exception(() => builder.EncryptMetadata(null, null)); |
|||
recordedException.Should().NotBeNull(); |
|||
recordedException.Should().BeOfType<ArgumentException>(); |
|||
} |
|||
|
|||
private void AssertOutcomeCsfleSchemaBuilder(CsfleSchemaBuilder builder, Dictionary<string, string> expectedSchema) |
|||
{ |
|||
var builtSchema = builder.Build(); |
|||
expectedSchema.Should().HaveCount(builtSchema.Count); |
|||
foreach (var collectionNamespace in expectedSchema.Keys) |
|||
{ |
|||
var parsed = BsonDocument.Parse(expectedSchema[collectionNamespace]); |
|||
builtSchema[collectionNamespace].Should().BeEquivalentTo(parsed); |
|||
} |
|||
} |
|||
|
|||
private void AssertOutcomeCollectionBuilder<T>(EncryptedCollectionBuilder<T> builder, string expected) |
|||
{ |
|||
var builtSchema = builder.Build(); |
|||
var expectedSchema = BsonDocument.Parse(expected); |
|||
builtSchema.Should().BeEquivalentTo(expectedSchema); |
|||
} |
|||
|
|||
internal class TestClass |
|||
{ |
|||
public ObjectId Id { get; set; } |
|||
|
|||
public string TestString { get; set; } |
|||
} |
|||
|
|||
internal class Patient |
|||
{ |
|||
[BsonId] |
|||
public ObjectId Id { get; set; } |
|||
|
|||
[BsonElement("name")] |
|||
public string Name { get; set; } |
|||
|
|||
[BsonElement("ssn")] |
|||
public int Ssn { get; set; } |
|||
|
|||
[BsonElement("bloodType")] |
|||
public string BloodType { get; set; } |
|||
|
|||
[BsonElement("medicalRecords")] |
|||
public List<MedicalRecord> MedicalRecords { get; set; } |
|||
|
|||
[BsonElement("insurance")] |
|||
public Insurance Insurance { get; set; } |
|||
} |
|||
|
|||
internal class MedicalRecord |
|||
{ |
|||
[BsonElement("weight")] |
|||
public int Weight { get; set; } |
|||
|
|||
[BsonElement("bloodPressure")] |
|||
public string BloodPressure { get; set; } |
|||
} |
|||
|
|||
internal class Insurance |
|||
{ |
|||
[BsonElement("provider")] |
|||
public string Provider { get; set; } |
|||
|
|||
[BsonElement("policyNumber")] |
|||
public int PolicyNumber { get; set; } |
|||
} |
|||
} |
|||
} |
@ -1 +0,0 @@ |
|||
|
@ -1 +0,0 @@ |
|||
|
Write
Preview
Loading…
Cancel
Save
Reference in new issue