diff --git a/src/libmongoc/src/mongoc/mongoc-util-private.h b/src/libmongoc/src/mongoc/mongoc-util-private.h index 062fcb431bb..84e007396ca 100644 --- a/src/libmongoc/src/mongoc/mongoc-util-private.h +++ b/src/libmongoc/src/mongoc/mongoc-util-private.h @@ -100,6 +100,9 @@ mongoc_ends_with (const char *str, const char *suffix); void mongoc_lowercase (const char *src, char *buf /* OUT */); +void +mongoc_lowercase_inplace (char *src); + bool mongoc_parse_port (uint16_t *port, const char *str); diff --git a/src/libmongoc/src/mongoc/mongoc-util.c b/src/libmongoc/src/mongoc/mongoc-util.c index 969b6cdabf3..60c8b35d4ba 100644 --- a/src/libmongoc/src/mongoc/mongoc-util.c +++ b/src/libmongoc/src/mongoc/mongoc-util.c @@ -526,6 +526,18 @@ mongoc_lowercase (const char *src, char *buf /* OUT */) } } +void +mongoc_lowercase_inplace (char *src) +{ + for (; *src; ++src) { + /* UTF8 non-ascii characters have a 1 at the leftmost bit. If this is the + * case, just copy */ + if ((*src & (0x1 << 7)) == 0) { + *src = (char) tolower (*src); + } + } +} + bool mongoc_parse_port (uint16_t *port, const char *str) { diff --git a/src/libmongoc/tests/json/client_side_encryption/legacy/fle2v2-BypassQueryAnalysis.json b/src/libmongoc/tests/json/client_side_encryption/legacy/fle2v2-BypassQueryAnalysis.json deleted file mode 100644 index dcc3983ae0c..00000000000 --- a/src/libmongoc/tests/json/client_side_encryption/legacy/fle2v2-BypassQueryAnalysis.json +++ /dev/null @@ -1,262 +0,0 @@ -{ - "runOn": [ - { - "minServerVersion": "7.0.0", - "serverless": "forbid", - "topology": [ - "replicaset", - "sharded", - "load-balanced" - ] - } - ], - "database_name": "default", - "collection_name": "default", - "data": [], - "encrypted_fields": { - "fields": [ - { - "keyId": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - }, - "path": "encryptedIndexed", - "bsonType": "string", - "queries": { - "queryType": "equality", - "contention": { - "$numberLong": "0" - } - } - }, - { - "keyId": { - "$binary": { - "base64": "q83vqxI0mHYSNBI0VniQEg==", - "subType": "04" - } - }, - "path": "encryptedUnindexed", - "bsonType": "string" - } - ] - }, - "key_vault_data": [ - { - "_id": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - }, - "keyMaterial": { - "$binary": { - "base64": "sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==", - "subType": "00" - } - }, - "creationDate": { - "$date": { - "$numberLong": "1648914851981" - } - }, - "updateDate": { - "$date": { - "$numberLong": "1648914851981" - } - }, - "status": { - "$numberInt": "0" - }, - "masterKey": { - "provider": "local" - } - } - ], - "tests": [ - { - "description": "BypassQueryAnalysis decrypts", - "clientOptions": { - "autoEncryptOpts": { - "kmsProviders": { - "local": { - "key": { - "$binary": { - "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", - "subType": "00" - } - } - } - }, - "bypassQueryAnalysis": true - } - }, - "operations": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 1, - "encryptedIndexed": { - "$binary": { - "base64": "C18BAAAFZAAgAAAAANnt+eLTkv4GdDPl8IAfJOvTzArOgFJQ2S/DcLza4W0DBXMAIAAAAAD2u+omZme3P2gBPehMQyQHQ153tPN1+z7bksYA9jKTpAVwADAAAAAAUnCOQqIvmR65YKyYnsiVfVrg9hwUVO3RhhKExo3RWOzgaS0QdsBL5xKFS0JhZSoWBXUAEAAAAAQSNFZ4EjSYdhI0EjRWeJASEHQAAgAAAAV2AFAAAAAAEjRWeBI0mHYSNBI0VniQEpQbp/ZJpWBKeDtKLiXb0P2E9wvc0g3f373jnYQYlJquOrlPOoEy3ngsHPJuSUijvWDsrQzqYa349K7G/66qaXEFZQAgAAAAAOuac/eRLYakKX6B0vZ1r3QodOQFfjqJD+xlGiPu4/PsBWwAIAAAAACkm0o9bj6j0HuADKc0svbqO2UHj6GrlNdF6yKNxh63xRJrAAAAAAAAAAAAAA==", - "subType": "06" - } - } - } - } - }, - { - "name": "find", - "arguments": { - "filter": { - "_id": 1 - } - }, - "result": [ - { - "_id": 1, - "encryptedIndexed": "123" - } - ] - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "listCollections": 1, - "filter": { - "name": "default" - } - }, - "command_name": "listCollections" - } - }, - { - "command_started_event": { - "command": { - "insert": "default", - "documents": [ - { - "_id": 1, - "encryptedIndexed": { - "$binary": { - "base64": "C18BAAAFZAAgAAAAANnt+eLTkv4GdDPl8IAfJOvTzArOgFJQ2S/DcLza4W0DBXMAIAAAAAD2u+omZme3P2gBPehMQyQHQ153tPN1+z7bksYA9jKTpAVwADAAAAAAUnCOQqIvmR65YKyYnsiVfVrg9hwUVO3RhhKExo3RWOzgaS0QdsBL5xKFS0JhZSoWBXUAEAAAAAQSNFZ4EjSYdhI0EjRWeJASEHQAAgAAAAV2AFAAAAAAEjRWeBI0mHYSNBI0VniQEpQbp/ZJpWBKeDtKLiXb0P2E9wvc0g3f373jnYQYlJquOrlPOoEy3ngsHPJuSUijvWDsrQzqYa349K7G/66qaXEFZQAgAAAAAOuac/eRLYakKX6B0vZ1r3QodOQFfjqJD+xlGiPu4/PsBWwAIAAAAACkm0o9bj6j0HuADKc0svbqO2UHj6GrlNdF6yKNxh63xRJrAAAAAAAAAAAAAA==", - "subType": "06" - } - } - } - ], - "ordered": true, - "encryptionInformation": { - "type": 1, - "schema": { - "default.default": { - "escCollection": "enxcol_.default.esc", - "ecocCollection": "enxcol_.default.ecoc", - "fields": [ - { - "keyId": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - }, - "path": "encryptedIndexed", - "bsonType": "string", - "queries": { - "queryType": "equality", - "contention": { - "$numberLong": "0" - } - } - }, - { - "keyId": { - "$binary": { - "base64": "q83vqxI0mHYSNBI0VniQEg==", - "subType": "04" - } - }, - "path": "encryptedUnindexed", - "bsonType": "string" - } - ] - } - } - } - }, - "command_name": "insert" - } - }, - { - "command_started_event": { - "command": { - "find": "default", - "filter": { - "_id": 1 - } - }, - "command_name": "find" - } - }, - { - "command_started_event": { - "command": { - "find": "datakeys", - "filter": { - "$or": [ - { - "_id": { - "$in": [ - { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - } - ] - } - }, - { - "keyAltNames": { - "$in": [] - } - } - ] - }, - "$db": "keyvault", - "readConcern": { - "level": "majority" - } - }, - "command_name": "find" - } - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 1, - "encryptedIndexed": { - "$$type": "binData" - }, - "__safeContent__": [ - { - "$binary": { - "base64": "31eCYlbQoVboc5zwC8IoyJVSkag9PxREka8dkmbXJeY=", - "subType": "00" - } - } - ] - } - ] - } - } - } - ] -} diff --git a/src/libmongoc/tests/json/client_side_encryption/legacy/fle2v2-EncryptedFields-vs-EncryptedFieldsMap.json b/src/libmongoc/tests/json/client_side_encryption/legacy/fle2v2-EncryptedFields-vs-EncryptedFieldsMap.json deleted file mode 100644 index b579979e945..00000000000 --- a/src/libmongoc/tests/json/client_side_encryption/legacy/fle2v2-EncryptedFields-vs-EncryptedFieldsMap.json +++ /dev/null @@ -1,213 +0,0 @@ -{ - "runOn": [ - { - "minServerVersion": "7.0.0", - "serverless": "forbid", - "topology": [ - "replicaset", - "sharded", - "load-balanced" - ] - } - ], - "database_name": "default", - "collection_name": "default", - "data": [], - "encrypted_fields": { - "fields": [ - { - "keyId": { - "$binary": { - "base64": "EjRWeBI0mHYSNBI0VniQEg==", - "subType": "04" - } - }, - "path": "encryptedIndexed", - "bsonType": "string", - "queries": { - "queryType": "equality", - "contention": { - "$numberLong": "0" - } - } - }, - { - "keyId": { - "$binary": { - "base64": "q83vqxI0mHYSNBI0VniQEg==", - "subType": "04" - } - }, - "path": "encryptedUnindexed", - "bsonType": "string" - } - ] - }, - "key_vault_data": [ - { - "_id": { - "$binary": { - "base64": "q83vqxI0mHYSNBI0VniQEg==", - "subType": "04" - } - }, - "keyMaterial": { - "$binary": { - "base64": "HBk9BWihXExNDvTp1lUxOuxuZK2Pe2ZdVdlsxPEBkiO1bS4mG5NNDsQ7zVxJAH8BtdOYp72Ku4Y3nwc0BUpIKsvAKX4eYXtlhv5zUQxWdeNFhg9qK7qb8nqhnnLeT0f25jFSqzWJoT379hfwDeu0bebJHr35QrJ8myZdPMTEDYF08QYQ48ShRBli0S+QzBHHAQiM2iJNr4svg2WR8JSeWQ==", - "subType": "00" - } - }, - "creationDate": { - "$date": { - "$numberLong": "1648914851981" - } - }, - "updateDate": { - "$date": { - "$numberLong": "1648914851981" - } - }, - "status": { - "$numberInt": "0" - }, - "masterKey": { - "provider": "local" - } - } - ], - "tests": [ - { - "description": "encryptedFieldsMap is preferred over remote encryptedFields", - "clientOptions": { - "autoEncryptOpts": { - "kmsProviders": { - "local": { - "key": { - "$binary": { - "base64": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk", - "subType": "00" - } - } - } - }, - "encryptedFieldsMap": { - "default.default": { - "fields": [] - } - } - } - }, - "operations": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 1, - "encryptedUnindexed": { - "$binary": { - "base64": "BqvN76sSNJh2EjQSNFZ4kBICTQaVZPWgXp41I7mPV1rLFTtw1tXzjcdSEyxpKKqujlko5TeizkB9hHQ009dVY1+fgIiDcefh+eQrm3CkhQ==", - "subType": "06" - } - } - } - } - }, - { - "name": "find", - "arguments": { - "filter": { - "_id": 1 - } - }, - "result": [ - { - "_id": 1, - "encryptedUnindexed": "value123" - } - ] - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "insert": "default", - "documents": [ - { - "_id": 1, - "encryptedUnindexed": { - "$binary": { - "base64": "BqvN76sSNJh2EjQSNFZ4kBICTQaVZPWgXp41I7mPV1rLFTtw1tXzjcdSEyxpKKqujlko5TeizkB9hHQ009dVY1+fgIiDcefh+eQrm3CkhQ==", - "subType": "06" - } - } - } - ], - "ordered": true - }, - "command_name": "insert" - } - }, - { - "command_started_event": { - "command": { - "find": "default", - "filter": { - "_id": 1 - } - }, - "command_name": "find" - } - }, - { - "command_started_event": { - "command": { - "find": "datakeys", - "filter": { - "$or": [ - { - "_id": { - "$in": [ - { - "$binary": { - "base64": "q83vqxI0mHYSNBI0VniQEg==", - "subType": "04" - } - } - ] - } - }, - { - "keyAltNames": { - "$in": [] - } - } - ] - }, - "$db": "keyvault", - "readConcern": { - "level": "majority" - } - }, - "command_name": "find" - } - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 1, - "encryptedUnindexed": { - "$binary": { - "base64": "BqvN76sSNJh2EjQSNFZ4kBICTQaVZPWgXp41I7mPV1rLFTtw1tXzjcdSEyxpKKqujlko5TeizkB9hHQ009dVY1+fgIiDcefh+eQrm3CkhQ==", - "subType": "06" - } - } - } - ] - } - } - } - ] -} diff --git a/src/libmongoc/tests/json/client_side_encryption/legacy/localSchema.json b/src/libmongoc/tests/json/client_side_encryption/legacy/localSchema.json deleted file mode 100644 index 4698520f6fa..00000000000 --- a/src/libmongoc/tests/json/client_side_encryption/legacy/localSchema.json +++ /dev/null @@ -1,258 +0,0 @@ -{ - "runOn": [ - { - "minServerVersion": "4.1.10" - } - ], - "database_name": "default", - "collection_name": "default", - "data": [], - "json_schema": {}, - "key_vault_data": [ - { - "status": 1, - "_id": { - "$binary": { - "base64": "AAAAAAAAAAAAAAAAAAAAAA==", - "subType": "04" - } - }, - "masterKey": { - "provider": "aws", - "key": "arn:aws:kms:us-east-1:579766882180:key/89fcc2c4-08b0-4bd9-9f25-e30687b580d0", - "region": "us-east-1" - }, - "updateDate": { - "$date": { - "$numberLong": "1552949630483" - } - }, - "keyMaterial": { - "$binary": { - "base64": "AQICAHhQNmWG2CzOm1dq3kWLM+iDUZhEqnhJwH9wZVpuZ94A8gEqnsxXlR51T5EbEVezUqqKAAAAwjCBvwYJKoZIhvcNAQcGoIGxMIGuAgEAMIGoBgkqhkiG9w0BBwEwHgYJYIZIAWUDBAEuMBEEDHa4jo6yp0Z18KgbUgIBEIB74sKxWtV8/YHje5lv5THTl0HIbhSwM6EqRlmBiFFatmEWaeMk4tO4xBX65eq670I5TWPSLMzpp8ncGHMmvHqRajNBnmFtbYxN3E3/WjxmdbOOe+OXpnGJPcGsftc7cB2shRfA4lICPnE26+oVNXT6p0Lo20nY5XC7jyCO", - "subType": "00" - } - }, - "creationDate": { - "$date": { - "$numberLong": "1552949630483" - } - }, - "keyAltNames": [ - "altname", - "another_altname" - ] - } - ], - "tests": [ - { - "description": "A local schema should override", - "clientOptions": { - "autoEncryptOpts": { - "schemaMap": { - "default.default": { - "properties": { - "encrypted_w_altname": { - "encrypt": { - "keyId": "/altname", - "bsonType": "string", - "algorithm": "AEAD_AES_256_CBC_HMAC_SHA_512-Random" - } - }, - "encrypted_string": { - "encrypt": { - "keyId": [ - { - "$binary": { - "base64": "AAAAAAAAAAAAAAAAAAAAAA==", - "subType": "04" - } - } - ], - "bsonType": "string", - "algorithm": "AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic" - } - }, - "random": { - "encrypt": { - "keyId": [ - { - "$binary": { - "base64": "AAAAAAAAAAAAAAAAAAAAAA==", - "subType": "04" - } - } - ], - "bsonType": "string", - "algorithm": "AEAD_AES_256_CBC_HMAC_SHA_512-Random" - } - }, - "encrypted_string_equivalent": { - "encrypt": { - "keyId": [ - { - "$binary": { - "base64": "AAAAAAAAAAAAAAAAAAAAAA==", - "subType": "04" - } - } - ], - "bsonType": "string", - "algorithm": "AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic" - } - } - }, - "bsonType": "object" - } - }, - "kmsProviders": { - "aws": {} - } - } - }, - "operations": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 1, - "encrypted_string": "string0" - } - } - }, - { - "name": "find", - "arguments": { - "filter": { - "_id": 1 - } - }, - "result": [ - { - "_id": 1, - "encrypted_string": "string0" - } - ] - } - ], - "expectations": [ - { - "command_started_event": { - "command": { - "find": "datakeys", - "filter": { - "$or": [ - { - "_id": { - "$in": [ - { - "$binary": { - "base64": "AAAAAAAAAAAAAAAAAAAAAA==", - "subType": "04" - } - } - ] - } - }, - { - "keyAltNames": { - "$in": [] - } - } - ] - }, - "$db": "keyvault", - "readConcern": { - "level": "majority" - } - }, - "command_name": "find" - } - }, - { - "command_started_event": { - "command": { - "insert": "default", - "documents": [ - { - "_id": 1, - "encrypted_string": { - "$binary": { - "base64": "AQAAAAAAAAAAAAAAAAAAAAACwj+3zkv2VM+aTfk60RqhXq6a/77WlLwu/BxXFkL7EppGsju/m8f0x5kBDD3EZTtGALGXlym5jnpZAoSIkswHoA==", - "subType": "06" - } - } - } - ], - "ordered": true - }, - "command_name": "insert" - } - }, - { - "command_started_event": { - "command": { - "find": "default", - "filter": { - "_id": 1 - } - }, - "command_name": "find" - } - } - ], - "outcome": { - "collection": { - "data": [ - { - "_id": 1, - "encrypted_string": { - "$binary": { - "base64": "AQAAAAAAAAAAAAAAAAAAAAACwj+3zkv2VM+aTfk60RqhXq6a/77WlLwu/BxXFkL7EppGsju/m8f0x5kBDD3EZTtGALGXlym5jnpZAoSIkswHoA==", - "subType": "06" - } - } - } - ] - } - } - }, - { - "description": "A local schema with no encryption is an error", - "clientOptions": { - "autoEncryptOpts": { - "schemaMap": { - "default.default": { - "properties": { - "test": { - "bsonType": "string" - } - }, - "bsonType": "object", - "required": [ - "test" - ] - } - }, - "kmsProviders": { - "aws": {} - } - } - }, - "operations": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 1, - "encrypted_string": "string0" - } - }, - "result": { - "errorContains": "JSON schema keyword 'required' is only allowed with a remote schema" - } - } - ] - } - ] -} diff --git a/src/libmongoc/tests/json/client_side_encryption/legacy/maxWireVersion.json b/src/libmongoc/tests/json/client_side_encryption/legacy/maxWireVersion.json deleted file mode 100644 index f04f58dffde..00000000000 --- a/src/libmongoc/tests/json/client_side_encryption/legacy/maxWireVersion.json +++ /dev/null @@ -1,74 +0,0 @@ -{ - "runOn": [ - { - "maxServerVersion": "4.0.99" - } - ], - "database_name": "default", - "collection_name": "default", - "data": [], - "key_vault_data": [ - { - "status": 1, - "_id": { - "$binary": { - "base64": "AAAAAAAAAAAAAAAAAAAAAA==", - "subType": "04" - } - }, - "masterKey": { - "provider": "aws", - "key": "arn:aws:kms:us-east-1:579766882180:key/89fcc2c4-08b0-4bd9-9f25-e30687b580d0", - "region": "us-east-1" - }, - "updateDate": { - "$date": { - "$numberLong": "1552949630483" - } - }, - "keyMaterial": { - "$binary": { - "base64": "AQICAHhQNmWG2CzOm1dq3kWLM+iDUZhEqnhJwH9wZVpuZ94A8gEqnsxXlR51T5EbEVezUqqKAAAAwjCBvwYJKoZIhvcNAQcGoIGxMIGuAgEAMIGoBgkqhkiG9w0BBwEwHgYJYIZIAWUDBAEuMBEEDHa4jo6yp0Z18KgbUgIBEIB74sKxWtV8/YHje5lv5THTl0HIbhSwM6EqRlmBiFFatmEWaeMk4tO4xBX65eq670I5TWPSLMzpp8ncGHMmvHqRajNBnmFtbYxN3E3/WjxmdbOOe+OXpnGJPcGsftc7cB2shRfA4lICPnE26+oVNXT6p0Lo20nY5XC7jyCO", - "subType": "00" - } - }, - "creationDate": { - "$date": { - "$numberLong": "1552949630483" - } - }, - "keyAltNames": [ - "altname", - "another_altname" - ] - } - ], - "tests": [ - { - "description": "operation fails with maxWireVersion < 8", - "clientOptions": { - "autoEncryptOpts": { - "kmsProviders": { - "aws": {} - }, - "extraOptions": { - "mongocryptdBypassSpawn": true - } - } - }, - "operations": [ - { - "name": "insertOne", - "arguments": { - "document": { - "encrypted_string": "string0" - } - }, - "result": { - "errorContains": "Auto-encryption requires a minimum MongoDB version of 4.2" - } - } - ] - } - ] -} diff --git a/src/libmongoc/tests/json/client_side_encryption/unified/fle2v2-BypassQueryAnalysis.json b/src/libmongoc/tests/json/client_side_encryption/unified/fle2v2-BypassQueryAnalysis.json new file mode 100644 index 00000000000..0817508f8f8 --- /dev/null +++ b/src/libmongoc/tests/json/client_side_encryption/unified/fle2v2-BypassQueryAnalysis.json @@ -0,0 +1,322 @@ +{ + "description": "fle2v2-BypassQueryAnalysis", + "schemaVersion": "1.23", + "runOnRequirements": [ + { + "minServerVersion": "7.0.0", + "serverless": "forbid", + "csfle": true, + "topologies": [ + "replicaset", + "sharded", + "load-balanced" + ] + } + ], + "createEntities": [ + { + "client": { + "id": "client0", + "autoEncryptOpts": { + "kmsProviders": { + "local": { + "key": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk" + } + }, + "keyVaultNamespace": "keyvault.datakeys", + "bypassQueryAnalysis": true + }, + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "encryptedDB", + "client": "client0", + "databaseName": "default" + } + }, + { + "collection": { + "id": "encryptedColl", + "database": "encryptedDB", + "collectionName": "default" + } + }, + { + "client": { + "id": "client1" + } + }, + { + "database": { + "id": "unencryptedDB", + "client": "client1", + "databaseName": "default" + } + }, + { + "collection": { + "id": "unencryptedColl", + "database": "unencryptedDB", + "collectionName": "default" + } + } + ], + "initialData": [ + { + "databaseName": "keyvault", + "collectionName": "datakeys", + "documents": [ + { + "_id": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "keyMaterial": { + "$binary": { + "base64": "sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==", + "subType": "00" + } + }, + "creationDate": { + "$date": { + "$numberLong": "1648914851981" + } + }, + "updateDate": { + "$date": { + "$numberLong": "1648914851981" + } + }, + "status": { + "$numberInt": "0" + }, + "masterKey": { + "provider": "local" + } + } + ] + }, + { + "databaseName": "default", + "collectionName": "default", + "documents": [], + "createOptions": { + "encryptedFields": { + "fields": [ + { + "keyId": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "encryptedIndexed", + "bsonType": "string", + "queries": { + "queryType": "equality", + "contention": { + "$numberLong": "0" + } + } + }, + { + "keyId": { + "$binary": { + "base64": "q83vqxI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "encryptedUnindexed", + "bsonType": "string" + } + ] + } + } + } + ], + "tests": [ + { + "description": "BypassQueryAnalysis decrypts", + "operations": [ + { + "object": "encryptedColl", + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "encryptedIndexed": { + "$binary": { + "base64": "C18BAAAFZAAgAAAAANnt+eLTkv4GdDPl8IAfJOvTzArOgFJQ2S/DcLza4W0DBXMAIAAAAAD2u+omZme3P2gBPehMQyQHQ153tPN1+z7bksYA9jKTpAVwADAAAAAAUnCOQqIvmR65YKyYnsiVfVrg9hwUVO3RhhKExo3RWOzgaS0QdsBL5xKFS0JhZSoWBXUAEAAAAAQSNFZ4EjSYdhI0EjRWeJASEHQAAgAAAAV2AFAAAAAAEjRWeBI0mHYSNBI0VniQEpQbp/ZJpWBKeDtKLiXb0P2E9wvc0g3f373jnYQYlJquOrlPOoEy3ngsHPJuSUijvWDsrQzqYa349K7G/66qaXEFZQAgAAAAAOuac/eRLYakKX6B0vZ1r3QodOQFfjqJD+xlGiPu4/PsBWwAIAAAAACkm0o9bj6j0HuADKc0svbqO2UHj6GrlNdF6yKNxh63xRJrAAAAAAAAAAAAAA==", + "subType": "06" + } + } + } + } + }, + { + "object": "encryptedColl", + "name": "find", + "arguments": { + "filter": { + "_id": 1 + } + }, + "expectResult": [ + { + "_id": 1, + "encryptedIndexed": "123" + } + ] + }, + { + "object": "unencryptedColl", + "name": "find", + "arguments": { + "filter": {} + }, + "expectResult": [ + { + "_id": 1, + "encryptedIndexed": { + "$$type": "binData" + }, + "__safeContent__": [ + { + "$binary": { + "base64": "31eCYlbQoVboc5zwC8IoyJVSkag9PxREka8dkmbXJeY=", + "subType": "00" + } + } + ] + } + ] + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "command": { + "listCollections": 1, + "filter": { + "name": "default" + } + }, + "commandName": "listCollections" + } + }, + { + "commandStartedEvent": { + "command": { + "insert": "default", + "documents": [ + { + "_id": 1, + "encryptedIndexed": { + "$binary": { + "base64": "C18BAAAFZAAgAAAAANnt+eLTkv4GdDPl8IAfJOvTzArOgFJQ2S/DcLza4W0DBXMAIAAAAAD2u+omZme3P2gBPehMQyQHQ153tPN1+z7bksYA9jKTpAVwADAAAAAAUnCOQqIvmR65YKyYnsiVfVrg9hwUVO3RhhKExo3RWOzgaS0QdsBL5xKFS0JhZSoWBXUAEAAAAAQSNFZ4EjSYdhI0EjRWeJASEHQAAgAAAAV2AFAAAAAAEjRWeBI0mHYSNBI0VniQEpQbp/ZJpWBKeDtKLiXb0P2E9wvc0g3f373jnYQYlJquOrlPOoEy3ngsHPJuSUijvWDsrQzqYa349K7G/66qaXEFZQAgAAAAAOuac/eRLYakKX6B0vZ1r3QodOQFfjqJD+xlGiPu4/PsBWwAIAAAAACkm0o9bj6j0HuADKc0svbqO2UHj6GrlNdF6yKNxh63xRJrAAAAAAAAAAAAAA==", + "subType": "06" + } + } + } + ], + "ordered": true, + "encryptionInformation": { + "type": 1, + "schema": { + "default.default": { + "escCollection": "enxcol_.default.esc", + "ecocCollection": "enxcol_.default.ecoc", + "fields": [ + { + "keyId": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "encryptedIndexed", + "bsonType": "string", + "queries": { + "queryType": "equality", + "contention": { + "$numberLong": "0" + } + } + }, + { + "keyId": { + "$binary": { + "base64": "q83vqxI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "encryptedUnindexed", + "bsonType": "string" + } + ] + } + } + } + }, + "commandName": "insert" + } + }, + { + "commandStartedEvent": { + "command": { + "find": "default", + "filter": { + "_id": 1 + } + }, + "commandName": "find" + } + }, + { + "commandStartedEvent": { + "command": { + "find": "datakeys", + "filter": { + "$or": [ + { + "_id": { + "$in": [ + { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + } + ] + } + }, + { + "keyAltNames": { + "$in": [] + } + } + ] + }, + "$db": "keyvault", + "readConcern": { + "level": "majority" + } + }, + "commandName": "find" + } + } + ] + } + ] + } + ] +} diff --git a/src/libmongoc/tests/json/client_side_encryption/unified/fle2v2-EncryptedFields-vs-EncryptedFieldsMap.json b/src/libmongoc/tests/json/client_side_encryption/unified/fle2v2-EncryptedFields-vs-EncryptedFieldsMap.json new file mode 100644 index 00000000000..bd1d0703ec1 --- /dev/null +++ b/src/libmongoc/tests/json/client_side_encryption/unified/fle2v2-EncryptedFields-vs-EncryptedFieldsMap.json @@ -0,0 +1,256 @@ +{ + "description": "fle2v2-EncryptedFields-vs-EncryptedFieldsMap", + "schemaVersion": "1.23", + "runOnRequirements": [ + { + "minServerVersion": "7.0.0", + "serverless": "forbid", + "csfle": true, + "topologies": [ + "replicaset", + "sharded", + "load-balanced" + ] + } + ], + "createEntities": [ + { + "client": { + "id": "client0", + "autoEncryptOpts": { + "keyVaultNamespace": "keyvault.datakeys", + "kmsProviders": { + "local": { + "key": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk" + } + }, + "encryptedFieldsMap": { + "default.default": { + "fields": [] + } + } + }, + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "encryptedDB", + "client": "client0", + "databaseName": "default" + } + }, + { + "collection": { + "id": "encryptedColl", + "database": "encryptedDB", + "collectionName": "default" + } + } + ], + "initialData": [ + { + "databaseName": "keyvault", + "collectionName": "datakeys", + "documents": [ + { + "_id": { + "$binary": { + "base64": "q83vqxI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "keyMaterial": { + "$binary": { + "base64": "HBk9BWihXExNDvTp1lUxOuxuZK2Pe2ZdVdlsxPEBkiO1bS4mG5NNDsQ7zVxJAH8BtdOYp72Ku4Y3nwc0BUpIKsvAKX4eYXtlhv5zUQxWdeNFhg9qK7qb8nqhnnLeT0f25jFSqzWJoT379hfwDeu0bebJHr35QrJ8myZdPMTEDYF08QYQ48ShRBli0S+QzBHHAQiM2iJNr4svg2WR8JSeWQ==", + "subType": "00" + } + }, + "creationDate": { + "$date": { + "$numberLong": "1648914851981" + } + }, + "updateDate": { + "$date": { + "$numberLong": "1648914851981" + } + }, + "status": { + "$numberInt": "0" + }, + "masterKey": { + "provider": "local" + } + } + ] + }, + { + "databaseName": "default", + "collectionName": "default", + "documents": [], + "createOptions": { + "encryptedFields": { + "fields": [ + { + "keyId": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "encryptedIndexed", + "bsonType": "string", + "queries": { + "queryType": "equality", + "contention": { + "$numberLong": "0" + } + } + }, + { + "keyId": { + "$binary": { + "base64": "q83vqxI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "encryptedUnindexed", + "bsonType": "string" + } + ] + } + } + } + ], + "tests": [ + { + "description": "encryptedFieldsMap is preferred over remote encryptedFields", + "operations": [ + { + "object": "encryptedColl", + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "encryptedUnindexed": { + "$binary": { + "base64": "BqvN76sSNJh2EjQSNFZ4kBICTQaVZPWgXp41I7mPV1rLFTtw1tXzjcdSEyxpKKqujlko5TeizkB9hHQ009dVY1+fgIiDcefh+eQrm3CkhQ==", + "subType": "06" + } + } + } + } + }, + { + "object": "encryptedColl", + "name": "find", + "arguments": { + "filter": { + "_id": 1 + } + }, + "expectResult": [ + { + "_id": 1, + "encryptedUnindexed": "value123" + } + ] + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "databaseName": "default", + "commandName": "insert", + "command": { + "insert": "default", + "documents": [ + { + "_id": 1, + "encryptedUnindexed": { + "$binary": { + "base64": "BqvN76sSNJh2EjQSNFZ4kBICTQaVZPWgXp41I7mPV1rLFTtw1tXzjcdSEyxpKKqujlko5TeizkB9hHQ009dVY1+fgIiDcefh+eQrm3CkhQ==", + "subType": "06" + } + } + } + ], + "ordered": true + } + } + }, + { + "commandStartedEvent": { + "databaseName": "default", + "commandName": "find", + "command": { + "find": "default", + "filter": { + "_id": 1 + } + } + } + }, + { + "commandStartedEvent": { + "databaseName": "keyvault", + "commandName": "find", + "command": { + "find": "datakeys", + "filter": { + "$or": [ + { + "_id": { + "$in": [ + { + "$binary": { + "base64": "q83vqxI0mHYSNBI0VniQEg==", + "subType": "04" + } + } + ] + } + }, + { + "keyAltNames": { + "$in": [] + } + } + ] + }, + "$db": "keyvault", + "readConcern": { + "level": "majority" + } + } + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "default", + "databaseName": "default", + "documents": [ + { + "_id": 1, + "encryptedUnindexed": { + "$binary": { + "base64": "BqvN76sSNJh2EjQSNFZ4kBICTQaVZPWgXp41I7mPV1rLFTtw1tXzjcdSEyxpKKqujlko5TeizkB9hHQ009dVY1+fgIiDcefh+eQrm3CkhQ==", + "subType": "06" + } + } + } + ] + } + ] + } + ] +} diff --git a/src/libmongoc/tests/json/client_side_encryption/unified/localSchema.json b/src/libmongoc/tests/json/client_side_encryption/unified/localSchema.json new file mode 100644 index 00000000000..fef4b4e1229 --- /dev/null +++ b/src/libmongoc/tests/json/client_side_encryption/unified/localSchema.json @@ -0,0 +1,336 @@ +{ + "description": "localSchema", + "schemaVersion": "1.23", + "runOnRequirements": [ + { + "minServerVersion": "4.1.10", + "csfle": true + } + ], + "createEntities": [ + { + "client": { + "id": "client0", + "autoEncryptOpts": { + "keyVaultNamespace": "keyvault.datakeys", + "schemaMap": { + "default.default": { + "properties": { + "encrypted_w_altname": { + "encrypt": { + "keyId": "/altname", + "bsonType": "string", + "algorithm": "AEAD_AES_256_CBC_HMAC_SHA_512-Random" + } + }, + "encrypted_string": { + "encrypt": { + "keyId": [ + { + "$binary": { + "base64": "AAAAAAAAAAAAAAAAAAAAAA==", + "subType": "04" + } + } + ], + "bsonType": "string", + "algorithm": "AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic" + } + }, + "random": { + "encrypt": { + "keyId": [ + { + "$binary": { + "base64": "AAAAAAAAAAAAAAAAAAAAAA==", + "subType": "04" + } + } + ], + "bsonType": "string", + "algorithm": "AEAD_AES_256_CBC_HMAC_SHA_512-Random" + } + }, + "encrypted_string_equivalent": { + "encrypt": { + "keyId": [ + { + "$binary": { + "base64": "AAAAAAAAAAAAAAAAAAAAAA==", + "subType": "04" + } + } + ], + "bsonType": "string", + "algorithm": "AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic" + } + } + }, + "bsonType": "object" + } + }, + "kmsProviders": { + "aws": { + "accessKeyId": { + "$$placeholder": 1 + }, + "secretAccessKey": { + "$$placeholder": 1 + } + } + } + }, + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "client": { + "id": "client1", + "autoEncryptOpts": { + "keyVaultNamespace": "keyvault.datakeys", + "schemaMap": { + "default.default": { + "properties": { + "test": { + "bsonType": "string" + } + }, + "bsonType": "object", + "required": [ + "test" + ] + } + }, + "kmsProviders": { + "aws": { + "accessKeyId": { + "$$placeholder": 1 + }, + "secretAccessKey": { + "$$placeholder": 1 + } + } + } + }, + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "encryptedDB", + "client": "client0", + "databaseName": "default" + } + }, + { + "collection": { + "id": "encryptedColl", + "database": "encryptedDB", + "collectionName": "default" + } + }, + { + "database": { + "id": "encryptedDB2", + "client": "client1", + "databaseName": "default" + } + }, + { + "collection": { + "id": "encryptedColl2", + "database": "encryptedDB2", + "collectionName": "default" + } + } + ], + "initialData": [ + { + "databaseName": "keyvault", + "collectionName": "datakeys", + "documents": [ + { + "status": 1, + "_id": { + "$binary": { + "base64": "AAAAAAAAAAAAAAAAAAAAAA==", + "subType": "04" + } + }, + "masterKey": { + "provider": "aws", + "key": "arn:aws:kms:us-east-1:579766882180:key/89fcc2c4-08b0-4bd9-9f25-e30687b580d0", + "region": "us-east-1" + }, + "updateDate": { + "$date": { + "$numberLong": "1552949630483" + } + }, + "keyMaterial": { + "$binary": { + "base64": "AQICAHhQNmWG2CzOm1dq3kWLM+iDUZhEqnhJwH9wZVpuZ94A8gEqnsxXlR51T5EbEVezUqqKAAAAwjCBvwYJKoZIhvcNAQcGoIGxMIGuAgEAMIGoBgkqhkiG9w0BBwEwHgYJYIZIAWUDBAEuMBEEDHa4jo6yp0Z18KgbUgIBEIB74sKxWtV8/YHje5lv5THTl0HIbhSwM6EqRlmBiFFatmEWaeMk4tO4xBX65eq670I5TWPSLMzpp8ncGHMmvHqRajNBnmFtbYxN3E3/WjxmdbOOe+OXpnGJPcGsftc7cB2shRfA4lICPnE26+oVNXT6p0Lo20nY5XC7jyCO", + "subType": "00" + } + }, + "creationDate": { + "$date": { + "$numberLong": "1552949630483" + } + }, + "keyAltNames": [ + "altname", + "another_altname" + ] + } + ] + }, + { + "databaseName": "default", + "collectionName": "default", + "documents": [] + } + ], + "tests": [ + { + "description": "A local schema should override", + "operations": [ + { + "object": "encryptedColl", + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "encrypted_string": "string0" + } + } + }, + { + "object": "encryptedColl", + "name": "find", + "arguments": { + "filter": { + "_id": 1 + } + }, + "expectResult": [ + { + "_id": 1, + "encrypted_string": "string0" + } + ] + } + ], + "expectEvents": [ + { + "client": "client0", + "events": [ + { + "commandStartedEvent": { + "databaseName": "keyvault", + "commandName": "find", + "command": { + "find": "datakeys", + "filter": { + "$or": [ + { + "_id": { + "$in": [ + { + "$binary": { + "base64": "AAAAAAAAAAAAAAAAAAAAAA==", + "subType": "04" + } + } + ] + } + }, + { + "keyAltNames": { + "$in": [] + } + } + ] + }, + "readConcern": { + "level": "majority" + } + } + } + }, + { + "commandStartedEvent": { + "commandName": "insert", + "command": { + "insert": "default", + "documents": [ + { + "_id": 1, + "encrypted_string": { + "$binary": { + "base64": "AQAAAAAAAAAAAAAAAAAAAAACwj+3zkv2VM+aTfk60RqhXq6a/77WlLwu/BxXFkL7EppGsju/m8f0x5kBDD3EZTtGALGXlym5jnpZAoSIkswHoA==", + "subType": "06" + } + } + } + ], + "ordered": true + } + } + }, + { + "commandStartedEvent": { + "commandName": "find", + "command": { + "find": "default", + "filter": { + "_id": 1 + } + } + } + } + ] + } + ], + "outcome": [ + { + "collectionName": "default", + "databaseName": "default", + "documents": [ + { + "_id": 1, + "encrypted_string": { + "$binary": { + "base64": "AQAAAAAAAAAAAAAAAAAAAAACwj+3zkv2VM+aTfk60RqhXq6a/77WlLwu/BxXFkL7EppGsju/m8f0x5kBDD3EZTtGALGXlym5jnpZAoSIkswHoA==", + "subType": "06" + } + } + } + ] + } + ] + }, + { + "description": "A local schema with no encryption is an error", + "operations": [ + { + "object": "encryptedColl2", + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "encrypted_string": "string0" + } + }, + "expectError": { + "isClientError": true + } + } + ] + } + ] +} diff --git a/src/libmongoc/tests/json/client_side_encryption/unified/maxWireVersion.json b/src/libmongoc/tests/json/client_side_encryption/unified/maxWireVersion.json new file mode 100644 index 00000000000..07b565da18e --- /dev/null +++ b/src/libmongoc/tests/json/client_side_encryption/unified/maxWireVersion.json @@ -0,0 +1,101 @@ +{ + "description": "maxWireVersion", + "schemaVersion": "1.23", + "runOnRequirements": [ + { + "maxServerVersion": "4.0.99", + "csfle": true + } + ], + "createEntities": [ + { + "client": { + "id": "client0", + "autoEncryptOpts": { + "keyVaultNamespace": "keyvault.datakeys", + "kmsProviders": { + "aws": {} + }, + "extraOptions": { + "mongocryptdBypassSpawn": true + } + } + } + }, + { + "database": { + "id": "database0", + "client": "client0", + "databaseName": "default" + } + }, + { + "collection": { + "id": "collection0", + "database": "database0", + "collectionName": "default" + } + } + ], + "initialData": [ + { + "databaseName": "keyvault", + "collectionName": "datakeys", + "documents": [ + { + "status": 1, + "_id": { + "$binary": { + "base64": "AAAAAAAAAAAAAAAAAAAAAA==", + "subType": "04" + } + }, + "masterKey": { + "provider": "aws", + "key": "arn:aws:kms:us-east-1:579766882180:key/89fcc2c4-08b0-4bd9-9f25-e30687b580d0", + "region": "us-east-1" + }, + "updateDate": { + "$date": { + "$numberLong": "1552949630483" + } + }, + "keyMaterial": { + "$binary": { + "base64": "AQICAHhQNmWG2CzOm1dq3kWLM+iDUZhEqnhJwH9wZVpuZ94A8gEqnsxXlR51T5EbEVezUqqKAAAAwjCBvwYJKoZIhvcNAQcGoIGxMIGuAgEAMIGoBgkqhkiG9w0BBwEwHgYJYIZIAWUDBAEuMBEEDHa4jo6yp0Z18KgbUgIBEIB74sKxWtV8/YHje5lv5THTl0HIbhSwM6EqRlmBiFFatmEWaeMk4tO4xBX65eq670I5TWPSLMzpp8ncGHMmvHqRajNBnmFtbYxN3E3/WjxmdbOOe+OXpnGJPcGsftc7cB2shRfA4lICPnE26+oVNXT6p0Lo20nY5XC7jyCO", + "subType": "00" + } + }, + "creationDate": { + "$date": { + "$numberLong": "1552949630483" + } + }, + "keyAltNames": [ + "altname", + "another_altname" + ] + } + ] + } + ], + "tests": [ + { + "description": "operation fails with maxWireVersion < 8", + "operations": [ + { + "name": "insertOne", + "object": "collection0", + "arguments": { + "document": { + "encrypted_string": "string0" + } + }, + "expectError": { + "errorContains": "Auto-encryption requires a minimum MongoDB version of 4.2" + } + } + ] + } + ] +} diff --git a/src/libmongoc/tests/json/unified/poc-queryable-encryption.json b/src/libmongoc/tests/json/unified/poc-queryable-encryption.json new file mode 100644 index 00000000000..9788977cb60 --- /dev/null +++ b/src/libmongoc/tests/json/unified/poc-queryable-encryption.json @@ -0,0 +1,188 @@ +{ + "description": "poc-queryable-encryption", + "schemaVersion": "1.23", + "runOnRequirements": [ + { + "minServerVersion": "7.0", + "csfle": true + } + ], + "createEntities": [ + { + "client": { + "id": "client0", + "autoEncryptOpts": { + "keyVaultNamespace": "keyvault.datakeys", + "kmsProviders": { + "local": { + "key": "Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk" + } + } + } + } + }, + { + "database": { + "id": "encryptedDB", + "client": "client0", + "databaseName": "poc-queryable-encryption" + } + }, + { + "collection": { + "id": "encryptedColl", + "database": "encryptedDB", + "collectionName": "encrypted" + } + }, + { + "client": { + "id": "client1" + } + }, + { + "database": { + "id": "unencryptedDB", + "client": "client1", + "databaseName": "poc-queryable-encryption" + } + }, + { + "collection": { + "id": "unencryptedColl", + "database": "unencryptedDB", + "collectionName": "encrypted" + } + } + ], + "initialData": [ + { + "databaseName": "keyvault", + "collectionName": "datakeys", + "documents": [ + { + "_id": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "keyMaterial": { + "$binary": { + "base64": "sHe0kz57YW7v8g9VP9sf/+K1ex4JqKc5rf/URX3n3p8XdZ6+15uXPaSayC6adWbNxkFskuMCOifDoTT+rkqMtFkDclOy884RuGGtUysq3X7zkAWYTKi8QAfKkajvVbZl2y23UqgVasdQu3OVBQCrH/xY00nNAs/52e958nVjBuzQkSb1T8pKJAyjZsHJ60+FtnfafDZSTAIBJYn7UWBCwQ==", + "subType": "00" + } + }, + "creationDate": { + "$date": { + "$numberLong": "1641024000000" + } + }, + "updateDate": { + "$date": { + "$numberLong": "1641024000000" + } + }, + "status": 1, + "masterKey": { + "provider": "local" + } + } + ] + }, + { + "databaseName": "poc-queryable-encryption", + "collectionName": "encrypted", + "documents": [], + "createOptions": { + "encryptedFields": { + "fields": [ + { + "keyId": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "encryptedInt", + "bsonType": "int", + "queries": { + "queryType": "equality", + "contention": { + "$numberLong": "0" + } + } + } + ] + } + } + } + ], + "tests": [ + { + "description": "insert, replace, and find with queryable encryption", + "operations": [ + { + "object": "encryptedColl", + "name": "insertOne", + "arguments": { + "document": { + "_id": 1, + "encryptedInt": 11 + } + } + }, + { + "object": "encryptedColl", + "name": "replaceOne", + "arguments": { + "filter": { + "encryptedInt": 11 + }, + "replacement": { + "encryptedInt": 22 + } + } + }, + { + "object": "encryptedColl", + "name": "find", + "arguments": { + "filter": { + "encryptedInt": 22 + } + }, + "expectResult": [ + { + "_id": 1, + "encryptedInt": 22 + } + ] + }, + { + "object": "unencryptedColl", + "name": "find", + "arguments": { + "filter": {} + }, + "expectResult": [ + { + "_id": 1, + "encryptedInt": { + "$$type": "binData" + }, + "__safeContent__": [ + { + "$binary": { + "base64": "rhS16TJojgDDBtbluxBokvcotP1mQTGeYpNt8xd3MJQ=", + "subType": "00" + } + } + ] + } + ] + } + ] + } + ] +} diff --git a/src/libmongoc/tests/json/unified/poc-transactions.json b/src/libmongoc/tests/json/unified/poc-transactions.json index 0355ca20605..2055a3b7057 100644 --- a/src/libmongoc/tests/json/unified/poc-transactions.json +++ b/src/libmongoc/tests/json/unified/poc-transactions.json @@ -11,7 +11,7 @@ { "minServerVersion": "4.1.8", "topologies": [ - "sharded-replicaset" + "sharded" ] } ], @@ -93,7 +93,7 @@ "minServerVersion": "4.3.4", "topologies": [ "replicaset", - "sharded-replicaset" + "sharded" ] } ], @@ -203,7 +203,7 @@ "minServerVersion": "4.3.4", "topologies": [ "replicaset", - "sharded-replicaset" + "sharded" ] } ], diff --git a/src/libmongoc/tests/unified/entity-map.c b/src/libmongoc/tests/unified/entity-map.c index ad4f50b110e..738b22ebd5e 100644 --- a/src/libmongoc/tests/unified/entity-map.c +++ b/src/libmongoc/tests/unified/entity-map.c @@ -16,6 +16,7 @@ #include "entity-map.h" +#include "bson/bson.h" #include "bsonutil/bson-parser.h" #include "TestSuite.h" #include @@ -48,6 +49,9 @@ typedef void (*event_serialize_func_t) (bson_t *bson, const void *event); static void entity_destroy (entity_t *entity); +static bool +_parse_and_set_auto_encryption_opts (mongoc_client_t *client, bson_t *opts, bson_error_t *error); + entity_map_t * entity_map_new (void) { @@ -724,6 +728,7 @@ entity_client_new (entity_map_t *em, bson_t *bson, bson_error_t *error) bool ret = false; mongoc_apm_callbacks_t *callbacks = NULL; bson_t *uri_options = NULL; + bson_t *auto_encryption_opts = NULL; mongoc_structured_log_opts_t *log_opts = mongoc_structured_log_opts_new (); bool use_multiple_mongoses = false; bool use_multiple_mongoses_set = false; @@ -803,32 +808,37 @@ entity_client_new (entity_map_t *em, bson_t *bson, bson_error_t *error) *p = bsonAs (boolean); })), // Which events should be available as entities: - find ( - key ("storeEventsAsEntities"), - if (not(type (array)), then (error ("'storeEventsAsEntities' must be an array"))), - visitEach (parse ( - find (keyWithType ("id", utf8), storeStrRef (store_entity_id), do ({ - if (!entity_map_add_bson_array (em, store_entity_id, error)) { - test_error ("failed to create storeEventsAsEntities " - "entity '%s': %s", - store_entity_id, - error->message); - } - })), - find (keyWithType ("events", array), - visitEach (case (when (not(type (utf8)), - error ("Every 'storeEventsAsEntities.events' " - "element must be a string")), - when (eval (is_supported_event_type (bson_iter_utf8 (&bsonVisitIter, NULL))), do ({ - const char *const type = bson_iter_utf8 (&bsonVisitIter, NULL); - set_event_callback (callbacks, type); - add_store_event (entity, type, store_entity_id); - })), - when (eval (is_unsupported_event_type (bson_iter_utf8 (&bsonVisitIter, NULL))), - do (MONGOC_DEBUG ("Skipping unsupported event type '%s'", bsonAs (cstr)))), - else (do (test_error ("Unknown event type '%s'", bsonAs (cstr))))))), - visitOthers ( - errorf (err, "Unexpected field '%s' in storeEventsAsEntities", bson_iter_key (&bsonVisitIter)))))), + find (key ("storeEventsAsEntities"), + if (not(type (array)), then (error ("'storeEventsAsEntities' must be an array"))), + visitEach (parse ( + find (keyWithType ("id", utf8), storeStrRef (store_entity_id), do ({ + if (!entity_map_add_bson_array (em, store_entity_id, error)) { + test_error ("failed to create storeEventsAsEntities " + "entity '%s': %s", + store_entity_id, + error->message); + } + })), + find (keyWithType ("events", array), + visitEach (case (when (not(type (utf8)), + error ("Every 'storeEventsAsEntities.events' " + "element must be a string")), + when (anyOf (iStrEqual ("commandStartedEvent"), + iStrEqual ("commandFailedEvent"), + iStrEqual ("commandSucceededEvent")), + do ({ + const char *const type = bson_iter_utf8 (&bsonVisitIter, NULL); + set_event_callback (callbacks, type); + add_store_event (entity, type, store_entity_id); + })), + when (eval (is_unsupported_event_type (bson_iter_utf8 (&bsonVisitIter, NULL))), + do (MONGOC_DEBUG ("Skipping unsupported event type '%s'", bsonAs (cstr)))), + else (do (test_error ("Unknown event type '%s'", bsonAs (cstr))))))), + visitOthers ( + errorf (err, "Unexpected field '%s' in storeEventsAsEntities", bson_iter_key (&bsonVisitIter)))))), + find (key ("autoEncryptOpts"), + if (not(type (doc)), then (error ("'autoEncryptOpts' must be a document value"))), + storeDocDupPtr (auto_encryption_opts)), // Log messages to observe: find (key ("observeLogMessages"), if (not(type (doc)), then (error ("'observeLogMessages' must be a document"))), @@ -914,6 +924,12 @@ entity_client_new (entity_map_t *em, bson_t *bson, bson_error_t *error) client->topology->min_heartbeat_frequency_msec = REDUCED_MIN_HEARTBEAT_FREQUENCY_MS; } + if (auto_encryption_opts) { + if (!_parse_and_set_auto_encryption_opts (client, auto_encryption_opts, error)) { + goto done; + } + } + ret = true; done: mongoc_uri_destroy (uri); @@ -921,6 +937,7 @@ entity_client_new (entity_map_t *em, bson_t *bson, bson_error_t *error) mongoc_server_api_destroy (api); mongoc_structured_log_opts_destroy (log_opts); bson_destroy (uri_options); + bson_destroy (auto_encryption_opts); if (!ret) { entity_destroy (entity); return NULL; @@ -1232,7 +1249,7 @@ _parse_kms_provider_local ( } static bool -_parse_and_set_kms_providers (mongoc_client_encryption_opts_t *ce_opts, bson_t *kms_from_file, bson_error_t *error) +_get_kms_providers_docs (bson_t *kms_from_file, bson_t *kms_providers, bson_t *tls_opts, bson_error_t *error) { /* Map provider to corresponding KMS parser. */ typedef struct _prov_map_t { @@ -1255,10 +1272,6 @@ _parse_and_set_kms_providers (mongoc_client_encryption_opts_t *ce_opts, bson_t * {.provider = "local:name2", .parse = _parse_kms_provider_local}}; const size_t prov_map_size = sizeof (prov_map) / sizeof (prov_map[0]); - - bool ret = false; - bson_t kms_providers = BSON_INITIALIZER; - bson_t tls_opts = BSON_INITIALIZER; bson_iter_t iter; BSON_FOREACH (kms_from_file, iter) @@ -1270,12 +1283,12 @@ _parse_and_set_kms_providers (mongoc_client_encryption_opts_t *ce_opts, bson_t * if (!bson_init_from_value (&kms_doc, bson_iter_value (&iter))) { test_set_error (error, "kmsProviders field '%s' is not a valid document", provider); - goto done; + return false; } for (i = 0u; i < prov_map_size; ++i) { if (strcmp (provider, prov_map[i].provider) == 0) { - found = prov_map[i].parse (&kms_providers, &tls_opts, provider, &kms_doc, error); + found = prov_map[i].parse (kms_providers, tls_opts, provider, &kms_doc, error); goto parsed; } } @@ -1286,13 +1299,119 @@ _parse_and_set_kms_providers (mongoc_client_encryption_opts_t *ce_opts, bson_t * bson_destroy (&kms_doc); if (!found) { + return false; + } + } + return true; +} + +static bool +_parse_and_set_auto_encryption_opts (mongoc_client_t *client, bson_t *opts, bson_error_t *error) +{ + bool ret = false; + mongoc_auto_encryption_opts_t *auto_encryption_opts = mongoc_auto_encryption_opts_new (); + bson_t kms_providers = BSON_INITIALIZER; + bson_t tls_opts = BSON_INITIALIZER; + BSON_ASSERT (client); + + bson_parser_t *const parser = bson_parser_new (); + + bson_t *kms_providers_raw; + bson_parser_doc (parser, "kmsProviders", &kms_providers_raw); + + char *keyvault_ns; + bson_parser_utf8 (parser, "keyVaultNamespace", &keyvault_ns); + + bson_t *schema_map; + bson_parser_doc_optional (parser, "schemaMap", &schema_map); + + bool *bypass_auto_encryption; + bson_parser_bool_optional (parser, "bypassAutoEncryption", &bypass_auto_encryption); + + bool *bypass_query_analysis; + bson_parser_bool_optional (parser, "bypassQueryAnalysis", &bypass_query_analysis); + + bson_t *encrypted_fields_map; + bson_parser_doc_optional (parser, "encryptedFieldsMap", &encrypted_fields_map); + + int64_t *key_expiration_ms; + bson_parser_int_optional (parser, "keyExpirationMS", &key_expiration_ms); + + bson_t *extra_options; + bson_parser_doc_optional (parser, "extraOptions", &extra_options); + + if (!bson_parser_parse (parser, opts, error)) { + goto done; + } + + { + if (!_get_kms_providers_docs (kms_providers_raw, &kms_providers, &tls_opts, error)) { goto done; } + mongoc_auto_encryption_opts_set_kms_providers (auto_encryption_opts, &kms_providers); + mongoc_auto_encryption_opts_set_tls_opts (auto_encryption_opts, &tls_opts); + } + + { + // keyVaultNamespace + char *dot = strstr (keyvault_ns, "."); + BSON_ASSERT (dot); + char *db_name = bson_strndup (keyvault_ns, dot - keyvault_ns); + char *coll_name = bson_strdup (dot + 1); + mongoc_auto_encryption_opts_set_keyvault_namespace (auto_encryption_opts, db_name, coll_name); + + bson_free (db_name); + bson_free (coll_name); + } + + if (schema_map) { + mongoc_auto_encryption_opts_set_schema_map (auto_encryption_opts, schema_map); + } + + if (bypass_auto_encryption) { + mongoc_auto_encryption_opts_set_bypass_auto_encryption (auto_encryption_opts, *bypass_auto_encryption); + } + + if (bypass_query_analysis) { + mongoc_auto_encryption_opts_set_bypass_query_analysis (auto_encryption_opts, *bypass_query_analysis); + } + + if (encrypted_fields_map) { + mongoc_auto_encryption_opts_set_encrypted_fields_map (auto_encryption_opts, encrypted_fields_map); + } + + if (key_expiration_ms) { + mongoc_auto_encryption_opts_set_key_expiration (auto_encryption_opts, *key_expiration_ms); + } + + if (extra_options) { + mongoc_auto_encryption_opts_set_extra (auto_encryption_opts, extra_options); } + if (!mongoc_client_enable_auto_encryption (client, auto_encryption_opts, error)) { + goto done; + } + ret = true; + +done: + mongoc_auto_encryption_opts_destroy (auto_encryption_opts); + bson_destroy (&kms_providers); + bson_destroy (&tls_opts); + bson_parser_destroy_with_parsed_fields (parser); + return ret; +} + +static bool +_parse_and_set_kms_providers (mongoc_client_encryption_opts_t *ce_opts, bson_t *kms_from_file, bson_error_t *error) +{ + bool ret = false; + bson_t kms_providers = BSON_INITIALIZER; + bson_t tls_opts = BSON_INITIALIZER; + if (!_get_kms_providers_docs (kms_from_file, &kms_providers, &tls_opts, error)) { + goto done; + } mongoc_client_encryption_opts_set_kms_providers (ce_opts, &kms_providers); mongoc_client_encryption_opts_set_tls_opts (ce_opts, &tls_opts); - ret = true; done: @@ -1413,6 +1532,7 @@ typedef struct { mongoc_read_concern_t *rc; mongoc_write_concern_t *wc; mongoc_read_prefs_t *rp; + bson_t *encrypted_fields; } coll_or_db_opts_t; static coll_or_db_opts_t * @@ -1430,6 +1550,7 @@ coll_or_db_opts_destroy (coll_or_db_opts_t *opts) mongoc_read_concern_destroy (opts->rc); mongoc_read_prefs_destroy (opts->rp); mongoc_write_concern_destroy (opts->wc); + bson_destroy (opts->encrypted_fields); bson_free (opts); } diff --git a/src/libmongoc/tests/unified/operation.c b/src/libmongoc/tests/unified/operation.c index cfd9cc44989..24dfc734144 100644 --- a/src/libmongoc/tests/unified/operation.c +++ b/src/libmongoc/tests/unified/operation.c @@ -1141,6 +1141,9 @@ operation_drop_collection (test_t *test, operation_t *op, result_t *result, bson goto done; } + /* Forward all arguments other than collection name as-is. */ + BSON_ASSERT (bson_concat (opts, bson_parser_get_extra (parser))); + coll = mongoc_database_get_collection (db, collection); mongoc_collection_drop_with_opts (coll, opts, &op_error); @@ -1850,7 +1853,7 @@ operation_distinct (test_t *test, operation_t *op, result_t *result, bson_error_ } distinct = BCON_NEW ( - "distinct", mongoc_collection_get_name (coll), "key", BCON_UTF8 (field_name), "query", "{", &filter, "}"); + "distinct", mongoc_collection_get_name (coll), "key", BCON_UTF8 (field_name), "query", BCON_DOCUMENT (filter)); bson_destroy (&op_reply); mongoc_collection_read_command_with_opts (coll, distinct, NULL /* read prefs */, opts, &op_reply, &op_error); diff --git a/src/libmongoc/tests/unified/result.c b/src/libmongoc/tests/unified/result.c index 6752b1f02dc..3f6074b5b1e 100644 --- a/src/libmongoc/tests/unified/result.c +++ b/src/libmongoc/tests/unified/result.c @@ -413,7 +413,7 @@ result_check (result_t *result, entity_map_t *em, bson_val_t *expect_result, bso } if (error_contains) { - if (strstr (result->error.message, error_contains) == NULL) { + if (mongoc_strcasestr (result->error.message, error_contains) == NULL) { test_set_error ( error, "expected error to contain \"%s\", but got: \"%s\"", error_contains, result->error.message); goto done; diff --git a/src/libmongoc/tests/unified/runner.c b/src/libmongoc/tests/unified/runner.c index 79591051982..6ef52c4dfaf 100644 --- a/src/libmongoc/tests/unified/runner.c +++ b/src/libmongoc/tests/unified/runner.c @@ -14,7 +14,10 @@ * limitations under the License. */ +#include "bson/bson.h" +#include "bsonutil/bson-match.h" #include "bsonutil/bson-parser.h" +#include "bsonutil/bson-val.h" #include "entity-map.h" #include "json-test.h" #include "operation.h" @@ -23,6 +26,7 @@ #include "test-libmongoc.h" #include "test-diagnostics.h" #include +#include #include "util.h" #include #include @@ -637,7 +641,7 @@ check_schema_version (test_file_t *test_file) // 1.21 is partially supported (expectedError.writeErrors and expectedError.writeConcernErrors) // 1.22 is partially supported (keyExpirationMS in client encryption options) semver_t schema_version; - semver_parse ("1.22", &schema_version); + semver_parse ("1.23", &schema_version); if (schema_version.major != test_file->schema_version.major) { goto fail; @@ -903,7 +907,7 @@ test_setup_initial_data (test_t *test, bson_error_t *error) mongoc_write_concern_t *wc = NULL; bson_t *bulk_opts = NULL; bson_t *drop_opts = bson_new (); - bson_t *create_opts = bson_new (); + bson_t *create_opts = NULL; bool ret = false; bson_iter_bson (&initial_data_iter, &collection_data); @@ -911,10 +915,15 @@ test_setup_initial_data (test_t *test, bson_error_t *error) bson_parser_utf8 (parser, "databaseName", &database_name); bson_parser_utf8 (parser, "collectionName", &collection_name); bson_parser_array (parser, "documents", &documents); + bson_parser_doc_optional (parser, "createOptions", &create_opts); if (!bson_parser_parse (parser, &collection_data, error)) { goto loopexit; } + if (create_opts == NULL) { + create_opts = bson_new (); + } + wc = mongoc_write_concern_new (); mongoc_write_concern_set_w (wc, MONGOC_WRITE_CONCERN_W_MAJORITY); bulk_opts = bson_new (); @@ -944,6 +953,7 @@ test_setup_initial_data (test_t *test, bson_error_t *error) } coll = mongoc_client_get_collection (test_runner->internal_client, database_name, collection_name); + if (!mongoc_collection_drop_with_opts (coll, drop_opts, error)) { if (error->code != 26 && (NULL == strstr (error->message, "ns not found"))) { /* This is not a "ns not found" error. Fail the test. */ @@ -953,6 +963,51 @@ test_setup_initial_data (test_t *test, bson_error_t *error) memset (error, 0, sizeof (bson_error_t)); } + // Also drop `enxcol_..esc` and `enxcol_..ecoc` in case the collection will be used for QE. + { + char *collection_name_esc = bson_strdup_printf ("enxcol_.%s.esc", collection_name); + mongoc_collection_t *coll_esc = + mongoc_client_get_collection (test_runner->internal_client, database_name, collection_name_esc); + if (!mongoc_collection_drop_with_opts (coll_esc, drop_opts, error)) { + if (error->code != 26 && (NULL == strstr (error->message, "ns not found"))) { + /* This is not a "ns not found" error. Fail the test. */ + mongoc_collection_destroy (coll_esc); + bson_free (collection_name_esc); + goto loopexit; + } + /* Clear an "ns not found" error. */ + memset (error, 0, sizeof (bson_error_t)); + } + mongoc_collection_destroy (coll_esc); + bson_free (collection_name_esc); + } + + { + char *collection_name_ecoc = bson_strdup_printf ("enxcol_.%s.ecoc", collection_name); + mongoc_collection_t *coll_ecoc = + mongoc_client_get_collection (test_runner->internal_client, database_name, collection_name_ecoc); + if (!mongoc_collection_drop_with_opts (coll_ecoc, drop_opts, error)) { + if (error->code != 26 && (NULL == strstr (error->message, "ns not found"))) { + /* This is not a "ns not found" error. Fail the test. */ + mongoc_collection_destroy (coll_ecoc); + bson_free (collection_name_ecoc); + goto loopexit; + } + /* Clear an "ns not found" error. */ + memset (error, 0, sizeof (bson_error_t)); + } + mongoc_collection_destroy (coll_ecoc); + bson_free (collection_name_ecoc); + } + + mongoc_collection_t *new_coll = NULL; + db = mongoc_client_get_database (test_runner->internal_client, database_name); + new_coll = mongoc_database_create_collection (db, collection_name, create_opts, error); + if (!new_coll) { + goto loopexit; + } + mongoc_collection_destroy (new_coll); + /* Insert documents if specified. */ if (bson_count_keys (documents) > 0) { bson_iter_t documents_iter; @@ -975,15 +1030,6 @@ test_setup_initial_data (test_t *test, bson_error_t *error) if (!mongoc_bulk_operation_execute (bulk_insert, NULL, error)) { goto loopexit; } - } else { - mongoc_collection_t *new_coll = NULL; - /* Test does not need data inserted, just create the collection. */ - db = mongoc_client_get_database (test_runner->internal_client, database_name); - new_coll = mongoc_database_create_collection (db, collection_name, create_opts, error); - if (!new_coll) { - goto loopexit; - } - mongoc_collection_destroy (new_coll); } ret = true; @@ -1329,6 +1375,24 @@ test_count_matching_events_for_client ( return true; } +// `is_keyvault_listcollections` returns true if a `listCollections` event produced by libmongoc should be ignored. +// The extra events are caused by operations on the key vault collection. Unlike other drivers, libmongoc does not +// create a separate client for key vault operations. +static bool +is_keyvault_listcollections (const bson_t *event) +{ + if (!bson_has_field (event, "commandName") || !bson_has_field (event, "databaseName")) { + return false; + } + + const char *cmdname = bson_lookup_utf8 (event, "commandName"); + const char *dbname = bson_lookup_utf8 (event, "databaseName"); + if (cmdname && 0 == strcmp (cmdname, "listCollections") && dbname && 0 == strcmp (dbname, "keyvault")) { + return true; + } + return false; +} + static bool test_check_expected_events_for_client (test_t *test, bson_t *expected_events_for_client, bson_error_t *error) { @@ -1373,6 +1437,10 @@ test_check_expected_events_for_client (test_t *test, bson_t *expected_events_for event_t *eiter; LL_FOREACH (entity->events, eiter) { + if (is_keyvault_listcollections (eiter->serialized)) { + // Ignore. + continue; + } if (event_matches_eventtype (eiter, event_type)) { actual_num_events++; } @@ -1396,8 +1464,11 @@ test_check_expected_events_for_client (test_t *test, bson_t *expected_events_for bson_iter_t iter; BSON_FOREACH (expected_events, iter) { - while (eiter && !event_matches_eventtype (eiter, event_type)) { + while (eiter && + (is_keyvault_listcollections (eiter->serialized) || !event_matches_eventtype (eiter, event_type))) { + // Skip. eiter = eiter->next; + continue; } bson_t expected_event; bson_iter_bson (&iter, &expected_event); @@ -1406,7 +1477,11 @@ test_check_expected_events_for_client (test_t *test, bson_t *expected_events_for goto done; } if (!test_check_event (test, &expected_event, eiter, error)) { - test_diagnostics_error_info ("checking for expected event: %s", tmp_json (&expected_event)); + test_diagnostics_error_info ("could not match event\n" + "\texpected: %s\n" + "\tactual: %s", + tmp_json (&expected_event), + tmp_json (eiter->serialized)); goto done; } eiter = eiter->next; diff --git a/src/libmongoc/tests/unified/util.c b/src/libmongoc/tests/unified/util.c index ff35613a7a7..f514e3c067c 100644 --- a/src/libmongoc/tests/unified/util.c +++ b/src/libmongoc/tests/unified/util.c @@ -161,3 +161,30 @@ usecs_since_epoch (void) return secs * factor + usecs; } + +const char * +mongoc_strcasestr (const char *haystack, const char *needle) +{ + if (!*needle) { + return haystack; + } + + const char *h = haystack; + while (*h) { + const char *h_start = h; + const char *n = needle; + + while (*n && *h && tolower (*h) == tolower (*n)) { + h++; + n++; + } + + if (!*n) { + return h_start; // Match found + } + + h = h_start + 1; // Move to the next starting position in haystack + } + + return NULL; // No match found +} diff --git a/src/libmongoc/tests/unified/util.h b/src/libmongoc/tests/unified/util.h index 4ea8c98a027..9ca4b8dd568 100644 --- a/src/libmongoc/tests/unified/util.h +++ b/src/libmongoc/tests/unified/util.h @@ -36,4 +36,6 @@ is_unsupported_event_type (const char *event_type); int64_t usecs_since_epoch (void); +const char * +mongoc_strcasestr (const char *haystack, const char *needle); #endif /* UNIFIED_UTIL_H */