summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPawel Terlecki <pawel.terlecki@mongodb.com>2019-03-12 16:22:02 -0400
committerPawel Terlecki <pawel.terlecki@mongodb.com>2019-03-12 16:22:02 -0400
commit704046922042664717cd60b615052d11acc0db77 (patch)
tree2931bff95b45e8bcbd63b6deb34d83914c72bc6d
parentafe082642124dbda2367cb51c3d748873df9bf7b (diff)
downloadmongo-704046922042664717cd60b615052d11acc0db77.tar.gz
SERVER-39171 Implement encrypt.bsonType validation
A new MatchExpression was added in JSONSchemaParser. FleBlob is currently used only here. We may decide to share it in the future as necessary.
-rw-r--r--src/mongo/db/matcher/SConscript1
-rw-r--r--src/mongo/db/matcher/expression.h1
-rw-r--r--src/mongo/db/matcher/expression_parser.cpp33
-rw-r--r--src/mongo/db/matcher/expression_parser.h1
-rw-r--r--src/mongo/db/matcher/expression_parser_test.cpp132
-rw-r--r--src/mongo/db/matcher/expression_type.cpp40
-rw-r--r--src/mongo/db/matcher/expression_type.h112
-rw-r--r--src/mongo/db/matcher/schema/json_schema_parser.cpp43
-rw-r--r--src/mongo/db/matcher/schema/json_schema_parser_test.cpp19
-rw-r--r--src/mongo/db/pipeline/document_source_match.cpp1
-rw-r--r--src/mongo/db/query/canonical_query_encoder.cpp3
11 files changed, 310 insertions, 76 deletions
diff --git a/src/mongo/db/matcher/SConscript b/src/mongo/db/matcher/SConscript
index 06d5c7b0549..0b081311c62 100644
--- a/src/mongo/db/matcher/SConscript
+++ b/src/mongo/db/matcher/SConscript
@@ -40,7 +40,6 @@ env.Library(
'expression_text_base.cpp',
'expression_text_noop.cpp',
'expression_tree.cpp',
- 'expression_type.cpp',
'expression_where_base.cpp',
'expression_where_noop.cpp',
'expression_with_placeholder.cpp',
diff --git a/src/mongo/db/matcher/expression.h b/src/mongo/db/matcher/expression.h
index 02db8946ef4..0fb0a2060b4 100644
--- a/src/mongo/db/matcher/expression.h
+++ b/src/mongo/db/matcher/expression.h
@@ -112,6 +112,7 @@ public:
// JSON Schema expressions.
INTERNAL_SCHEMA_ALLOWED_PROPERTIES,
INTERNAL_SCHEMA_ALL_ELEM_MATCH_FROM_INDEX,
+ INTERNAL_SCHEMA_BIN_DATA_ENCRYPTED_TYPE,
INTERNAL_SCHEMA_BIN_DATA_SUBTYPE,
INTERNAL_SCHEMA_COND,
INTERNAL_SCHEMA_EQ,
diff --git a/src/mongo/db/matcher/expression_parser.cpp b/src/mongo/db/matcher/expression_parser.cpp
index b86387bc2f6..c3cbe66f628 100644
--- a/src/mongo/db/matcher/expression_parser.cpp
+++ b/src/mongo/db/matcher/expression_parser.cpp
@@ -1412,6 +1412,33 @@ StatusWithMatchExpression parseInternalSchemaBinDataSubType(StringData name, BSO
name, static_cast<BinDataType>(valueAsInt.getValue()))};
}
+StatusWithMatchExpression parseInternalSchemaBinDataEncryptedType(StringData name, BSONElement e) {
+ if (!e.isNumber()) {
+ return Status(ErrorCodes::FailedToParse,
+ str::stream() << InternalSchemaBinDataEncryptedTypeExpression::kName
+ << " must be represented as a number");
+ }
+
+ auto valueAsInt = MatchExpressionParser::parseIntegerElementToInt(e);
+ if (!valueAsInt.isOK()) {
+ return Status(ErrorCodes::FailedToParse,
+ str::stream() << "Invalid numerical type code for "
+ << InternalSchemaBinDataEncryptedTypeExpression::kName
+ << ": "
+ << e.number());
+ }
+
+ if (!isValidBSONType(valueAsInt.getValue())) {
+ return Status(ErrorCodes::FailedToParse,
+ str::stream() << InternalSchemaBinDataEncryptedTypeExpression::kName
+ << " value must represent a valid BSON type: "
+ << valueAsInt.getValue());
+ }
+
+ return {stdx::make_unique<InternalSchemaBinDataEncryptedTypeExpression>(
+ name, static_cast<BSONType>(valueAsInt.getValue()))};
+}
+
/**
* Parses a single field in a sub expression.
* If the query is { x : { $gt : 5, $lt : 8 } },
@@ -1740,6 +1767,10 @@ StatusWithMatchExpression parseSubField(const BSONObj& context,
return {stdx::make_unique<InternalSchemaEqMatchExpression>(name, e)};
}
+ case PathAcceptingKeyword::INTERNAL_SCHEMA_BIN_DATA_ENCRYPTED_TYPE: {
+ return parseInternalSchemaBinDataEncryptedType(name, e);
+ }
+
case PathAcceptingKeyword::INTERNAL_SCHEMA_BIN_DATA_SUBTYPE: {
return parseInternalSchemaBinDataSubType(name, e);
}
@@ -1880,6 +1911,8 @@ MONGO_INITIALIZER(MatchExpressionParser)(InitializerContext* context) {
{"_internalExprEq", PathAcceptingKeyword::INTERNAL_EXPR_EQ},
{"_internalSchemaAllElemMatchFromIndex",
PathAcceptingKeyword::INTERNAL_SCHEMA_ALL_ELEM_MATCH_FROM_INDEX},
+ {"_internalSchemaBinDataEncryptedType",
+ PathAcceptingKeyword::INTERNAL_SCHEMA_BIN_DATA_ENCRYPTED_TYPE},
{"_internalSchemaBinDataSubType",
PathAcceptingKeyword::INTERNAL_SCHEMA_BIN_DATA_SUBTYPE},
{"_internalSchemaEq", PathAcceptingKeyword::INTERNAL_SCHEMA_EQ},
diff --git a/src/mongo/db/matcher/expression_parser.h b/src/mongo/db/matcher/expression_parser.h
index 7ee6cf4bac5..a5adb44cb61 100644
--- a/src/mongo/db/matcher/expression_parser.h
+++ b/src/mongo/db/matcher/expression_parser.h
@@ -62,6 +62,7 @@ enum class PathAcceptingKeyword {
GREATER_THAN_OR_EQUAL,
INTERNAL_EXPR_EQ,
INTERNAL_SCHEMA_ALL_ELEM_MATCH_FROM_INDEX,
+ INTERNAL_SCHEMA_BIN_DATA_ENCRYPTED_TYPE,
INTERNAL_SCHEMA_BIN_DATA_SUBTYPE,
INTERNAL_SCHEMA_EQ,
INTERNAL_SCHEMA_FMOD,
diff --git a/src/mongo/db/matcher/expression_parser_test.cpp b/src/mongo/db/matcher/expression_parser_test.cpp
index 35be4683fe3..0b9de433bc6 100644
--- a/src/mongo/db/matcher/expression_parser_test.cpp
+++ b/src/mongo/db/matcher/expression_parser_test.cpp
@@ -36,6 +36,7 @@
#include "mongo/db/matcher/expression.h"
#include "mongo/db/matcher/expression_always_boolean.h"
#include "mongo/db/matcher/expression_leaf.h"
+#include "mongo/db/matcher/expression_type.h"
#include "mongo/db/matcher/extensions_callback_noop.h"
#include "mongo/db/pipeline/expression_context_for_test.h"
@@ -575,10 +576,139 @@ TEST(InternalBinDataSubTypeMatchExpressionTest, InvalidSubTypeDoesNotParse) {
TEST(InternalBinDataSubTypeMatchExpressionTest, InvalidNumericalSubTypeDoesNotParse) {
boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
auto query1 = BSON("a" << BSON("$_internalSchemaBinDataSubType" << 99));
- auto query2 = BSON("a" << BSON("$_ internalSchemaBinDataSubType" << 2.1));
+ auto query2 = BSON("a" << BSON("$_internalSchemaBinDataSubType" << 2.1));
auto statusWith1 = MatchExpressionParser::parse(query1, expCtx);
auto statusWith2 = MatchExpressionParser::parse(query2, expCtx);
ASSERT_NOT_OK(statusWith1.getStatus());
ASSERT_NOT_OK(statusWith2.getStatus());
}
+
+TEST(InternalSchemaBinDataEncryptedTypeExpressionTest, BsonTypeMatches) {
+ boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
+ auto query = BSON("a" << BSON("$_internalSchemaBinDataEncryptedType" << BSONType::String));
+ auto expr = uassertStatusOK(MatchExpressionParser::parse(query, expCtx));
+
+ FleBlobHeader blob;
+ blob.fleBlobSubtype = FleBlobSubtype::Deterministic;
+ memset(blob.keyUUID, 0, sizeof(blob.keyUUID));
+ blob.originalBsonType = BSONType::String;
+
+ BSONObj matchingDoc = BSON("a" << BSONBinData(reinterpret_cast<const void*>(&blob),
+ sizeof(FleBlobHeader),
+ BinDataType::Encrypt));
+ ASSERT_TRUE(expr->matchesBSON(matchingDoc));
+}
+
+TEST(InternalSchemaBinDataEncryptedTypeExpressionTest, BsonTypeDoesNotMatch) {
+ boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
+ auto query = BSON("a" << BSON("$_internalSchemaBinDataEncryptedType" << BSONType::String));
+ auto expr = uassertStatusOK(MatchExpressionParser::parse(query, expCtx));
+
+ FleBlobHeader blob;
+ blob.fleBlobSubtype = FleBlobSubtype::Deterministic;
+ memset(blob.keyUUID, 0, sizeof(blob.keyUUID));
+ blob.originalBsonType = BSONType::NumberInt;
+
+ BSONObj notMatchingDoc = BSON("a" << BSONBinData(reinterpret_cast<const void*>(&blob),
+ sizeof(FleBlobHeader),
+ BinDataType::Encrypt));
+ ASSERT_FALSE(expr->matchesBSON(notMatchingDoc));
+}
+
+TEST(InternalSchemaBinDataEncryptedTypeExpressionTest, NonNumericalArgumentDoesNotParse) {
+ boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
+ ASSERT_EQ(MatchExpressionParser::parse(BSON("a" << BSON("$_internalSchemaBinDataEncryptedType"
+ << "bar")),
+ expCtx)
+ .getStatus(),
+ ErrorCodes::FailedToParse);
+ ASSERT_EQ(MatchExpressionParser::parse(BSON("a" << BSON("$_internalSchemaBinDataEncryptedType"
+ << "0")),
+ expCtx)
+ .getStatus(),
+ ErrorCodes::FailedToParse);
+}
+
+TEST(InternalSchemaBinDataEncryptedTypeExpressionTest, InvalidNumericalArgumentDoesNotParse) {
+ boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
+ ASSERT_EQ(MatchExpressionParser::parse(
+ BSON("a" << BSON("$_internalSchemaBinDataEncryptedType" << 0.21)), expCtx)
+ .getStatus(),
+ ErrorCodes::FailedToParse);
+ ASSERT_EQ(MatchExpressionParser::parse(
+ BSON("a" << BSON("$_internalSchemaBinDataEncryptedType" << 13.3)), expCtx)
+ .getStatus(),
+ ErrorCodes::FailedToParse);
+}
+
+TEST(InternalSchemaBinDataEncryptedTypeExpressionTest, NonBsonTypeArgumentDoesNotParse) {
+ boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
+ ASSERT_EQ(MatchExpressionParser::parse(BSON("a" << BSON("$_internalSchemaBinDataEncryptedType"
+ << (BSONType::JSTypeMax + 1))),
+ expCtx)
+ .getStatus(),
+ ErrorCodes::FailedToParse);
+}
+
+TEST(InternalSchemaBinDataEncryptedTypeExpressionTest, IntentToEncryptFleBlobDoesNotMatch) {
+ boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
+ auto query = BSON("a" << BSON("$_internalSchemaBinDataEncryptedType" << BSONType::String));
+ auto expr = uassertStatusOK(MatchExpressionParser::parse(query, expCtx));
+
+ FleBlobHeader blob;
+ blob.fleBlobSubtype = FleBlobSubtype::IntentToEncrypt;
+ memset(blob.keyUUID, 0, sizeof(blob.keyUUID));
+ blob.originalBsonType = BSONType::String;
+ BSONObj notMatch = BSON("a" << BSONBinData(reinterpret_cast<const void*>(&blob),
+ sizeof(FleBlobHeader),
+ BinDataType::Encrypt));
+
+ ASSERT_FALSE(expr->matchesBSON(notMatch));
+}
+
+TEST(InternalSchemaBinDataEncryptedTypeExpressionTest, UnknownFleBlobTypeDoesNotMatch) {
+ boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
+ auto query = BSON("a" << BSON("$_internalSchemaBinDataEncryptedType" << BSONType::String));
+ auto expr = uassertStatusOK(MatchExpressionParser::parse(query, expCtx));
+
+ FleBlobHeader blob;
+ blob.fleBlobSubtype = 6;
+ memset(blob.keyUUID, 0, sizeof(blob.keyUUID));
+ blob.originalBsonType = BSONType::String;
+ BSONObj notMatch = BSON("a" << BSONBinData(reinterpret_cast<const void*>(&blob),
+ sizeof(FleBlobHeader),
+ BinDataType::Encrypt));
+ try {
+ expr->matchesBSON(notMatch);
+ } catch (...) {
+ ASSERT_EQ(exceptionToStatus().code(), 33118);
+ }
+}
+
+TEST(InternalSchemaBinDataEncryptedTypeExpressionTest, EmptyFleBlobDoesNotMatch) {
+ boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
+ auto query = BSON("a" << BSON("$_internalSchemaBinDataEncryptedType" << BSONType::String));
+ auto expr = uassertStatusOK(MatchExpressionParser::parse(query, expCtx));
+
+ BSONObj notMatch = BSON("a" << BSONBinData(nullptr, 0, BinDataType::Encrypt));
+ ASSERT_FALSE(expr->matchesBSON(notMatch));
+}
+
+TEST(InternalSchemaBinDataEncryptedTypeExpressionTest, NonEncryptBinDataSubTypeDoesNotMatch) {
+ boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
+ auto query = BSON("a" << BSON("$_internalSchemaBinDataEncryptedType" << BSONType::String));
+ auto expr = uassertStatusOK(MatchExpressionParser::parse(query, expCtx));
+
+ BSONObj notMatch = BSON("a" << BSONBinData("\x69\xb7", 2, BinDataGeneral));
+ ASSERT_FALSE(expr->matchesBSON(notMatch));
+}
+
+TEST(InternalSchemaBinDataEncryptedTypeExpressionTest, NonBinDataValueDoesNotMatch) {
+ boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest());
+ auto query = BSON("a" << BSON("$_internalSchemaBinDataEncryptedType" << BSONType::String));
+ auto expr = uassertStatusOK(MatchExpressionParser::parse(query, expCtx));
+
+ BSONObj notMatch = BSON("a" << BSONArray());
+ ASSERT_FALSE(expr->matchesBSON(notMatch));
+}
} // namespace mongo
diff --git a/src/mongo/db/matcher/expression_type.cpp b/src/mongo/db/matcher/expression_type.cpp
deleted file mode 100644
index e620328996f..00000000000
--- a/src/mongo/db/matcher/expression_type.cpp
+++ /dev/null
@@ -1,40 +0,0 @@
-/**
- * Copyright (C) 2018-present MongoDB, Inc.
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the Server Side Public License, version 1,
- * as published by MongoDB, Inc.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * Server Side Public License for more details.
- *
- * You should have received a copy of the Server Side Public License
- * along with this program. If not, see
- * <http://www.mongodb.com/licensing/server-side-public-license>.
- *
- * As a special exception, the copyright holders give permission to link the
- * code of portions of this program with the OpenSSL library under certain
- * conditions as described in each individual source file and distribute
- * linked combinations including the program with the OpenSSL library. You
- * must comply with the Server Side Public License in all respects for
- * all of the code used other than as permitted herein. If you modify file(s)
- * with this exception, you may extend this exception to your version of the
- * file(s), but you are not obligated to do so. If you do not wish to do so,
- * delete this exception statement from your version. If you delete this
- * exception statement from all source files in the program, then also delete
- * it in the license file.
- */
-
-#include "mongo/platform/basic.h"
-
-#include "mongo/db/matcher/expression_type.h"
-
-namespace mongo {
-
-constexpr StringData InternalSchemaBinDataSubTypeExpression::kName;
-constexpr StringData InternalSchemaTypeExpression::kName;
-constexpr StringData TypeMatchExpression::kName;
-
-} // namespace mongo
diff --git a/src/mongo/db/matcher/expression_type.h b/src/mongo/db/matcher/expression_type.h
index d3e66580092..c206103693c 100644
--- a/src/mongo/db/matcher/expression_type.h
+++ b/src/mongo/db/matcher/expression_type.h
@@ -34,6 +34,20 @@
namespace mongo {
+/**
+ * Types of the encryption payload.
+ */
+enum FleBlobSubtype { IntentToEncrypt = 0, Deterministic = 1, Random = 2 };
+
+/**
+ * The structure represents how data is laid out in an encrypted payload.
+ */
+struct FleBlobHeader {
+ int8_t fleBlobSubtype;
+ int8_t keyUUID[16];
+ int8_t originalBsonType;
+};
+
template <class T>
class TypeMatchExpressionBase : public LeafMatchExpression {
public:
@@ -165,8 +179,6 @@ public:
ElementPath::NonLeafArrayBehavior::kTraverse),
_binDataSubType(binDataSubType) {}
- virtual ~InternalSchemaBinDataSubTypeExpression() = default;
-
StringData name() const {
return kName;
}
@@ -223,4 +235,100 @@ private:
BinDataType _binDataSubType;
};
+
+/**
+ * Implements matching semantics for the JSON Schema keyword encrypt.bsonType. A document
+ * matches successfully if a field is encrypted and the encrypted payload indicates the
+ * original BSON element matches the specified type.
+ */
+class InternalSchemaBinDataEncryptedTypeExpression final : public LeafMatchExpression {
+public:
+ static constexpr StringData kName = "$_internalSchemaBinDataEncryptedType"_sd;
+
+ InternalSchemaBinDataEncryptedTypeExpression(StringData path, BSONType encryptedType)
+ : LeafMatchExpression(MatchExpression::INTERNAL_SCHEMA_BIN_DATA_ENCRYPTED_TYPE,
+ path,
+ ElementPath::LeafArrayBehavior::kNoTraversal,
+ ElementPath::NonLeafArrayBehavior::kTraverse),
+ _encryptedType(encryptedType) {}
+
+ StringData name() const {
+ return kName;
+ }
+
+ bool matchesSingleElement(const BSONElement& elem,
+ MatchDetails* details = nullptr) const final {
+ if (elem.type() != BSONType::BinData)
+ return false;
+ if (elem.binDataType() != BinDataType::Encrypt)
+ return false;
+
+ int binDataLen;
+ auto binData = elem.binData(binDataLen);
+ if (!binDataLen)
+ return false;
+
+ auto fleBlobSubType = binData[0];
+ switch (fleBlobSubType) {
+ case FleBlobSubtype::IntentToEncrypt:
+ return false;
+ case FleBlobSubtype::Deterministic:
+ case FleBlobSubtype::Random: {
+ // Verify the type of the encrypted data.
+ auto fleBlob = reinterpret_cast<const FleBlobHeader*>(binData);
+ return fleBlob->originalBsonType == _encryptedType;
+ }
+ default:
+ uasserted(33118,
+ str::stream() << "unexpected subtype " << static_cast<int>(fleBlobSubType)
+ << " of encrypted binary data (0, 1 and 2 are allowed)");
+ }
+ }
+
+ std::unique_ptr<MatchExpression> shallowClone() const final {
+ auto expr =
+ stdx::make_unique<InternalSchemaBinDataEncryptedTypeExpression>(path(), _encryptedType);
+ if (getTag()) {
+ expr->setTag(getTag()->clone());
+ }
+ return std::move(expr);
+ }
+
+ void debugString(StringBuilder& debug, int level) const final {
+ _debugAddSpace(debug, level);
+ debug << path() << " " << name() << ": " << typeName(_encryptedType);
+
+ if (auto td = getTag()) {
+ debug << " ";
+ td->debugString(&debug);
+ }
+ debug << "\n";
+ }
+
+ BSONObj getSerializedRightHandSide() const final {
+ BSONObjBuilder bob;
+ bob.append(name(), _encryptedType);
+ return bob.obj();
+ }
+
+ bool equivalent(const MatchExpression* other) const final {
+ if (matchType() != other->matchType())
+ return false;
+
+ auto realOther = static_cast<const InternalSchemaBinDataEncryptedTypeExpression*>(other);
+
+ if (path() != realOther->path()) {
+ return false;
+ }
+
+ return _encryptedType == realOther->_encryptedType;
+ }
+
+private:
+ ExpressionOptimizerFunc getOptimizer() const final {
+ return [](std::unique_ptr<MatchExpression> expression) { return expression; };
+ }
+
+ BSONType _encryptedType;
+};
} // namespace mongo
diff --git a/src/mongo/db/matcher/schema/json_schema_parser.cpp b/src/mongo/db/matcher/schema/json_schema_parser.cpp
index 14e861be176..2b35d8e34b5 100644
--- a/src/mongo/db/matcher/schema/json_schema_parser.cpp
+++ b/src/mongo/db/matcher/schema/json_schema_parser.cpp
@@ -1276,32 +1276,6 @@ Status translateScalarKeywords(StringMap<BSONElement>& keywordMap,
}
/**
- * Parses the parameters to the 'encrypt' keyword. Returns an OK status if these parameters
- * are valid, and a non-OK status otherwise."
- */
-Status verifyEncryptOptions(BSONObj encryptObj) {
- const IDLParserErrorContext encryptCtxt("encrypt");
-
- // This checks the types of all the fields. Will throw on any parsing error.
- EncryptionInfo encryptInfo;
- try {
- encryptInfo = EncryptionInfo::parse(encryptCtxt, encryptObj);
- } catch (...) {
- return exceptionToStatus();
- }
- auto typePointer = encryptInfo.getBsonType();
- if (typePointer) {
- auto it = kTypeAliasMap.find(typePointer.get());
- if (it == kTypeAliasMap.end()) {
- return {ErrorCodes::FailedToParse,
- "Invalid BSON type found in encrypt object: " + typePointer.get()};
- }
- }
-
- return Status::OK();
-}
-
-/**
* Parses JSON Schema encrypt keyword in 'keywordMap' and adds it to 'andExpr'. Returns a
* non-OK status if an error occurs during parsing.
*/
@@ -1350,12 +1324,19 @@ Status translateEncryptionKeywords(StringMap<BSONElement>& keywordMap,
<< "' must be an object "};
}
- auto encryptStatus = verifyEncryptOptions(encryptElt.embeddedObject());
- if (!encryptStatus.isOK()) {
- return encryptStatus;
- }
+ try {
+ // This checks the types of all the fields. Will throw on any parsing error.
+ const IDLParserErrorContext encryptCtxt("encrypt");
+ auto encryptInfo = EncryptionInfo::parse(encryptCtxt, encryptElt.embeddedObject());
- andExpr->add(new InternalSchemaBinDataSubTypeExpression(path, BinDataType::Encrypt));
+ andExpr->add(new InternalSchemaBinDataSubTypeExpression(path, BinDataType::Encrypt));
+
+ if (auto typeOptional = encryptInfo.getBsonType())
+ andExpr->add(new InternalSchemaBinDataEncryptedTypeExpression(
+ path, typeFromName(typeOptional.get())));
+ } catch (const AssertionException&) {
+ return exceptionToStatus();
+ }
}
return Status::OK();
diff --git a/src/mongo/db/matcher/schema/json_schema_parser_test.cpp b/src/mongo/db/matcher/schema/json_schema_parser_test.cpp
index 0040d28db38..6483b6f195e 100644
--- a/src/mongo/db/matcher/schema/json_schema_parser_test.cpp
+++ b/src/mongo/db/matcher/schema/json_schema_parser_test.cpp
@@ -1871,6 +1871,23 @@ TEST(JSONSchemaParserTest, EncryptTranslatesCorrectly) {
})"));
}
+TEST(JSONSchemaParserTest, EncryptWithBsonTypeTranslatesCorrectly) {
+ BSONObj schema = fromjson("{properties: {foo: {encrypt: {bsonType: \"string\"}}}}");
+ auto result = JSONSchemaParser::parse(schema);
+ ASSERT_OK(result.getStatus());
+ auto optimizedResult = MatchExpression::optimize(std::move(result.getValue()));
+ ASSERT_SERIALIZES_TO(optimizedResult, fromjson(R"(
+ {
+ $or:
+ [{foo: {$not: {$exists: true}}}, {
+ $and:
+ [ {foo: {$_internalSchemaBinDataSubType: 6}},
+ {foo: {$_internalSchemaBinDataEncryptedType: 2}},
+ {foo: {$_internalSchemaType: [5]}}]
+ }]
+ })"));
+}
+
TEST(JSONSchemaParserTest, TopLevelEncryptTranslatesCorrectly) {
BSONObj schema = fromjson("{encrypt: {}}");
auto result = JSONSchemaParser::parse(schema);
@@ -2004,7 +2021,7 @@ TEST(JSONSchemaParserTest, FailsToParseWithBadBSONType) {
auto schema = BSON("properties" << BSON("foo" << BSON("encrypt" << BSON("bsonType"
<< "Stringx"))));
auto result = JSONSchemaParser::parse(schema);
- ASSERT_EQ(result.getStatus().code(), ErrorCodes::FailedToParse);
+ ASSERT_EQ(result.getStatus().code(), ErrorCodes::BadValue);
schema = BSON("properties" << BSON("foo" << BSON("encrypt" << BSON("bsonType" << 1))));
result = JSONSchemaParser::parse(schema);
diff --git a/src/mongo/db/pipeline/document_source_match.cpp b/src/mongo/db/pipeline/document_source_match.cpp
index 11aa84c71a4..7cb3bcec620 100644
--- a/src/mongo/db/pipeline/document_source_match.cpp
+++ b/src/mongo/db/pipeline/document_source_match.cpp
@@ -271,6 +271,7 @@ Document redactSafePortionDollarOps(BSONObj expr) {
case PathAcceptingKeyword::GEO_NEAR:
case PathAcceptingKeyword::INTERNAL_EXPR_EQ:
case PathAcceptingKeyword::INTERNAL_SCHEMA_ALL_ELEM_MATCH_FROM_INDEX:
+ case PathAcceptingKeyword::INTERNAL_SCHEMA_BIN_DATA_ENCRYPTED_TYPE:
case PathAcceptingKeyword::INTERNAL_SCHEMA_BIN_DATA_SUBTYPE:
case PathAcceptingKeyword::INTERNAL_SCHEMA_EQ:
case PathAcceptingKeyword::INTERNAL_SCHEMA_FMOD:
diff --git a/src/mongo/db/query/canonical_query_encoder.cpp b/src/mongo/db/query/canonical_query_encoder.cpp
index 27f8bc24590..91982bff80a 100644
--- a/src/mongo/db/query/canonical_query_encoder.cpp
+++ b/src/mongo/db/query/canonical_query_encoder.cpp
@@ -174,6 +174,9 @@ const char* encodeMatchType(MatchExpression::MatchType mt) {
case MatchExpression::INTERNAL_SCHEMA_ALLOWED_PROPERTIES:
return "internalSchemaAllowedProperties";
+ case MatchExpression::INTERNAL_SCHEMA_BIN_DATA_ENCRYPTED_TYPE:
+ return "internalSchemaBinDataEncryptedType";
+
case MatchExpression::INTERNAL_SCHEMA_BIN_DATA_SUBTYPE:
return "internalSchemaBinDataSubType";