summaryrefslogtreecommitdiff
path: root/src/mongo/db/matcher
diff options
context:
space:
mode:
authorvrachev <vlad.rachev@mongodb.com>2019-01-25 13:52:07 -0500
committervrachev <vlad.rachev@mongodb.com>2019-01-25 15:55:25 -0500
commitffe35fbcb46d344fef138fe82dfbedc070205ab8 (patch)
tree89a37f65a6a0dad6fd8ea161d9a30dee9e89d758 /src/mongo/db/matcher
parent69fe02ad41a2a14ef0b8a11406a72af1748aae3d (diff)
downloadmongo-ffe35fbcb46d344fef138fe82dfbedc070205ab8.tar.gz
SERVER-38900 Implement JSON Schema parsing for 'encrypt'
Diffstat (limited to 'src/mongo/db/matcher')
-rw-r--r--src/mongo/db/matcher/schema/json_schema_parser.cpp54
-rw-r--r--src/mongo/db/matcher/schema/json_schema_parser_test.cpp110
2 files changed, 164 insertions, 0 deletions
diff --git a/src/mongo/db/matcher/schema/json_schema_parser.cpp b/src/mongo/db/matcher/schema/json_schema_parser.cpp
index 029e0985790..53ae23b6379 100644
--- a/src/mongo/db/matcher/schema/json_schema_parser.cpp
+++ b/src/mongo/db/matcher/schema/json_schema_parser.cpp
@@ -100,6 +100,7 @@ constexpr StringData kSchemaUniqueItemsKeyword = "uniqueItems"_sd;
// MongoDB-specific (non-standard) JSON Schema keyword constants.
constexpr StringData kSchemaBsonTypeKeyword = "bsonType"_sd;
+constexpr StringData kSchemaEncryptKeyword = "encrypt"_sd;
// Explicitly unsupported JSON Schema keywords.
const std::set<StringData> unsupportedKeywords{
@@ -1329,6 +1330,30 @@ Status translateScalarKeywords(StringMap<BSONElement>& keywordMap,
}
/**
+ * Parses JSON Schema encrypt keyword in 'keywordMap' and adds it to 'andExpr'. Returns a
+ * non-OK status if an error occurs during parsing.
+ */
+Status translateEncryptionKeywords(StringMap<BSONElement>& keywordMap,
+ StringData path,
+ InternalSchemaTypeExpression* typeExpr,
+ AndMatchExpression* andExpr) {
+ if (auto encryptElt = keywordMap[kSchemaEncryptKeyword]) {
+ if (encryptElt.type() != BSONType::Object) {
+ return {ErrorCodes::FailedToParse,
+ str::stream() << "$jsonSchema keyword '" << kSchemaEncryptKeyword
+ << "' must be an object "};
+ } else if (!encryptElt.embeddedObject().isEmpty()) {
+ return {ErrorCodes::FailedToParse,
+ str::stream() << "$jsonSchema keyword '" << kSchemaEncryptKeyword
+ << "' must be an empty object "};
+ }
+ andExpr->add(new InternalSchemaBinDataSubTypeExpression(path, BinDataType::Encrypt));
+ }
+
+ return Status::OK();
+}
+
+/**
* Validates that the following metadata keywords have the correct type:
* - description
* - title
@@ -1363,6 +1388,7 @@ StatusWithMatchExpression _parse(StringData path, BSONObj schema, bool ignoreUnk
{std::string(kSchemaBsonTypeKeyword), {}},
{std::string(kSchemaDependenciesKeyword), {}},
{std::string(kSchemaDescriptionKeyword), {}},
+ {std::string(kSchemaEncryptKeyword), {}},
{std::string(kSchemaEnumKeyword), {}},
{std::string(kSchemaExclusiveMaximumKeyword), {}},
{std::string(kSchemaExclusiveMinimumKeyword), {}},
@@ -1418,6 +1444,7 @@ StatusWithMatchExpression _parse(StringData path, BSONObj schema, bool ignoreUnk
auto typeElem = keywordMap[kSchemaTypeKeyword];
auto bsonTypeElem = keywordMap[kSchemaBsonTypeKeyword];
+ auto encryptElem = keywordMap[kSchemaEncryptKeyword];
if (typeElem && bsonTypeElem) {
return Status(ErrorCodes::FailedToParse,
str::stream() << "Cannot specify both $jsonSchema keywords '"
@@ -1425,6 +1452,22 @@ StatusWithMatchExpression _parse(StringData path, BSONObj schema, bool ignoreUnk
<< "' and '"
<< kSchemaBsonTypeKeyword
<< "'");
+ } else if (typeElem && encryptElem) {
+ return Status(ErrorCodes::FailedToParse,
+ str::stream() << "$jsonSchema keyword '" << kSchemaEncryptKeyword
+ << "' cannot be used in conjunction with '"
+ << kSchemaTypeKeyword
+ << "', '"
+ << kSchemaEncryptKeyword
+ << "' implies type 'bsonType::BinData'");
+ } else if (bsonTypeElem && encryptElem) {
+ return Status(ErrorCodes::FailedToParse,
+ str::stream() << "$jsonSchema keyword '" << kSchemaEncryptKeyword
+ << "' cannot be used in conjunction with '"
+ << kSchemaBsonTypeKeyword
+ << "', '"
+ << kSchemaEncryptKeyword
+ << "' implies type 'bsonType::BinData'");
}
std::unique_ptr<InternalSchemaTypeExpression> typeExpr;
@@ -1442,6 +1485,11 @@ StatusWithMatchExpression _parse(StringData path, BSONObj schema, bool ignoreUnk
return parseBsonTypeResult.getStatus();
}
typeExpr = std::move(parseBsonTypeResult.getValue());
+ } else if (encryptElem) {
+ // The presence of the encrypt keyword implies the restriction that the field must be
+ // of type BinData.
+ typeExpr = stdx::make_unique<InternalSchemaTypeExpression>(
+ path, MatcherTypeSet(BSONType::BinData));
}
auto andExpr = stdx::make_unique<AndMatchExpression>();
@@ -1458,6 +1506,12 @@ StatusWithMatchExpression _parse(StringData path, BSONObj schema, bool ignoreUnk
return translationStatus;
}
+ translationStatus =
+ translateEncryptionKeywords(keywordMap, path, typeExpr.get(), andExpr.get());
+ if (!translationStatus.isOK()) {
+ return translationStatus;
+ }
+
translationStatus = translateObjectKeywords(
keywordMap, path, typeExpr.get(), andExpr.get(), ignoreUnknownKeywords);
if (!translationStatus.isOK()) {
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 8528d88bce1..0c7fbb52f59 100644
--- a/src/mongo/db/matcher/schema/json_schema_parser_test.cpp
+++ b/src/mongo/db/matcher/schema/json_schema_parser_test.cpp
@@ -2087,5 +2087,115 @@ TEST(JSONSchemaParserTest, TopLevelEnumWithZeroObjectsTranslatesCorrectly) {
ASSERT_SERIALIZES_TO(optimizedResult, fromjson("{$alwaysFalse: 1}"));
}
+TEST(JSONSchemaParserTest, EncryptTranslatesCorrectly) {
+ BSONObj schema = fromjson("{properties: {foo: {encrypt: {}}}}");
+ auto result = JSONSchemaParser::parse(schema);
+ ASSERT_OK(result.getStatus());
+ auto optimizedResult = MatchExpression::optimize(std::move(result.getValue()));
+ ASSERT_SERIALIZES_TO(optimizedResult, fromjson(R"(
+ {
+ $or: [
+ {$nor: [{foo: {$exists: true}}]},
+ {$and: [
+ {foo: {$_internalSchemaBinDataSubType: 6}},
+ {foo: {$_internalSchemaType: [5]}}
+ ]
+ }
+ ]
+ }
+ })"));
+}
+
+TEST(JSONSchemaParserTest, TopLevelEncryptTranslatesCorrectly) {
+ BSONObj schema = fromjson("{encrypt: {}}");
+ auto result = JSONSchemaParser::parse(schema);
+ ASSERT_OK(result.getStatus());
+ auto optimizedResult = MatchExpression::optimize(std::move(result.getValue()));
+ ASSERT_SERIALIZES_TO(optimizedResult, BSON(AlwaysFalseMatchExpression::kName << 1));
+}
+
+TEST(JSONSchemaParserTest, NestedEncryptTranslatesCorrectly) {
+ BSONObj schema =
+ fromjson("{properties: {a: {type: 'object', properties: {b: {encrypt: {}}}}}}}");
+ auto result = JSONSchemaParser::parse(schema);
+ ASSERT_OK(result.getStatus());
+ auto optimizedResult = MatchExpression::optimize(std::move(result.getValue()));
+ ASSERT_SERIALIZES_TO(optimizedResult, fromjson(R"(
+ {
+ $or: [
+ {$nor: [{a: {$exists: true}}]},
+ {$and: [
+ {a: {$_internalSchemaObjectMatch: {$or: [
+ {$nor: [{b: {$exists: true}}]},
+ {$and: [
+ {b: {$_internalSchemaBinDataSubType: 6}},
+ {b: {$_internalSchemaType: [5]}}
+ ]
+ }
+ ]
+ }
+ }
+ },
+ {a: {$_internalSchemaType: [3]}}
+ ]
+ }
+ ]
+ })"));
+}
+
+TEST(JSONSchemaParserTest, NestedEncryptInArrayTranslatesCorrectly) {
+ BSONObj schema = fromjson("{properties: {a: {type: 'array', items: {encrypt: {}}}}}");
+ auto result = JSONSchemaParser::parse(schema);
+ ASSERT_OK(result.getStatus());
+ auto optimizedResult = MatchExpression::optimize(std::move(result.getValue()));
+ ASSERT_SERIALIZES_TO(optimizedResult, fromjson(R"(
+ {
+ $or: [
+ {$nor: [{a: {$exists: true}}]},
+ {
+ $and: [
+ {
+ a: {
+ $_internalSchemaAllElemMatchFromIndex: [
+ 0,
+ {$and: [
+ {i: {$_internalSchemaBinDataSubType: 6}},
+ {i: {$_internalSchemaType: [5]}}]
+ }
+ ]
+ }
+ },
+ {a: {$_internalSchemaType: [4]}}
+ ]
+ }
+ ]
+ })"));
+}
+
+TEST(JSONSchemaParserTest, FailsToParseIfBothEncryptAndTypeArePresent) {
+ BSONObj schema = fromjson("{encrypt: {}, type: 'object'}");
+ auto result = JSONSchemaParser::parse(schema);
+ ASSERT_EQ(result.getStatus(), ErrorCodes::FailedToParse);
+}
+
+TEST(JSONSchemaParserTest, FailsToParseIfBothEncryptAndBSONTypeArePresent) {
+ BSONObj schema = fromjson("{encrypt: {}, bsonType: 'binData'}");
+ auto result = JSONSchemaParser::parse(schema);
+ ASSERT_EQ(result.getStatus(), ErrorCodes::FailedToParse);
+}
+
+TEST(JSONSchemaParserTest, FailsToParseIfEncryptValueIsNotObject) {
+ BSONObj schema = fromjson("{properties: {foo: {encrypt: 12}}}");
+ auto result = JSONSchemaParser::parse(schema);
+ ASSERT_EQ(result.getStatus(), ErrorCodes::FailedToParse);
+}
+
+TEST(JSONSchemaParserTest, FailsToParseIfEncryptValueIsNotEmptyObject) {
+ BSONObj schema = fromjson("{properties: {foo: {encrypt: {foo: 12}}}}");
+ auto result = JSONSchemaParser::parse(schema);
+ ASSERT_EQ(result.getStatus(), ErrorCodes::FailedToParse);
+}
+
+
} // namespace
} // namespace mongo