diff options
author | vrachev <vlad.rachev@mongodb.com> | 2019-01-25 13:52:07 -0500 |
---|---|---|
committer | vrachev <vlad.rachev@mongodb.com> | 2019-01-25 15:55:25 -0500 |
commit | ffe35fbcb46d344fef138fe82dfbedc070205ab8 (patch) | |
tree | 89a37f65a6a0dad6fd8ea161d9a30dee9e89d758 /src/mongo/db/matcher | |
parent | 69fe02ad41a2a14ef0b8a11406a72af1748aae3d (diff) | |
download | mongo-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.cpp | 54 | ||||
-rw-r--r-- | src/mongo/db/matcher/schema/json_schema_parser_test.cpp | 110 |
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 |