summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNick Zolnierz <nicholas.zolnierz@mongodb.com>2020-04-09 10:45:06 -0400
committerEvergreen Agent <no-reply@evergreen.mongodb.com>2020-04-30 20:34:39 +0000
commit00a81e7a01f1ab0450df115282b452bceac91cf7 (patch)
tree72df1fa9f7769e76fbe00b84e8e0b8ff159675fa
parent0832b7f863ea766d16c76f94ed9cafdf489d2b54 (diff)
downloadmongo-00a81e7a01f1ab0450df115282b452bceac91cf7.tar.gz
SERVER-45514 Reject document validators with encryption-related keywords if the validationAction is "warn" or validationLevel is "moderate"
(cherry picked from commit 3032eb8c2a10163bf727767efe2b73b8d60c9ecb)
-rw-r--r--jstests/core/doc_validation_encrypt_keywords.js98
-rw-r--r--src/mongo/db/catalog/coll_mod.cpp110
-rw-r--r--src/mongo/db/catalog/collection.h2
-rw-r--r--src/mongo/db/catalog/collection_impl.cpp67
-rw-r--r--src/mongo/db/catalog/collection_impl.h2
-rw-r--r--src/mongo/db/catalog/collection_mock.h2
-rw-r--r--src/mongo/db/catalog/database_impl.cpp13
-rw-r--r--src/mongo/db/matcher/expression_parser.cpp2
-rw-r--r--src/mongo/db/matcher/expression_parser.h3
-rw-r--r--src/mongo/db/matcher/schema/json_schema_parser.cpp135
-rw-r--r--src/mongo/db/matcher/schema/json_schema_parser.h9
-rw-r--r--src/mongo/db/matcher/schema/object_keywords_test.cpp33
-rw-r--r--src/mongo/db/pipeline/pipeline.h4
13 files changed, 347 insertions, 133 deletions
diff --git a/jstests/core/doc_validation_encrypt_keywords.js b/jstests/core/doc_validation_encrypt_keywords.js
new file mode 100644
index 00000000000..1db56a082ae
--- /dev/null
+++ b/jstests/core/doc_validation_encrypt_keywords.js
@@ -0,0 +1,98 @@
+// Verify encryption-related keywords are only allowed in document validators if the action is
+// 'error' and validation level is 'strict'.
+//
+// Cannot implicitly shard accessed collections because of collection existing when none
+// expected.
+// @tags: [assumes_no_implicit_collection_creation_after_drop, requires_non_retryable_commands]
+(function() {
+"use strict";
+
+var collName = "doc_validation_encrypt_keywords";
+var coll = db[collName];
+coll.drop();
+
+const encryptSchema = {
+ $jsonSchema: {properties: {_id: {encrypt: {}}}}
+};
+const nestedEncryptSchema = {
+ $jsonSchema: {properties: {user: {type: "object", properties: {ssn: {encrypt: {}}}}}}
+};
+const encryptMetadataSchema = {
+ $jsonSchema: {encryptMetadata: {algorithm: "AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic"}}
+};
+
+assert.commandFailedWithCode(
+ db.createCollection(collName, {validator: encryptSchema, validationAction: "warn"}),
+ ErrorCodes.QueryFeatureNotAllowed);
+assert.commandFailedWithCode(
+ db.createCollection(collName, {validator: nestedEncryptSchema, validationAction: "warn"}),
+ ErrorCodes.QueryFeatureNotAllowed);
+assert.commandFailedWithCode(
+ db.createCollection(collName, {validator: encryptMetadataSchema, validationAction: "warn"}),
+ ErrorCodes.QueryFeatureNotAllowed);
+
+assert.commandFailedWithCode(
+ db.createCollection(collName, {validator: encryptSchema, validationLevel: "moderate"}),
+ ErrorCodes.QueryFeatureNotAllowed);
+assert.commandFailedWithCode(
+ db.createCollection(collName, {validator: nestedEncryptSchema, validationLevel: "moderate"}),
+ ErrorCodes.QueryFeatureNotAllowed);
+assert.commandFailedWithCode(
+ db.createCollection(collName, {validator: encryptMetadataSchema, validationLevel: "moderate"}),
+ ErrorCodes.QueryFeatureNotAllowed);
+
+// Create the collection with a valid document validator and action 'warn'.
+assert.commandWorked(db.createCollection(
+ collName, {validator: {$jsonSchema: {required: ["_id"]}}, validationAction: "warn"}));
+
+// Verify that we can't collMod the validator to include an encryption-related keyword.
+assert.commandFailedWithCode(db.runCommand({collMod: collName, validator: encryptSchema}),
+ ErrorCodes.QueryFeatureNotAllowed);
+assert.commandFailedWithCode(db.runCommand({collMod: collName, validator: nestedEncryptSchema}),
+ ErrorCodes.QueryFeatureNotAllowed);
+assert.commandFailedWithCode(db.runCommand({collMod: collName, validator: encryptMetadataSchema}),
+ ErrorCodes.QueryFeatureNotAllowed);
+coll.drop();
+
+// Create the collection with an encrypted validator and action 'error'.
+assert.commandWorked(
+ db.createCollection(collName, {validator: encryptSchema, validationAction: "error"}));
+
+// Verify that we can't collMod the validation action to 'warn' since the schema contains an
+// encryption-related keyword.
+assert.commandFailedWithCode(db.runCommand({collMod: collName, validationAction: "warn"}),
+ ErrorCodes.QueryFeatureNotAllowed);
+
+// Verify that we can't collMod the validation level to 'moderate' since the schema contains an
+// encryption-related keyword.
+assert.commandFailedWithCode(db.runCommand({collMod: collName, validationLevel: "moderate"}),
+ ErrorCodes.QueryFeatureNotAllowed);
+coll.drop();
+
+// Create the collection without a document validator.
+assert.commandWorked(db.createCollection(collName));
+
+// Verify that we can't collMod with an encrypted validator and validation action 'warn' or level
+// 'moderate'.
+assert.commandFailedWithCode(
+ db.runCommand({collMod: collName, validator: encryptSchema, validationAction: "warn"}),
+ ErrorCodes.QueryFeatureNotAllowed);
+assert.commandFailedWithCode(
+ db.runCommand({collMod: collName, validator: nestedEncryptSchema, validationAction: "warn"}),
+ ErrorCodes.QueryFeatureNotAllowed);
+assert.commandFailedWithCode(
+ db.runCommand({collMod: collName, validator: encryptMetadataSchema, validationAction: "warn"}),
+ ErrorCodes.QueryFeatureNotAllowed);
+
+assert.commandFailedWithCode(
+ db.runCommand({collMod: collName, validator: encryptSchema, validationLevel: "moderate"}),
+ ErrorCodes.QueryFeatureNotAllowed);
+assert.commandFailedWithCode(
+ db.runCommand({collMod: collName, validator: nestedEncryptSchema, validationLevel: "moderate"}),
+ ErrorCodes.QueryFeatureNotAllowed);
+assert.commandFailedWithCode(
+ db.runCommand(
+ {collMod: collName, validator: encryptMetadataSchema, validationModerate: "moderate"}),
+ ErrorCodes.QueryFeatureNotAllowed);
+coll.drop();
+})();
diff --git a/src/mongo/db/catalog/coll_mod.cpp b/src/mongo/db/catalog/coll_mod.cpp
index 6f2f5dcfafd..7b0b6091338 100644
--- a/src/mongo/db/catalog/coll_mod.cpp
+++ b/src/mongo/db/catalog/coll_mod.cpp
@@ -72,9 +72,9 @@ struct CollModRequest {
BSONElement indexHidden = {};
BSONElement viewPipeLine = {};
std::string viewOn = {};
- BSONElement collValidator = {};
- std::string collValidationAction = {};
- std::string collValidationLevel = {};
+ boost::optional<Collection::Validator> collValidator;
+ boost::optional<std::string> collValidationAction;
+ boost::optional<std::string> collValidationLevel;
bool recordPreImages = false;
};
@@ -191,15 +191,13 @@ StatusWith<CollModRequest> parseCollModRequest(OperationContext* opCtx,
ServerGlobalParams::FeatureCompatibility::Version::kFullyUpgradedTo44) {
maxFeatureCompatibilityVersion = currentFCV;
}
- auto statusW = coll->parseValidator(opCtx,
- e.Obj(),
- MatchExpressionParser::kDefaultSpecialFeatures,
- maxFeatureCompatibilityVersion);
- if (!statusW.isOK()) {
- return statusW.getStatus();
+ cmr.collValidator = coll->parseValidator(opCtx,
+ e.Obj().getOwned(),
+ MatchExpressionParser::kDefaultSpecialFeatures,
+ maxFeatureCompatibilityVersion);
+ if (!cmr.collValidator->isOK()) {
+ return cmr.collValidator->getStatus();
}
-
- cmr.collValidator = e;
} else if (fieldName == "validationLevel" && !isView) {
auto status = coll->parseValidationLevel(e.String());
if (!status.isOK())
@@ -345,19 +343,22 @@ Status _collModInternal(OperationContext* opCtx,
auto oplogEntryObj = oplogEntryBuilder.obj();
// Save both states of the CollModRequest to allow writeConflictRetries.
- const CollModRequest cmrOld = statusW.getValue();
- CollModRequest cmrNew = statusW.getValue();
-
- if (!cmrOld.indexHidden.eoo()) {
-
+ CollModRequest cmrNew = std::move(statusW.getValue());
+ auto viewPipeline = cmrNew.viewPipeLine;
+ auto viewOn = cmrNew.viewOn;
+ auto indexExpireAfterSeconds = cmrNew.indexExpireAfterSeconds;
+ auto indexHidden = cmrNew.indexHidden;
+ auto idx = cmrNew.idx;
+
+ if (indexHidden) {
if (serverGlobalParams.featureCompatibility.getVersion() <
ServerGlobalParams::FeatureCompatibility::Version::kFullyUpgradedTo44 &&
- cmrOld.indexHidden.booleanSafe()) {
+ indexHidden.booleanSafe()) {
return Status(ErrorCodes::BadValue, "Hidden indexes can only be created with FCV 4.4");
}
if (coll->ns().isSystem())
return Status(ErrorCodes::BadValue, "Can't hide index on system collection");
- if (cmrOld.idx->isIdIndex())
+ if (idx->isIdIndex())
return Status(ErrorCodes::BadValue, "can't hide _id index");
}
@@ -367,11 +368,11 @@ Status _collModInternal(OperationContext* opCtx,
// Handle collMod on a view and return early. The View Catalog handles the creation of oplog
// entries for modifications on a view.
if (view) {
- if (!cmrOld.viewPipeLine.eoo())
- view->setPipeline(cmrOld.viewPipeLine);
+ if (viewPipeline)
+ view->setPipeline(viewPipeline);
- if (!cmrOld.viewOn.empty())
- view->setViewOn(NamespaceString(dbName, cmrOld.viewOn));
+ if (!viewOn.empty())
+ view->setViewOn(NamespaceString(dbName, viewOn));
ViewCatalog* catalog = ViewCatalog::get(db);
@@ -401,53 +402,51 @@ Status _collModInternal(OperationContext* opCtx,
// Handle collMod operation type appropriately.
- if (!cmrOld.indexExpireAfterSeconds.eoo() || !cmrOld.indexHidden.eoo()) {
+ if (indexExpireAfterSeconds || indexHidden) {
BSONElement newExpireSecs = {};
BSONElement oldExpireSecs = {};
BSONElement newHidden = {};
BSONElement oldHidden = {};
+
// TTL Index
- if (!cmrOld.indexExpireAfterSeconds.eoo()) {
- newExpireSecs = cmrOld.indexExpireAfterSeconds;
- oldExpireSecs = cmrOld.idx->infoObj().getField("expireAfterSeconds");
+ if (indexExpireAfterSeconds) {
+ newExpireSecs = indexExpireAfterSeconds;
+ oldExpireSecs = idx->infoObj().getField("expireAfterSeconds");
if (SimpleBSONElementComparator::kInstance.evaluate(oldExpireSecs !=
newExpireSecs)) {
// Change the value of "expireAfterSeconds" on disk.
DurableCatalog::get(opCtx)->updateTTLSetting(opCtx,
coll->getCatalogId(),
- cmrOld.idx->indexName(),
+ idx->indexName(),
newExpireSecs.safeNumberLong());
}
}
// User wants to hide or unhide index.
- if (!cmrOld.indexHidden.eoo()) {
- newHidden = cmrOld.indexHidden;
- oldHidden = cmrOld.idx->infoObj().getField("hidden");
+ if (indexHidden) {
+ newHidden = indexHidden;
+ oldHidden = idx->infoObj().getField("hidden");
// Make sure when we set 'hidden' to false, we can remove the hidden field from
// catalog.
if (SimpleBSONElementComparator::kInstance.evaluate(oldHidden != newHidden)) {
- DurableCatalog::get(opCtx)->updateHiddenSetting(opCtx,
- coll->getCatalogId(),
- cmrOld.idx->indexName(),
- newHidden.booleanSafe());
+ DurableCatalog::get(opCtx)->updateHiddenSetting(
+ opCtx, coll->getCatalogId(), idx->indexName(), newHidden.booleanSafe());
}
}
-
- indexCollModInfo = IndexCollModInfo{
- cmrOld.indexExpireAfterSeconds.eoo() ? boost::optional<Seconds>()
- : Seconds(newExpireSecs.safeNumberLong()),
- cmrOld.indexExpireAfterSeconds.eoo() ? boost::optional<Seconds>()
- : Seconds(oldExpireSecs.safeNumberLong()),
- cmrOld.indexHidden.eoo() ? boost::optional<bool>() : newHidden.booleanSafe(),
- cmrOld.indexHidden.eoo() ? boost::optional<bool>() : oldHidden.booleanSafe(),
- cmrNew.idx->indexName()};
+ indexCollModInfo =
+ IndexCollModInfo{!indexExpireAfterSeconds ? boost::optional<Seconds>()
+ : Seconds(newExpireSecs.safeNumberLong()),
+ !indexExpireAfterSeconds ? boost::optional<Seconds>()
+ : Seconds(oldExpireSecs.safeNumberLong()),
+ !indexHidden ? boost::optional<bool>() : newHidden.booleanSafe(),
+ !indexHidden ? boost::optional<bool>() : oldHidden.booleanSafe(),
+ cmrNew.idx->indexName()};
// Notify the index catalog that the definition of this index changed. This will
- // invalidate the idx pointer in cmrOld. On rollback of this WUOW, the idx pointer
- // in cmrNew will be invalidated and the idx pointer in cmrOld will be valid again.
- cmrNew.idx = coll->getIndexCatalog()->refreshEntry(opCtx, cmrOld.idx);
+ // invalidate the local idx pointer. On rollback of this WUOW, the idx pointer in
+ // cmrNew will be invalidated and the local var idx pointer will be valid again.
+ cmrNew.idx = coll->getIndexCatalog()->refreshEntry(opCtx, idx);
opCtx->recoveryUnit()->registerChange(std::make_unique<CollModResultChange>(
oldExpireSecs, newExpireSecs, oldHidden, newHidden, result));
@@ -457,13 +456,17 @@ Status _collModInternal(OperationContext* opCtx,
}
}
- // The Validator, ValidationAction and ValidationLevel are already parsed and must be OK.
- if (!cmrNew.collValidator.eoo())
- invariant(coll->setValidator(opCtx, cmrNew.collValidator.Obj()));
- if (!cmrNew.collValidationAction.empty())
- invariant(coll->setValidationAction(opCtx, cmrNew.collValidationAction));
- if (!cmrNew.collValidationLevel.empty())
- invariant(coll->setValidationLevel(opCtx, cmrNew.collValidationLevel));
+ if (cmrNew.collValidator) {
+ coll->setValidator(opCtx, std::move(*cmrNew.collValidator));
+ }
+ if (cmrNew.collValidationAction)
+ uassertStatusOKWithContext(
+ coll->setValidationAction(opCtx, *cmrNew.collValidationAction),
+ "Failed to set validationAction");
+ if (cmrNew.collValidationLevel) {
+ uassertStatusOKWithContext(coll->setValidationLevel(opCtx, *cmrNew.collValidationLevel),
+ "Failed to set validationLevel");
+ }
if (cmrNew.recordPreImages != oldCollOptions.recordPreImages) {
coll->setRecordPreImages(opCtx, cmrNew.recordPreImages);
@@ -471,7 +474,6 @@ Status _collModInternal(OperationContext* opCtx,
// Only observe non-view collMods, as view operations are observed as operations on the
// system.views collection.
-
auto* const opObserver = opCtx->getServiceContext()->getOpObserver();
opObserver->onCollMod(
opCtx, nss, coll->uuid(), oplogEntryObj, oldCollOptions, indexCollModInfo);
diff --git a/src/mongo/db/catalog/collection.h b/src/mongo/db/catalog/collection.h
index 86deaf95b0a..eb33fe95582 100644
--- a/src/mongo/db/catalog/collection.h
+++ b/src/mongo/db/catalog/collection.h
@@ -417,7 +417,7 @@ public:
* An empty validator removes all validation.
* Requires an exclusive lock on the collection.
*/
- virtual Status setValidator(OperationContext* const opCtx, const BSONObj validator) = 0;
+ virtual void setValidator(OperationContext* const opCtx, Validator validator) = 0;
virtual Status setValidationLevel(OperationContext* const opCtx, const StringData newLevel) = 0;
virtual Status setValidationAction(OperationContext* const opCtx,
diff --git a/src/mongo/db/catalog/collection_impl.cpp b/src/mongo/db/catalog/collection_impl.cpp
index 93c83701c6c..f19a49252b7 100644
--- a/src/mongo/db/catalog/collection_impl.cpp
+++ b/src/mongo/db/catalog/collection_impl.cpp
@@ -54,6 +54,7 @@
#include "mongo/db/index/index_access_method.h"
#include "mongo/db/index/index_descriptor.h"
#include "mongo/db/keypattern.h"
+#include "mongo/db/matcher/expression_always_boolean.h"
#include "mongo/db/matcher/expression_parser.h"
#include "mongo/db/op_observer.h"
#include "mongo/db/operation_context.h"
@@ -292,6 +293,16 @@ void CollectionImpl::init(OperationContext* opCtx) {
// Enforce that the validator can be used on this namespace.
uassertStatusOK(checkValidatorCanBeUsedOnNs(validatorDoc, ns(), _uuid));
+
+ // Make sure to parse the action and level before the MatchExpression, since certain features
+ // are not supported with certain combinations of action and level.
+ _validationAction = uassertStatusOK(_parseValidationAction(collectionOptions.validationAction));
+ _validationLevel = uassertStatusOK(_parseValidationLevel(collectionOptions.validationLevel));
+ if (collectionOptions.recordPreImages) {
+ uassertStatusOK(validatePreImageRecording(opCtx, _ns));
+ _recordPreImages = true;
+ }
+
// Store the result (OK / error) of parsing the validator, but do not enforce that the result is
// OK. This is intentional, as users may have validators on disk which were considered well
// formed in older versions but not in newer versions.
@@ -306,12 +317,6 @@ void CollectionImpl::init(OperationContext* opCtx) {
"namespace"_attr = _ns,
"validatorStatus"_attr = _validator.getStatus());
}
- _validationAction = uassertStatusOK(_parseValidationAction(collectionOptions.validationAction));
- _validationLevel = uassertStatusOK(_parseValidationLevel(collectionOptions.validationLevel));
- if (collectionOptions.recordPreImages) {
- uassertStatusOK(validatePreImageRecording(opCtx, _ns));
- _recordPreImages = true;
- }
getIndexCatalog()->init(opCtx).transitional_ignore();
_initialized = true;
@@ -428,6 +433,12 @@ Collection::Validator CollectionImpl::parseValidator(
// validator to apply some additional checks.
expCtx->isParsingCollectionValidator = true;
+ // If the validation action is "warn" or the level is "moderate", then disallow any encryption
+ // keywords. This is to prevent any plaintext data from showing up in the logs.
+ if (_validationAction == CollectionImpl::ValidationAction::WARN ||
+ _validationLevel == CollectionImpl::ValidationLevel::MODERATE)
+ allowedFeatures &= ~MatchExpressionParser::AllowedFeatures::kEncryptKeywords;
+
auto statusWithMatcher =
MatchExpressionParser::parse(validator, expCtx, ExtensionsCallbackNoop(), allowedFeatures);
@@ -985,28 +996,19 @@ void CollectionImpl::cappedTruncateAfter(OperationContext* opCtx, RecordId end,
_recordStore->cappedTruncateAfter(opCtx, end, inclusive);
}
-Status CollectionImpl::setValidator(OperationContext* opCtx, BSONObj validatorDoc) {
+void CollectionImpl::setValidator(OperationContext* opCtx, Validator validator) {
invariant(opCtx->lockState()->isCollectionLockedForMode(ns(), MODE_X));
- // Make owned early so that the parsed match expression refers to the owned object.
- if (!validatorDoc.isOwned())
- validatorDoc = validatorDoc.getOwned();
-
- // Note that, by the time we reach this, we should have already done a pre-parse that checks for
- // banned features, so we don't need to include that check again.
- auto newValidator =
- parseValidator(opCtx, validatorDoc, MatchExpressionParser::kAllowAllSpecialFeatures);
- if (!newValidator.isOK())
- return newValidator.getStatus();
-
- DurableCatalog::get(opCtx)->updateValidator(
- opCtx, getCatalogId(), validatorDoc, getValidationLevel(), getValidationAction());
+ DurableCatalog::get(opCtx)->updateValidator(opCtx,
+ getCatalogId(),
+ validator.validatorDoc.getOwned(),
+ getValidationLevel(),
+ getValidationAction());
opCtx->recoveryUnit()->onRollback([this, oldValidator = std::move(_validator)]() mutable {
this->_validator = std::move(oldValidator);
});
- _validator = std::move(newValidator);
- return Status::OK();
+ _validator = std::move(validator);
}
StringData CollectionImpl::getValidationLevel() const {
@@ -1042,6 +1044,17 @@ Status CollectionImpl::setValidationLevel(OperationContext* opCtx, StringData ne
auto oldValidationLevel = _validationLevel;
_validationLevel = levelSW.getValue();
+ // If setting the level to 'moderate', then reparse the validator to verify that there aren't
+ // any incompatible keywords.
+ if (_validationLevel == CollectionImpl::ValidationLevel::MODERATE) {
+ auto allowedFeatures = MatchExpressionParser::kAllowAllSpecialFeatures;
+ allowedFeatures &= ~MatchExpressionParser::AllowedFeatures::kEncryptKeywords;
+ auto validator = parseValidator(opCtx, _validator.validatorDoc, allowedFeatures);
+ if (!validator.isOK()) {
+ return validator.getStatus();
+ }
+ }
+
DurableCatalog::get(opCtx)->updateValidator(opCtx,
getCatalogId(),
_validator.validatorDoc,
@@ -1064,6 +1077,16 @@ Status CollectionImpl::setValidationAction(OperationContext* opCtx, StringData n
auto oldValidationAction = _validationAction;
_validationAction = actionSW.getValue();
+ // If setting the action to 'warn', then reparse the validator to verify that there aren't any
+ // incompatible keywords.
+ if (_validationAction == CollectionImpl::ValidationAction::WARN) {
+ auto allowedFeatures = MatchExpressionParser::kAllowAllSpecialFeatures;
+ allowedFeatures &= ~MatchExpressionParser::AllowedFeatures::kEncryptKeywords;
+ auto validator = parseValidator(opCtx, _validator.validatorDoc, allowedFeatures);
+ if (!validator.isOK()) {
+ return validator.getStatus();
+ }
+ }
DurableCatalog::get(opCtx)->updateValidator(opCtx,
getCatalogId(),
diff --git a/src/mongo/db/catalog/collection_impl.h b/src/mongo/db/catalog/collection_impl.h
index e3828f9471c..0d5926ec534 100644
--- a/src/mongo/db/catalog/collection_impl.h
+++ b/src/mongo/db/catalog/collection_impl.h
@@ -247,7 +247,7 @@ public:
* An empty validator removes all validation.
* Requires an exclusive lock on the collection.
*/
- Status setValidator(OperationContext* opCtx, BSONObj validator) final;
+ void setValidator(OperationContext* opCtx, Validator validator) final;
Status setValidationLevel(OperationContext* opCtx, StringData newLevel) final;
Status setValidationAction(OperationContext* opCtx, StringData newAction) final;
diff --git a/src/mongo/db/catalog/collection_mock.h b/src/mongo/db/catalog/collection_mock.h
index 9e3f97f1cc5..10672b977d4 100644
--- a/src/mongo/db/catalog/collection_mock.h
+++ b/src/mongo/db/catalog/collection_mock.h
@@ -178,7 +178,7 @@ public:
std::abort();
}
- Status setValidator(OperationContext* opCtx, BSONObj validator) {
+ void setValidator(OperationContext* opCtx, Validator validator) {
std::abort();
}
diff --git a/src/mongo/db/catalog/database_impl.cpp b/src/mongo/db/catalog/database_impl.cpp
index 426010cd762..974964ba5ad 100644
--- a/src/mongo/db/catalog/database_impl.cpp
+++ b/src/mongo/db/catalog/database_impl.cpp
@@ -934,8 +934,17 @@ Status DatabaseImpl::userCreateNS(OperationContext* opCtx,
// validator to apply some additional checks.
expCtx->isParsingCollectionValidator = true;
- auto statusWithMatcher =
- MatchExpressionParser::parse(collectionOptions.validator, std::move(expCtx));
+ // If the validation action is "warn" or the level is "moderate", then disallow any
+ // encryption keywords. This is to prevent any plaintext data from showing up in the logs.
+ auto allowedFeatures = MatchExpressionParser::kDefaultSpecialFeatures;
+ if (collectionOptions.validationAction == "warn" ||
+ collectionOptions.validationLevel == "moderate")
+ allowedFeatures &= ~MatchExpressionParser::AllowedFeatures::kEncryptKeywords;
+
+ auto statusWithMatcher = MatchExpressionParser::parse(collectionOptions.validator,
+ std::move(expCtx),
+ ExtensionsCallbackNoop(),
+ allowedFeatures);
// We check the status of the parse to see if there are any banned features, but we don't
// actually need the result for now.
diff --git a/src/mongo/db/matcher/expression_parser.cpp b/src/mongo/db/matcher/expression_parser.cpp
index a06e07b82d7..42ee487c408 100644
--- a/src/mongo/db/matcher/expression_parser.cpp
+++ b/src/mongo/db/matcher/expression_parser.cpp
@@ -394,7 +394,7 @@ StatusWithMatchExpression parseJSONSchema(StringData name,
}
return JSONSchemaParser::parse(
- expCtx, elem.Obj(), internalQueryIgnoreUnknownJSONSchemaKeywords.load());
+ expCtx, elem.Obj(), allowedFeatures, internalQueryIgnoreUnknownJSONSchemaKeywords.load());
}
template <class T>
diff --git a/src/mongo/db/matcher/expression_parser.h b/src/mongo/db/matcher/expression_parser.h
index b4048949055..466fc089eac 100644
--- a/src/mongo/db/matcher/expression_parser.h
+++ b/src/mongo/db/matcher/expression_parser.h
@@ -99,13 +99,14 @@ public:
kJavascript = 1 << 2,
kExpr = 1 << 3,
kJSONSchema = 1 << 4,
+ kEncryptKeywords = 1 << 5,
};
using AllowedFeatureSet = unsigned long long;
static constexpr AllowedFeatureSet kBanAllSpecialFeatures = 0;
static constexpr AllowedFeatureSet kAllowAllSpecialFeatures =
std::numeric_limits<unsigned long long>::max();
static constexpr AllowedFeatureSet kDefaultSpecialFeatures =
- AllowedFeatures::kExpr | AllowedFeatures::kJSONSchema;
+ AllowedFeatures::kExpr | AllowedFeatures::kJSONSchema | AllowedFeatures::kEncryptKeywords;
/**
* Parses PathAcceptingKeyword from 'typeElem'. Returns 'defaultKeyword' if 'typeElem'
diff --git a/src/mongo/db/matcher/schema/json_schema_parser.cpp b/src/mongo/db/matcher/schema/json_schema_parser.cpp
index 4cdc0ba0502..e7d8a0960c2 100644
--- a/src/mongo/db/matcher/schema/json_schema_parser.cpp
+++ b/src/mongo/db/matcher/schema/json_schema_parser.cpp
@@ -67,6 +67,7 @@ namespace mongo {
using PatternSchema = InternalSchemaAllowedPropertiesMatchExpression::PatternSchema;
using Pattern = InternalSchemaAllowedPropertiesMatchExpression::Pattern;
+using AllowedFeatureSet = MatchExpressionParser::AllowedFeatureSet;
namespace {
@@ -96,6 +97,7 @@ constexpr StringData kNamePlaceholder = "i"_sd;
StatusWithMatchExpression _parse(const boost::intrusive_ptr<ExpressionContext>& expCtx,
StringData path,
BSONObj schema,
+ AllowedFeatureSet allowedFeatures,
bool ignoreUnknownKeywords);
/**
@@ -305,6 +307,7 @@ template <class T>
StatusWithMatchExpression parseLogicalKeyword(const boost::intrusive_ptr<ExpressionContext>& expCtx,
StringData path,
BSONElement logicalElement,
+ AllowedFeatureSet allowedFeatures,
bool ignoreUnknownKeywords) {
if (logicalElement.type() != BSONType::Array) {
return {ErrorCodes::TypeMismatch,
@@ -328,7 +331,8 @@ StatusWithMatchExpression parseLogicalKeyword(const boost::intrusive_ptr<Express
<< elem.type()};
}
- auto nestedSchemaMatch = _parse(expCtx, path, elem.embeddedObject(), ignoreUnknownKeywords);
+ auto nestedSchemaMatch =
+ _parse(expCtx, path, elem.embeddedObject(), allowedFeatures, ignoreUnknownKeywords);
if (!nestedSchemaMatch.isOK()) {
return nestedSchemaMatch.getStatus();
}
@@ -462,6 +466,7 @@ StatusWithMatchExpression parseProperties(const boost::intrusive_ptr<ExpressionC
BSONElement propertiesElt,
InternalSchemaTypeExpression* typeExpr,
const StringDataSet& requiredProperties,
+ AllowedFeatureSet allowedFeatures,
bool ignoreUnknownKeywords) {
if (propertiesElt.type() != BSONType::Object) {
return {Status(ErrorCodes::TypeMismatch,
@@ -482,6 +487,7 @@ StatusWithMatchExpression parseProperties(const boost::intrusive_ptr<ExpressionC
auto nestedSchemaMatch = _parse(expCtx,
property.fieldNameStringData(),
property.embeddedObject(),
+ allowedFeatures,
ignoreUnknownKeywords);
if (!nestedSchemaMatch.isOK()) {
return nestedSchemaMatch.getStatus();
@@ -522,6 +528,7 @@ StatusWithMatchExpression parseProperties(const boost::intrusive_ptr<ExpressionC
StatusWith<std::vector<PatternSchema>> parsePatternProperties(
const boost::intrusive_ptr<ExpressionContext>& expCtx,
BSONElement patternPropertiesElt,
+ AllowedFeatureSet allowedFeatures,
bool ignoreUnknownKeywords) {
std::vector<PatternSchema> patternProperties;
if (!patternPropertiesElt) {
@@ -547,8 +554,11 @@ StatusWith<std::vector<PatternSchema>> parsePatternProperties(
// Parse the nested schema using a placeholder as the path, since we intend on using the
// resulting match expression inside an ExpressionWithPlaceholder.
- auto nestedSchemaMatch =
- _parse(expCtx, kNamePlaceholder, patternSchema.embeddedObject(), ignoreUnknownKeywords);
+ auto nestedSchemaMatch = _parse(expCtx,
+ kNamePlaceholder,
+ patternSchema.embeddedObject(),
+ allowedFeatures,
+ ignoreUnknownKeywords);
if (!nestedSchemaMatch.isOK()) {
return nestedSchemaMatch.getStatus();
}
@@ -565,6 +575,7 @@ StatusWith<std::vector<PatternSchema>> parsePatternProperties(
StatusWithMatchExpression parseAdditionalProperties(
const boost::intrusive_ptr<ExpressionContext>& expCtx,
BSONElement additionalPropertiesElt,
+ AllowedFeatureSet allowedFeatures,
bool ignoreUnknownKeywords) {
if (!additionalPropertiesElt) {
// The absence of the 'additionalProperties' keyword is identical in meaning to the presence
@@ -590,8 +601,11 @@ StatusWithMatchExpression parseAdditionalProperties(
// Parse the nested schema using a placeholder as the path, since we intend on using the
// resulting match expression inside an ExpressionWithPlaceholder.
- auto nestedSchemaMatch = _parse(
- expCtx, kNamePlaceholder, additionalPropertiesElt.embeddedObject(), ignoreUnknownKeywords);
+ auto nestedSchemaMatch = _parse(expCtx,
+ kNamePlaceholder,
+ additionalPropertiesElt.embeddedObject(),
+ allowedFeatures,
+ ignoreUnknownKeywords);
if (!nestedSchemaMatch.isOK()) {
return nestedSchemaMatch.getStatus();
}
@@ -610,6 +624,7 @@ StatusWithMatchExpression parseAllowedProperties(
BSONElement patternPropertiesElt,
BSONElement additionalPropertiesElt,
InternalSchemaTypeExpression* typeExpr,
+ AllowedFeatureSet allowedFeatures,
bool ignoreUnknownKeywords) {
// Collect the set of properties named by the 'properties' keyword.
StringDataSet propertyNames;
@@ -621,14 +636,14 @@ StatusWithMatchExpression parseAllowedProperties(
propertyNames.insert(propertyNamesVec.begin(), propertyNamesVec.end());
}
- auto patternProperties =
- parsePatternProperties(expCtx, patternPropertiesElt, ignoreUnknownKeywords);
+ auto patternProperties = parsePatternProperties(
+ expCtx, patternPropertiesElt, allowedFeatures, ignoreUnknownKeywords);
if (!patternProperties.isOK()) {
return patternProperties.getStatus();
}
- auto otherwiseExpr =
- parseAdditionalProperties(expCtx, additionalPropertiesElt, ignoreUnknownKeywords);
+ auto otherwiseExpr = parseAdditionalProperties(
+ expCtx, additionalPropertiesElt, allowedFeatures, ignoreUnknownKeywords);
if (!otherwiseExpr.isOK()) {
return otherwiseExpr.getStatus();
}
@@ -694,11 +709,12 @@ StatusWithMatchExpression translateSchemaDependency(
const boost::intrusive_ptr<ExpressionContext>& expCtx,
StringData path,
BSONElement dependency,
+ AllowedFeatureSet allowedFeatures,
bool ignoreUnknownKeywords) {
invariant(dependency.type() == BSONType::Object);
auto nestedSchemaMatch =
- _parse(expCtx, path, dependency.embeddedObject(), ignoreUnknownKeywords);
+ _parse(expCtx, path, dependency.embeddedObject(), allowedFeatures, ignoreUnknownKeywords);
if (!nestedSchemaMatch.isOK()) {
return nestedSchemaMatch.getStatus();
}
@@ -776,6 +792,7 @@ StatusWithMatchExpression translatePropertyDependency(StringData path, BSONEleme
StatusWithMatchExpression parseDependencies(const boost::intrusive_ptr<ExpressionContext>& expCtx,
StringData path,
BSONElement dependencies,
+ AllowedFeatureSet allowedFeatures,
bool ignoreUnknownKeywords) {
if (dependencies.type() != BSONType::Object) {
return {ErrorCodes::TypeMismatch,
@@ -795,7 +812,8 @@ StatusWithMatchExpression parseDependencies(const boost::intrusive_ptr<Expressio
}
auto dependencyExpr = (dependency.type() == BSONType::Object)
- ? translateSchemaDependency(expCtx, path, dependency, ignoreUnknownKeywords)
+ ? translateSchemaDependency(
+ expCtx, path, dependency, allowedFeatures, ignoreUnknownKeywords)
: translatePropertyDependency(path, dependency);
if (!dependencyExpr.isOK()) {
return dependencyExpr.getStatus();
@@ -833,6 +851,7 @@ StatusWith<boost::optional<long long>> parseItems(
const boost::intrusive_ptr<ExpressionContext>& expCtx,
StringData path,
BSONElement itemsElt,
+ AllowedFeatureSet allowedFeatures,
bool ignoreUnknownKeywords,
InternalSchemaTypeExpression* typeExpr,
AndMatchExpression* andExpr) {
@@ -854,8 +873,11 @@ StatusWith<boost::optional<long long>> parseItems(
// We want to make an ExpressionWithPlaceholder for $_internalSchemaMatchArrayIndex,
// so we use our default placeholder as the path.
- auto parsedSubschema =
- _parse(expCtx, kNamePlaceholder, subschema.embeddedObject(), ignoreUnknownKeywords);
+ auto parsedSubschema = _parse(expCtx,
+ kNamePlaceholder,
+ subschema.embeddedObject(),
+ allowedFeatures,
+ ignoreUnknownKeywords);
if (!parsedSubschema.isOK()) {
return parsedSubschema.getStatus();
}
@@ -879,8 +901,11 @@ StatusWith<boost::optional<long long>> parseItems(
// When "items" is an object, generate a single AllElemMatchFromIndex that applies to every
// element in the array to match. The parsed expression is intended for an
// ExpressionWithPlaceholder, so we use the default placeholder as the path.
- auto nestedItemsSchema =
- _parse(expCtx, kNamePlaceholder, itemsElt.embeddedObject(), ignoreUnknownKeywords);
+ auto nestedItemsSchema = _parse(expCtx,
+ kNamePlaceholder,
+ itemsElt.embeddedObject(),
+ allowedFeatures,
+ ignoreUnknownKeywords);
if (!nestedItemsSchema.isOK()) {
return nestedItemsSchema.getStatus();
}
@@ -910,6 +935,7 @@ Status parseAdditionalItems(const boost::intrusive_ptr<ExpressionContext>& expCt
StringData path,
BSONElement additionalItemsElt,
boost::optional<long long> startIndexForAdditionalItems,
+ AllowedFeatureSet allowedFeatures,
bool ignoreUnknownKeywords,
InternalSchemaTypeExpression* typeExpr,
AndMatchExpression* andExpr) {
@@ -924,8 +950,11 @@ Status parseAdditionalItems(const boost::intrusive_ptr<ExpressionContext>& expCt
emptyPlaceholder, std::make_unique<AlwaysFalseMatchExpression>());
}
} else if (additionalItemsElt.type() == BSONType::Object) {
- auto parsedOtherwiseExpr = _parse(
- expCtx, kNamePlaceholder, additionalItemsElt.embeddedObject(), ignoreUnknownKeywords);
+ auto parsedOtherwiseExpr = _parse(expCtx,
+ kNamePlaceholder,
+ additionalItemsElt.embeddedObject(),
+ allowedFeatures,
+ ignoreUnknownKeywords);
if (!parsedOtherwiseExpr.isOK()) {
return parsedOtherwiseExpr.getStatus();
}
@@ -957,12 +986,14 @@ Status parseAdditionalItems(const boost::intrusive_ptr<ExpressionContext>& expCt
Status parseItemsAndAdditionalItems(StringMap<BSONElement>& keywordMap,
const boost::intrusive_ptr<ExpressionContext>& expCtx,
StringData path,
+ AllowedFeatureSet allowedFeatures,
bool ignoreUnknownKeywords,
InternalSchemaTypeExpression* typeExpr,
AndMatchExpression* andExpr) {
boost::optional<long long> startIndexForAdditionalItems;
if (auto itemsElt = keywordMap[JSONSchemaParser::kSchemaItemsKeyword]) {
- auto index = parseItems(expCtx, path, itemsElt, ignoreUnknownKeywords, typeExpr, andExpr);
+ auto index = parseItems(
+ expCtx, path, itemsElt, allowedFeatures, ignoreUnknownKeywords, typeExpr, andExpr);
if (!index.isOK()) {
return index.getStatus();
}
@@ -974,6 +1005,7 @@ Status parseItemsAndAdditionalItems(StringMap<BSONElement>& keywordMap,
path,
additionalItemsElt,
startIndexForAdditionalItems,
+ allowedFeatures,
ignoreUnknownKeywords,
typeExpr,
andExpr);
@@ -996,10 +1028,11 @@ Status translateLogicalKeywords(StringMap<BSONElement>& keywordMap,
const boost::intrusive_ptr<ExpressionContext>& expCtx,
StringData path,
AndMatchExpression* andExpr,
+ AllowedFeatureSet allowedFeatures,
bool ignoreUnknownKeywords) {
if (auto allOfElt = keywordMap[JSONSchemaParser::kSchemaAllOfKeyword]) {
- auto allOfExpr =
- parseLogicalKeyword<AndMatchExpression>(expCtx, path, allOfElt, ignoreUnknownKeywords);
+ auto allOfExpr = parseLogicalKeyword<AndMatchExpression>(
+ expCtx, path, allOfElt, allowedFeatures, ignoreUnknownKeywords);
if (!allOfExpr.isOK()) {
return allOfExpr.getStatus();
}
@@ -1007,8 +1040,8 @@ Status translateLogicalKeywords(StringMap<BSONElement>& keywordMap,
}
if (auto anyOfElt = keywordMap[JSONSchemaParser::kSchemaAnyOfKeyword]) {
- auto anyOfExpr =
- parseLogicalKeyword<OrMatchExpression>(expCtx, path, anyOfElt, ignoreUnknownKeywords);
+ auto anyOfExpr = parseLogicalKeyword<OrMatchExpression>(
+ expCtx, path, anyOfElt, allowedFeatures, ignoreUnknownKeywords);
if (!anyOfExpr.isOK()) {
return anyOfExpr.getStatus();
}
@@ -1017,7 +1050,7 @@ Status translateLogicalKeywords(StringMap<BSONElement>& keywordMap,
if (auto oneOfElt = keywordMap[JSONSchemaParser::kSchemaOneOfKeyword]) {
auto oneOfExpr = parseLogicalKeyword<InternalSchemaXorMatchExpression>(
- expCtx, path, oneOfElt, ignoreUnknownKeywords);
+ expCtx, path, oneOfElt, allowedFeatures, ignoreUnknownKeywords);
if (!oneOfExpr.isOK()) {
return oneOfExpr.getStatus();
}
@@ -1032,7 +1065,8 @@ Status translateLogicalKeywords(StringMap<BSONElement>& keywordMap,
<< notElt.type()};
}
- auto parsedExpr = _parse(expCtx, path, notElt.embeddedObject(), ignoreUnknownKeywords);
+ auto parsedExpr =
+ _parse(expCtx, path, notElt.embeddedObject(), allowedFeatures, ignoreUnknownKeywords);
if (!parsedExpr.isOK()) {
return parsedExpr.getStatus();
}
@@ -1066,6 +1100,7 @@ Status translateLogicalKeywords(StringMap<BSONElement>& keywordMap,
Status translateArrayKeywords(StringMap<BSONElement>& keywordMap,
const boost::intrusive_ptr<ExpressionContext>& expCtx,
StringData path,
+ AllowedFeatureSet allowedFeatures,
bool ignoreUnknownKeywords,
InternalSchemaTypeExpression* typeExpr,
AndMatchExpression* andExpr) {
@@ -1096,7 +1131,7 @@ Status translateArrayKeywords(StringMap<BSONElement>& keywordMap,
}
return parseItemsAndAdditionalItems(
- keywordMap, expCtx, path, ignoreUnknownKeywords, typeExpr, andExpr);
+ keywordMap, expCtx, path, allowedFeatures, ignoreUnknownKeywords, typeExpr, andExpr);
}
/**
@@ -1117,6 +1152,7 @@ Status translateObjectKeywords(StringMap<BSONElement>& keywordMap,
StringData path,
InternalSchemaTypeExpression* typeExpr,
AndMatchExpression* andExpr,
+ AllowedFeatureSet allowedFeatures,
bool ignoreUnknownKeywords) {
StringDataSet requiredProperties;
if (auto requiredElt = keywordMap[JSONSchemaParser::kSchemaRequiredKeyword]) {
@@ -1128,8 +1164,13 @@ Status translateObjectKeywords(StringMap<BSONElement>& keywordMap,
}
if (auto propertiesElt = keywordMap[JSONSchemaParser::kSchemaPropertiesKeyword]) {
- auto propertiesExpr = parseProperties(
- expCtx, path, propertiesElt, typeExpr, requiredProperties, ignoreUnknownKeywords);
+ auto propertiesExpr = parseProperties(expCtx,
+ path,
+ propertiesElt,
+ typeExpr,
+ requiredProperties,
+ allowedFeatures,
+ ignoreUnknownKeywords);
if (!propertiesExpr.isOK()) {
return propertiesExpr.getStatus();
}
@@ -1149,6 +1190,7 @@ Status translateObjectKeywords(StringMap<BSONElement>& keywordMap,
patternPropertiesElt,
additionalPropertiesElt,
typeExpr,
+ allowedFeatures,
ignoreUnknownKeywords);
if (!allowedPropertiesExpr.isOK()) {
return allowedPropertiesExpr.getStatus();
@@ -1184,8 +1226,8 @@ Status translateObjectKeywords(StringMap<BSONElement>& keywordMap,
}
if (auto dependenciesElt = keywordMap[JSONSchemaParser::kSchemaDependenciesKeyword]) {
- auto dependenciesExpr =
- parseDependencies(expCtx, path, dependenciesElt, ignoreUnknownKeywords);
+ auto dependenciesExpr = parseDependencies(
+ expCtx, path, dependenciesElt, allowedFeatures, ignoreUnknownKeywords);
if (!dependenciesExpr.isOK()) {
return dependenciesExpr.getStatus();
}
@@ -1311,10 +1353,16 @@ Status translateScalarKeywords(StringMap<BSONElement>& keywordMap,
Status translateEncryptionKeywords(StringMap<BSONElement>& keywordMap,
const boost::intrusive_ptr<ExpressionContext>& expCtx,
StringData path,
+ AllowedFeatureSet allowedFeatures,
AndMatchExpression* andExpr) {
auto encryptElt = keywordMap[JSONSchemaParser::kSchemaEncryptKeyword];
auto encryptMetadataElt = keywordMap[JSONSchemaParser::kSchemaEncryptMetadataKeyword];
+ if ((allowedFeatures & MatchExpressionParser::AllowedFeatures::kEncryptKeywords) == 0u &&
+ (encryptElt || encryptMetadataElt))
+ return Status(ErrorCodes::QueryFeatureNotAllowed,
+ "Encryption-related validator keywords are not allowed in this context");
+
if (encryptElt && encryptMetadataElt) {
return Status(ErrorCodes::FailedToParse,
str::stream() << "Cannot specify both $jsonSchema keywords '"
@@ -1400,6 +1448,7 @@ Status validateMetadataKeywords(StringMap<BSONElement>& keywordMap) {
StatusWithMatchExpression _parse(const boost::intrusive_ptr<ExpressionContext>& expCtx,
StringData path,
BSONObj schema,
+ AllowedFeatureSet allowedFeatures,
bool ignoreUnknownKeywords) {
// Map from JSON Schema keyword to the corresponding element from 'schema', or to an empty
// BSONElement if the JSON Schema keyword is not specified.
@@ -1524,25 +1573,36 @@ StatusWithMatchExpression _parse(const boost::intrusive_ptr<ExpressionContext>&
return translationStatus;
}
- translationStatus = translateArrayKeywords(
- keywordMap, expCtx, path, ignoreUnknownKeywords, typeExpr.get(), andExpr.get());
+ translationStatus = translateArrayKeywords(keywordMap,
+ expCtx,
+ path,
+ allowedFeatures,
+ ignoreUnknownKeywords,
+ typeExpr.get(),
+ andExpr.get());
if (!translationStatus.isOK()) {
return translationStatus;
}
- translationStatus = translateEncryptionKeywords(keywordMap, expCtx, path, andExpr.get());
+ translationStatus =
+ translateEncryptionKeywords(keywordMap, expCtx, path, allowedFeatures, andExpr.get());
if (!translationStatus.isOK()) {
return translationStatus;
}
- translationStatus = translateObjectKeywords(
- keywordMap, expCtx, path, typeExpr.get(), andExpr.get(), ignoreUnknownKeywords);
+ translationStatus = translateObjectKeywords(keywordMap,
+ expCtx,
+ path,
+ typeExpr.get(),
+ andExpr.get(),
+ allowedFeatures,
+ ignoreUnknownKeywords);
if (!translationStatus.isOK()) {
return translationStatus;
}
- translationStatus =
- translateLogicalKeywords(keywordMap, expCtx, path, andExpr.get(), ignoreUnknownKeywords);
+ translationStatus = translateLogicalKeywords(
+ keywordMap, expCtx, path, andExpr.get(), allowedFeatures, ignoreUnknownKeywords);
if (!translationStatus.isOK()) {
return translationStatus;
}
@@ -1609,6 +1669,7 @@ StatusWith<MatcherTypeSet> JSONSchemaParser::parseTypeSet(
StatusWithMatchExpression JSONSchemaParser::parse(
const boost::intrusive_ptr<ExpressionContext>& expCtx,
BSONObj schema,
+ AllowedFeatureSet allowedFeatures,
bool ignoreUnknownKeywords) {
LOGV2_DEBUG(20728,
5,
@@ -1616,7 +1677,7 @@ StatusWithMatchExpression JSONSchemaParser::parse(
"schema_jsonString_JsonStringFormat_LegacyStrict"_attr =
schema.jsonString(JsonStringFormat::LegacyStrict));
try {
- auto translation = _parse(expCtx, ""_sd, schema, ignoreUnknownKeywords);
+ auto translation = _parse(expCtx, ""_sd, schema, allowedFeatures, ignoreUnknownKeywords);
if (shouldLog(logv2::LogSeverity::Debug(5)) && translation.isOK()) {
LOGV2_DEBUG(20729,
5,
diff --git a/src/mongo/db/matcher/schema/json_schema_parser.h b/src/mongo/db/matcher/schema/json_schema_parser.h
index 378b093ba09..b1fd403dac4 100644
--- a/src/mongo/db/matcher/schema/json_schema_parser.h
+++ b/src/mongo/db/matcher/schema/json_schema_parser.h
@@ -87,9 +87,12 @@ public:
* Converts a JSON schema, represented as BSON, into a semantically equivalent match expression
* tree. Returns a non-OK status if the schema is invalid or cannot be parsed.
*/
- static StatusWithMatchExpression parse(const boost::intrusive_ptr<ExpressionContext>& expCtx,
- BSONObj schema,
- bool ignoreUnknownKeywords = false);
+ static StatusWithMatchExpression parse(
+ const boost::intrusive_ptr<ExpressionContext>& expCtx,
+ BSONObj schema,
+ MatchExpressionParser::AllowedFeatureSet allowedFeatures =
+ MatchExpressionParser::kAllowAllSpecialFeatures,
+ bool ignoreUnknownKeywords = false);
/**
* Builds a set of type aliases from the given type element using 'aliasMapFind'. Returns a
diff --git a/src/mongo/db/matcher/schema/object_keywords_test.cpp b/src/mongo/db/matcher/schema/object_keywords_test.cpp
index 31709e7ab5e..73bb4b7d7dd 100644
--- a/src/mongo/db/matcher/schema/object_keywords_test.cpp
+++ b/src/mongo/db/matcher/schema/object_keywords_test.cpp
@@ -870,53 +870,67 @@ TEST(JSONSchemaObjectKeywordTest,
TEST(JSONSchemaObjectKeywordTest, CorrectlyIgnoresUnknownKeywordsParameterIsSet) {
const auto ignoreUnknownKeywords = true;
+ const auto allowedFeatures = MatchExpressionParser::kAllowAllSpecialFeatures;
auto schema = fromjson("{ignored_keyword: 1}");
- ASSERT_OK(JSONSchemaParser::parse(new ExpressionContextForTest(), schema, ignoreUnknownKeywords)
+ ASSERT_OK(JSONSchemaParser::parse(
+ new ExpressionContextForTest(), schema, allowedFeatures, ignoreUnknownKeywords)
.getStatus());
schema = fromjson("{properties: {a: {ignored_keyword: 1}}}");
- ASSERT_OK(JSONSchemaParser::parse(new ExpressionContextForTest(), schema, ignoreUnknownKeywords)
+ ASSERT_OK(JSONSchemaParser::parse(
+ new ExpressionContextForTest(), schema, allowedFeatures, ignoreUnknownKeywords)
.getStatus());
schema = fromjson("{properties: {a: {oneOf: [{ignored_keyword: {}}]}}}");
- ASSERT_OK(JSONSchemaParser::parse(new ExpressionContextForTest(), schema, ignoreUnknownKeywords)
+ ASSERT_OK(JSONSchemaParser::parse(
+ new ExpressionContextForTest(), schema, allowedFeatures, ignoreUnknownKeywords)
.getStatus());
}
TEST(JSONSchemaObjectKeywordTest, FailsToParseUnsupportedKeywordsWhenIgnoreUnknownParameterIsSet) {
const auto ignoreUnknownKeywords = true;
+ const auto allowedFeatures = MatchExpressionParser::kAllowAllSpecialFeatures;
- auto result = JSONSchemaParser::parse(
- new ExpressionContextForTest(), fromjson("{default: {}}"), ignoreUnknownKeywords);
+ auto result = JSONSchemaParser::parse(new ExpressionContextForTest(),
+ fromjson("{default: {}}"),
+ allowedFeatures,
+ ignoreUnknownKeywords);
ASSERT_STRING_CONTAINS(result.getStatus().reason(),
"$jsonSchema keyword 'default' is not currently supported");
result = JSONSchemaParser::parse(new ExpressionContextForTest(),
fromjson("{definitions: {numberField: {type: 'number'}}}"),
+ allowedFeatures,
ignoreUnknownKeywords);
ASSERT_STRING_CONTAINS(result.getStatus().reason(),
"$jsonSchema keyword 'definitions' is not currently supported");
- result = JSONSchemaParser::parse(
- new ExpressionContextForTest(), fromjson("{format: 'email'}"), ignoreUnknownKeywords);
+ result = JSONSchemaParser::parse(new ExpressionContextForTest(),
+ fromjson("{format: 'email'}"),
+ allowedFeatures,
+ ignoreUnknownKeywords);
ASSERT_STRING_CONTAINS(result.getStatus().reason(),
"$jsonSchema keyword 'format' is not currently supported");
- result = JSONSchemaParser::parse(
- new ExpressionContextForTest(), fromjson("{id: 'someschema.json'}"), ignoreUnknownKeywords);
+ result = JSONSchemaParser::parse(new ExpressionContextForTest(),
+ fromjson("{id: 'someschema.json'}"),
+ allowedFeatures,
+ ignoreUnknownKeywords);
ASSERT_STRING_CONTAINS(result.getStatus().reason(),
"$jsonSchema keyword 'id' is not currently supported");
result = JSONSchemaParser::parse(new ExpressionContextForTest(),
BSON("$ref"
<< "#/definitions/positiveInt"),
+ allowedFeatures,
ignoreUnknownKeywords);
ASSERT_STRING_CONTAINS(result.getStatus().reason(),
"$jsonSchema keyword '$ref' is not currently supported");
result = JSONSchemaParser::parse(new ExpressionContextForTest(),
fromjson("{$schema: 'hyper-schema'}"),
+ allowedFeatures,
ignoreUnknownKeywords);
ASSERT_STRING_CONTAINS(result.getStatus().reason(),
"$jsonSchema keyword '$schema' is not currently supported");
@@ -924,6 +938,7 @@ TEST(JSONSchemaObjectKeywordTest, FailsToParseUnsupportedKeywordsWhenIgnoreUnkno
result =
JSONSchemaParser::parse(new ExpressionContextForTest(),
fromjson("{$schema: 'http://json-schema.org/draft-04/schema#'}"),
+ allowedFeatures,
ignoreUnknownKeywords);
ASSERT_STRING_CONTAINS(result.getStatus().reason(),
"$jsonSchema keyword '$schema' is not currently supported");
diff --git a/src/mongo/db/pipeline/pipeline.h b/src/mongo/db/pipeline/pipeline.h
index 5a5c40ef9c4..8ef13836c62 100644
--- a/src/mongo/db/pipeline/pipeline.h
+++ b/src/mongo/db/pipeline/pipeline.h
@@ -93,7 +93,8 @@ public:
static constexpr MatchExpressionParser::AllowedFeatureSet kAllowedMatcherFeatures =
MatchExpressionParser::AllowedFeatures::kText |
MatchExpressionParser::AllowedFeatures::kExpr |
- MatchExpressionParser::AllowedFeatures::kJSONSchema;
+ MatchExpressionParser::AllowedFeatures::kJSONSchema |
+ MatchExpressionParser::AllowedFeatures::kEncryptKeywords;
/**
* The match expression features allowed when running a pipeline with $geoNear.
@@ -102,6 +103,7 @@ public:
MatchExpressionParser::AllowedFeatures::kText |
MatchExpressionParser::AllowedFeatures::kExpr |
MatchExpressionParser::AllowedFeatures::kJSONSchema |
+ MatchExpressionParser::AllowedFeatures::kEncryptKeywords |
MatchExpressionParser::AllowedFeatures::kGeoNear;
/**