/**
* Copyright (C) 2014 MongoDB Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* 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
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
*
* 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 GNU Affero General 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/catalog/index_key_validate.h"
#include
#include
#include
#include
#include "mongo/base/status.h"
#include "mongo/base/status_with.h"
#include "mongo/db/field_ref.h"
#include "mongo/db/index/index_descriptor.h"
#include "mongo/db/index_names.h"
#include "mongo/db/jsobj.h"
#include "mongo/db/namespace_string.h"
#include "mongo/db/query/collation/collator_factory_interface.h"
#include "mongo/db/service_context.h"
#include "mongo/util/fail_point_service.h"
#include "mongo/util/mongoutils/str.h"
#include "mongo/util/represent_as.h"
namespace mongo {
namespace index_key_validate {
using std::string;
using IndexVersion = IndexDescriptor::IndexVersion;
namespace {
// When the skipIndexCreateFieldNameValidation failpoint is enabled, validation for index field
// names will be disabled. This will allow for creation of indexes with invalid field names in their
// specification.
MONGO_FP_DECLARE(skipIndexCreateFieldNameValidation);
static const std::set allowedFieldNames = {
IndexDescriptor::k2dIndexMaxFieldName,
IndexDescriptor::k2dIndexBitsFieldName,
IndexDescriptor::k2dIndexMaxFieldName,
IndexDescriptor::k2dIndexMinFieldName,
IndexDescriptor::k2dsphereCoarsestIndexedLevel,
IndexDescriptor::k2dsphereFinestIndexedLevel,
IndexDescriptor::k2dsphereVersionFieldName,
IndexDescriptor::kBackgroundFieldName,
IndexDescriptor::kCollationFieldName,
IndexDescriptor::kDefaultLanguageFieldName,
IndexDescriptor::kDropDuplicatesFieldName,
IndexDescriptor::kExpireAfterSecondsFieldName,
IndexDescriptor::kGeoHaystackBucketSize,
IndexDescriptor::kIndexNameFieldName,
IndexDescriptor::kIndexVersionFieldName,
IndexDescriptor::kKeyPatternFieldName,
IndexDescriptor::kLanguageOverrideFieldName,
IndexDescriptor::kNamespaceFieldName,
IndexDescriptor::kPartialFilterExprFieldName,
IndexDescriptor::kSparseFieldName,
IndexDescriptor::kStorageEngineFieldName,
IndexDescriptor::kTextVersionFieldName,
IndexDescriptor::kUniqueFieldName,
IndexDescriptor::kWeightsFieldName,
// Index creation under legacy writeMode can result in an index spec with an _id field.
"_id"};
static const std::set allowedIdIndexFieldNames = {
IndexDescriptor::kCollationFieldName,
IndexDescriptor::kIndexNameFieldName,
IndexDescriptor::kIndexVersionFieldName,
IndexDescriptor::kKeyPatternFieldName,
IndexDescriptor::kNamespaceFieldName,
// Index creation under legacy writeMode can result in an index spec with an _id field.
"_id"};
}
Status validateKeyPattern(const BSONObj& key, IndexDescriptor::IndexVersion indexVersion) {
const ErrorCodes::Error code = ErrorCodes::CannotCreateIndex;
if (key.objsize() > 2048)
return Status(code, "Index key pattern too large.");
if (key.isEmpty())
return Status(code, "Index keys cannot be empty.");
string pluginName = IndexNames::findPluginName(key);
if (pluginName.size()) {
if (!IndexNames::isKnownName(pluginName))
return Status(
code, mongoutils::str::stream() << "Unknown index plugin '" << pluginName << '\'');
}
BSONObjIterator it(key);
while (it.more()) {
BSONElement keyElement = it.next();
switch (indexVersion) {
case IndexVersion::kV0:
case IndexVersion::kV1: {
if (keyElement.type() == BSONType::Object || keyElement.type() == BSONType::Array) {
return {code,
str::stream() << "Values in index key pattern cannot be of type "
<< typeName(keyElement.type())
<< " for index version v:"
<< static_cast(indexVersion)};
}
break;
}
case IndexVersion::kV2: {
if (keyElement.isNumber()) {
double value = keyElement.number();
if (std::isnan(value)) {
return {code, "Values in the index key pattern cannot be NaN."};
} else if (value == 0.0) {
return {code, "Values in the index key pattern cannot be 0."};
}
} else if (keyElement.type() != BSONType::String) {
return {code,
str::stream()
<< "Values in v:2 index key pattern cannot be of type "
<< typeName(keyElement.type())
<< ". Only numbers > 0, numbers < 0, and strings are allowed."};
}
break;
}
default:
MONGO_UNREACHABLE;
}
if (keyElement.type() == String && pluginName != keyElement.str()) {
return Status(code, "Can't use more than one index plugin for a single index.");
}
// Ensure that the fields on which we are building the index are valid: a field must not
// begin with a '$' unless it is part of a DBRef or text index, and a field path cannot
// contain an empty field. If a field cannot be created or updated, it should not be
// indexable.
FieldRef keyField(keyElement.fieldName());
const size_t numParts = keyField.numParts();
if (numParts == 0) {
return Status(code, "Index keys cannot be an empty field.");
}
// "$**" is acceptable for a text index.
if (mongoutils::str::equals(keyElement.fieldName(), "$**") &&
keyElement.valuestrsafe() == IndexNames::TEXT)
continue;
if (mongoutils::str::equals(keyElement.fieldName(), "_fts") &&
keyElement.valuestrsafe() != IndexNames::TEXT) {
return Status(code, "Index key contains an illegal field name: '_fts'");
}
for (size_t i = 0; i != numParts; ++i) {
const StringData part = keyField.getPart(i);
// Check if the index key path contains an empty field.
if (part.empty()) {
return Status(code, "Index keys cannot contain an empty field.");
}
if (part[0] != '$')
continue;
// Check if the '$'-prefixed field is part of a DBRef: since we don't have the
// necessary context to validate whether this is a proper DBRef, we allow index
// creation on '$'-prefixed names that match those used in a DBRef.
const bool mightBePartOfDbRef =
(i != 0) && (part == "$db" || part == "$id" || part == "$ref");
if (!mightBePartOfDbRef) {
return Status(code,
"Index key contains an illegal field name: "
"field name starts with '$'.");
}
}
}
return Status::OK();
}
StatusWith validateIndexSpec(
const BSONObj& indexSpec,
const NamespaceString& expectedNamespace,
const ServerGlobalParams::FeatureCompatibility& featureCompatibility) {
bool hasKeyPatternField = false;
bool hasIndexNameField = false;
bool hasNamespaceField = false;
bool hasVersionField = false;
bool hasCollationField = false;
auto fieldNamesValidStatus = validateIndexSpecFieldNames(indexSpec);
if (!fieldNamesValidStatus.isOK()) {
return fieldNamesValidStatus;
}
boost::optional resolvedIndexVersion;
for (auto&& indexSpecElem : indexSpec) {
auto indexSpecElemFieldName = indexSpecElem.fieldNameStringData();
if (IndexDescriptor::kKeyPatternFieldName == indexSpecElemFieldName) {
if (indexSpecElem.type() != BSONType::Object) {
return {ErrorCodes::TypeMismatch,
str::stream() << "The field '" << IndexDescriptor::kKeyPatternFieldName
<< "' must be an object, but got "
<< typeName(indexSpecElem.type())};
}
std::vector keys;
for (auto&& keyElem : indexSpecElem.Obj()) {
auto keyElemFieldName = keyElem.fieldNameStringData();
if (std::find(keys.begin(), keys.end(), keyElemFieldName) != keys.end()) {
return {ErrorCodes::BadValue,
str::stream() << "The field '" << keyElemFieldName
<< "' appears multiple times in the index key pattern "
<< indexSpecElem.Obj()};
}
keys.push_back(keyElemFieldName);
}
// Here we always validate the key pattern according to the most recent rules, in order
// to enforce that all new indexes have well-formed key patterns.
Status keyPatternValidateStatus =
validateKeyPattern(indexSpecElem.Obj(), IndexDescriptor::kLatestIndexVersion);
if (!keyPatternValidateStatus.isOK()) {
return keyPatternValidateStatus;
}
hasKeyPatternField = true;
} else if (IndexDescriptor::kIndexNameFieldName == indexSpecElemFieldName) {
if (indexSpecElem.type() != BSONType::String) {
return {ErrorCodes::TypeMismatch,
str::stream() << "The field '" << IndexDescriptor::kIndexNameFieldName
<< "' must be a string, but got "
<< typeName(indexSpecElem.type())};
}
hasIndexNameField = true;
} else if (IndexDescriptor::kNamespaceFieldName == indexSpecElemFieldName) {
if (indexSpecElem.type() != BSONType::String) {
return {ErrorCodes::TypeMismatch,
str::stream() << "The field '" << IndexDescriptor::kNamespaceFieldName
<< "' must be a string, but got "
<< typeName(indexSpecElem.type())};
}
StringData ns = indexSpecElem.valueStringData();
if (ns.empty()) {
return {ErrorCodes::BadValue,
str::stream() << "The field '" << IndexDescriptor::kNamespaceFieldName
<< "' cannot be an empty string"};
}
if (ns != expectedNamespace.ns()) {
return {ErrorCodes::BadValue,
str::stream() << "The value of the field '"
<< IndexDescriptor::kNamespaceFieldName
<< "' ("
<< ns
<< ") doesn't match the namespace '"
<< expectedNamespace.ns()
<< "'"};
}
hasNamespaceField = true;
} else if (IndexDescriptor::kIndexVersionFieldName == indexSpecElemFieldName) {
if (!indexSpecElem.isNumber()) {
return {ErrorCodes::TypeMismatch,
str::stream() << "The field '" << IndexDescriptor::kIndexVersionFieldName
<< "' must be a number, but got "
<< typeName(indexSpecElem.type())};
}
auto requestedIndexVersionAsInt = representAs(indexSpecElem.number());
if (!requestedIndexVersionAsInt) {
return {ErrorCodes::BadValue,
str::stream()
<< "Index version must be representable as a 32-bit integer, but got "
<< indexSpecElem.toString(false, false)};
}
const IndexVersion requestedIndexVersion =
static_cast(*requestedIndexVersionAsInt);
auto creationAllowedStatus = IndexDescriptor::isIndexVersionAllowedForCreation(
requestedIndexVersion, featureCompatibility, indexSpec);
if (!creationAllowedStatus.isOK()) {
return creationAllowedStatus;
}
hasVersionField = true;
resolvedIndexVersion = requestedIndexVersion;
} else if (IndexDescriptor::kCollationFieldName == indexSpecElemFieldName) {
if (indexSpecElem.type() != BSONType::Object) {
return {ErrorCodes::TypeMismatch,
str::stream() << "The field '" << IndexDescriptor::kCollationFieldName
<< "' must be an object, but got "
<< typeName(indexSpecElem.type())};
}
if (indexSpecElem.Obj().isEmpty()) {
return {ErrorCodes::BadValue,
str::stream() << "The field '" << IndexDescriptor::kCollationFieldName
<< "' cannot be an empty object."};
}
hasCollationField = true;
} else {
// We can assume field name is valid at this point. Validation of fieldname is handled
// prior to this in validateIndexSpecFieldNames().
continue;
}
}
if (!resolvedIndexVersion) {
resolvedIndexVersion =
IndexDescriptor::getDefaultIndexVersion(featureCompatibility.version.load());
}
if (!hasKeyPatternField) {
return {ErrorCodes::FailedToParse,
str::stream() << "The '" << IndexDescriptor::kKeyPatternFieldName
<< "' field is a required property of an index specification"};
}
if (!hasIndexNameField) {
return {ErrorCodes::FailedToParse,
str::stream() << "The '" << IndexDescriptor::kIndexNameFieldName
<< "' field is a required property of an index specification"};
}
if (hasCollationField && *resolvedIndexVersion < IndexVersion::kV2) {
return {ErrorCodes::CannotCreateIndex,
str::stream() << "Invalid index specification " << indexSpec
<< "; cannot create an index with the '"
<< IndexDescriptor::kCollationFieldName
<< "' option and "
<< IndexDescriptor::kIndexVersionFieldName
<< "="
<< static_cast(*resolvedIndexVersion)};
}
if (!hasNamespaceField || !hasVersionField) {
BSONObjBuilder bob;
if (!hasNamespaceField) {
// We create a new index specification with the 'ns' field set as 'expectedNamespace' if
// the field was omitted.
bob.append(IndexDescriptor::kNamespaceFieldName, expectedNamespace.ns());
}
if (!hasVersionField) {
// We create a new index specification with the 'v' field set as 'defaultIndexVersion'
// if the field was omitted.
bob.append(IndexDescriptor::kIndexVersionFieldName,
static_cast(*resolvedIndexVersion));
}
bob.appendElements(indexSpec);
return bob.obj();
}
return indexSpec;
}
Status validateIdIndexSpec(const BSONObj& indexSpec) {
for (auto&& indexSpecElem : indexSpec) {
auto indexSpecElemFieldName = indexSpecElem.fieldNameStringData();
if (!allowedIdIndexFieldNames.count(indexSpecElemFieldName)) {
return {
ErrorCodes::InvalidIndexSpecificationOption,
str::stream() << "The field '" << indexSpecElemFieldName
<< "' is not valid for an _id index specification. Specification: "
<< indexSpec};
}
}
auto keyPatternElem = indexSpec[IndexDescriptor::kKeyPatternFieldName];
// validateIndexSpec() should have already verified that 'keyPatternElem' is an object.
invariant(keyPatternElem.type() == BSONType::Object);
if (SimpleBSONObjComparator::kInstance.evaluate(keyPatternElem.Obj() != BSON("_id" << 1))) {
return {ErrorCodes::BadValue,
str::stream() << "The field '" << IndexDescriptor::kKeyPatternFieldName
<< "' for an _id index must be {_id: 1}, but got "
<< keyPatternElem.Obj()};
}
return Status::OK();
}
/**
* Top-level index spec field names are validated here. When adding a new field with a document as
* value, is the the sub-module's responsibility to ensure that the content is valid and that only
* expected fields are present at creation time
*/
Status validateIndexSpecFieldNames(const BSONObj& indexSpec) {
if (MONGO_FAIL_POINT(skipIndexCreateFieldNameValidation)) {
return Status::OK();
}
for (auto&& indexSpecElem : indexSpec) {
auto indexSpecElemFieldName = indexSpecElem.fieldNameStringData();
if (!allowedFieldNames.count(indexSpecElemFieldName)) {
return {ErrorCodes::InvalidIndexSpecificationOption,
str::stream() << "The field '" << indexSpecElemFieldName
<< "' is not valid for an index specification. Specification: "
<< indexSpec};
}
}
return Status::OK();
}
StatusWith validateIndexSpecCollation(OperationContext* txn,
const BSONObj& indexSpec,
const CollatorInterface* defaultCollator) {
if (auto collationElem = indexSpec[IndexDescriptor::kCollationFieldName]) {
// validateIndexSpec() should have already verified that 'collationElem' is an object.
invariant(collationElem.type() == BSONType::Object);
auto collator = CollatorFactoryInterface::get(txn->getServiceContext())
->makeFromBSON(collationElem.Obj());
if (!collator.isOK()) {
return collator.getStatus();
}
if (collator.getValue()) {
// If the collator factory returned a non-null collator, then inject the entire
// collation specification into the index specification. This is necessary to fill
// in any options that the user omitted.
BSONObjBuilder bob;
for (auto&& indexSpecElem : indexSpec) {
if (IndexDescriptor::kCollationFieldName != indexSpecElem.fieldNameStringData()) {
bob.append(indexSpecElem);
}
}
bob.append(IndexDescriptor::kCollationFieldName,
collator.getValue()->getSpec().toBSON());
return bob.obj();
} else {
// If the collator factory returned a null collator (representing the "simple"
// collation), then we simply omit the "collation" from the index specification.
// This is desirable to make the representation for the "simple" collation
// consistent between v=1 and v=2 indexes.
return indexSpec.removeField(IndexDescriptor::kCollationFieldName);
}
} else if (defaultCollator) {
// validateIndexSpec() should have added the "v" field if it was not present and
// verified that 'versionElem' is a number.
auto versionElem = indexSpec[IndexDescriptor::kIndexVersionFieldName];
invariant(versionElem.isNumber());
if (IndexVersion::kV2 <= static_cast(versionElem.numberInt())) {
// The user did not specify an explicit collation for this index and the collection
// has a default collator. If we're building a v=2 index, then we should inherit the
// collection default. However, if we're building a v=1 index, then we're implicitly
// building an index that's using the "simple" collation.
BSONObjBuilder bob;
bob.appendElements(indexSpec);
bob.append(IndexDescriptor::kCollationFieldName, defaultCollator->getSpec().toBSON());
return bob.obj();
}
}
return indexSpec;
}
} // namespace index_key_validate
} // namespace mongo