diff options
author | Andy Schwerin <schwerin@10gen.com> | 2012-12-20 12:47:09 -0500 |
---|---|---|
committer | Andy Schwerin <schwerin@10gen.com> | 2012-12-20 12:47:09 -0500 |
commit | d920d6bf42a45926da0909854fe72e1d391e1611 (patch) | |
tree | e0a38d6945772e593c79e1f4234fa29a4e9707a2 /src/mongo/db | |
parent | 61659ebd62559c840a1df22ce9910d6a94cab5af (diff) | |
parent | e223993deff1dfd1f054acd01c29759b56a9afb1 (diff) | |
download | mongo-d920d6bf42a45926da0909854fe72e1d391e1611.tar.gz |
Merge branch 'accept_new_docs'
Diffstat (limited to 'src/mongo/db')
-rw-r--r-- | src/mongo/db/auth/authorization_manager.cpp | 114 | ||||
-rw-r--r-- | src/mongo/db/auth/authorization_manager.h | 6 | ||||
-rw-r--r-- | src/mongo/db/auth/authorization_manager_test.cpp | 99 | ||||
-rw-r--r-- | src/mongo/db/ops/update.cpp | 8 | ||||
-rw-r--r-- | src/mongo/db/pdfile.cpp | 12 |
5 files changed, 234 insertions, 5 deletions
diff --git a/src/mongo/db/auth/authorization_manager.cpp b/src/mongo/db/auth/authorization_manager.cpp index 69e2876544d..5853f5527f2 100644 --- a/src/mongo/db/auth/authorization_manager.cpp +++ b/src/mongo/db/auth/authorization_manager.cpp @@ -29,6 +29,7 @@ #include "mongo/db/auth/privilege.h" #include "mongo/db/auth/privilege_set.h" #include "mongo/db/client.h" +#include "mongo/db/jsobj.h" #include "mongo/db/namespacestring.h" #include "mongo/db/security_common.h" #include "mongo/util/mongoutils/str.h" @@ -216,6 +217,119 @@ namespace { return Status::OK(); } + static inline Status _badValue(const char* reason, int location) { + return Status(ErrorCodes::BadValue, reason, location); + } + + static inline Status _badValue(const std::string& reason, int location) { + return Status(ErrorCodes::BadValue, reason, location); + } + + static inline StringData makeStringDataFromBSONElement(const BSONElement& element) { + return StringData(element.valuestr(), element.valuestrsize() - 1); + } + + static Status _checkRolesArray(const BSONElement& rolesElement) { + if (rolesElement.type() != Array) { + return _badValue("Role fields must be an array when present in system.users entries", + 0); + } + for (BSONObjIterator iter(rolesElement.embeddedObject()); iter.more(); iter.next()) { + BSONElement element = *iter; + if (element.type() != String || makeStringDataFromBSONElement(element).empty()) { + return _badValue("Roles must be non-empty strings.", 0); + } + } + return Status::OK(); + } + + Status AuthorizationManager::checkValidPrivilegeDocument(const StringData& dbname, + const BSONObj& doc) { + BSONElement userElement = doc[USERNAME_FIELD_NAME]; + BSONElement userSourceElement = doc[USERSOURCE_FIELD_NAME]; + BSONElement passwordElement = doc[PASSWORD_FIELD_NAME]; + BSONElement rolesElement = doc[ROLES_FIELD_NAME]; + BSONElement otherDBRolesElement = doc[OTHER_DB_ROLES_FIELD_NAME]; + BSONElement readOnlyElement = doc[READONLY_FIELD_NAME]; + + // Validate the "user" element. + if (userElement.type() != String) + return _badValue("system.users entry needs 'user' field to be a string", 14051); + if (makeStringDataFromBSONElement(userElement).empty()) + return _badValue("system.users entry needs 'user' field to be non-empty", 14053); + + // Must set exactly one of "userSource" and "pwd" fields. + if (userSourceElement.eoo() == passwordElement.eoo()) { + return _badValue("system.users entry must have either a 'pwd' field or a 'userSource' " + "field, but not both", 0); + } + + // Cannot have both "roles" and "readOnly" elements. + if (!rolesElement.eoo() && !readOnlyElement.eoo()) { + return _badValue("system.users entry must not have both 'roles' and 'readOnly' fields", + 0); + } + + // Validate the "pwd" element, if present. + if (!passwordElement.eoo()) { + if (passwordElement.type() != String) + return _badValue("system.users entry needs 'pwd' field to be a string", 14052); + if (makeStringDataFromBSONElement(passwordElement).empty()) + return _badValue("system.users entry needs 'pwd' field to be non-empty", 14054); + } + + // Validate the "userSource" element, if present. + if (!userSourceElement.eoo()) { + if (userSourceElement.type() != String || + makeStringDataFromBSONElement(userSourceElement).empty()) { + + return _badValue("system.users entry needs 'userSource' field to be a non-empty " + "string, if present", 0); + } + if (userSourceElement.str() == dbname) { + return _badValue(mongoutils::str::stream() << "'" << dbname << + "' is not a valid value for the userSource field in " << + dbname << ".system.users entries", + 0); + } + if (rolesElement.eoo()) { + return _badValue("system.users entry needs 'roles' field if 'userSource' field " + "is present.", 0); + } + } + + // Validate the "roles" element. + if (!rolesElement.eoo()) { + Status status = _checkRolesArray(rolesElement); + if (!status.isOK()) + return status; + } + + if (!otherDBRolesElement.eoo()) { + if (dbname != ADMIN_DBNAME) { + return _badValue("Only admin.system.users entries may contain 'otherDBRoles' " + "fields", 0); + } + if (rolesElement.eoo()) { + return _badValue("system.users entries with 'otherDBRoles' fields must contain " + "'roles' fields", 0); + } + if (otherDBRolesElement.type() != Object) { + return _badValue("'otherDBRoles' field must be an object when present in " + "system.users entries", 0); + } + for (BSONObjIterator iter(otherDBRolesElement.embeddedObject()); + iter.more(); iter.next()) { + + Status status = _checkRolesArray(*iter); + if (!status.isOK()) + return status; + } + } + + return Status::OK(); + } + AuthorizationManager::AuthorizationManager(AuthExternalState* externalState) { _externalState.reset(externalState); } diff --git a/src/mongo/db/auth/authorization_manager.h b/src/mongo/db/auth/authorization_manager.h index 7dc3a000242..6c51f65c858 100644 --- a/src/mongo/db/auth/authorization_manager.h +++ b/src/mongo/db/auth/authorization_manager.h @@ -59,6 +59,12 @@ namespace mongo { static const std::string SERVER_RESOURCE_NAME; static const std::string CLUSTER_RESOURCE_NAME; + // Checks to see if "doc" is a valid privilege document, assuming it is stored in the + // "system.users" collection of database "dbname". + // + // Returns Status::OK() if the document is good, or Status(ErrorCodes::BadValue), otherwise. + static Status checkValidPrivilegeDocument(const StringData& dbname, const BSONObj& doc); + // Takes ownership of the externalState. explicit AuthorizationManager(AuthExternalState* externalState); ~AuthorizationManager(); diff --git a/src/mongo/db/auth/authorization_manager_test.cpp b/src/mongo/db/auth/authorization_manager_test.cpp index 134ebfce51a..606f030dbbd 100644 --- a/src/mongo/db/auth/authorization_manager_test.cpp +++ b/src/mongo/db/auth/authorization_manager_test.cpp @@ -371,5 +371,104 @@ namespace { "anydb", principal, oldAndNewMixed, &result)); } + TEST(AuthorizationManagerTest, DocumentValidationCompatibility) { + Status (*check)(const StringData&, const BSONObj&) = + &AuthorizationManager::checkValidPrivilegeDocument; + + // Good documents, with and without "readOnly" fields. + ASSERT_OK(check("test", BSON("user" << "andy" << "pwd" << "a"))); + ASSERT_OK(check("test", BSON("user" << "andy" << "pwd" << "a" << "readOnly" << 1))); + ASSERT_OK(check("test", BSON("user" << "andy" << "pwd" << "a" << "readOnly" << false))); + ASSERT_OK(check("test", BSON("user" << "andy" << "pwd" << "a" << "readOnly" << "yes"))); + + // Must have a "pwd" field. + ASSERT_NOT_OK(check("test", BSON("user" << "andy"))); + + // "pwd" field must be a string. + ASSERT_NOT_OK(check("test", BSON("user" << "andy" << "pwd" << 100))); + + // "pwd" field string must not be empty. + ASSERT_NOT_OK(check("test", BSON("user" << "andy" << "pwd" << ""))); + + // Must have a "user" field. + ASSERT_NOT_OK(check("test", BSON("pwd" << "a"))); + + // "user" field must be a string. + ASSERT_NOT_OK(check("test", BSON("user" << 100 << "pwd" << "a"))); + + // "user" field string must not be empty. + ASSERT_NOT_OK(check("test", BSON("user" << "" << "pwd" << "a"))); + } + + TEST(AuthorizationManagerTest, DocumentValidationExtended) { + Status (*check)(const StringData&, const BSONObj&) = + &AuthorizationManager::checkValidPrivilegeDocument; + + // Document describing new-style user on "test". + ASSERT_OK(check("test", BSON("user" << "andy" << "pwd" << "a" << + "roles" << BSON_ARRAY("read")))); + + // Document giving roles on "test" to a user from "test2". + ASSERT_OK(check("test", BSON("user" << "andy" << "userSource" << "test2" << + "roles" << BSON_ARRAY("read")))); + + // Cannot have "userSource" field value == dbname. + ASSERT_NOT_OK(check("test", BSON("user" << "andy" << "userSource" << "test" << + "roles" << BSON_ARRAY("read")))); + + // Cannot have both "userSource" and "pwd" + ASSERT_NOT_OK(check("test", BSON("user" << "andy" << "userSource" << "test2" << + "pwd" << "a" << "roles" << BSON_ARRAY("read")))); + + // Cannot have an otherDBRoles field except in the admin database. + ASSERT_NOT_OK(check("test", + BSON("user" << "andy" << "userSource" << "test2" << + "roles" << BSON_ARRAY("read") << + "otherDBRoles" << BSON("test2" << BSON_ARRAY("readWrite"))))); + + ASSERT_OK(check("admin", + BSON("user" << "andy" << "userSource" << "test2" << + "roles" << BSON_ARRAY("read") << + "otherDBRoles" << BSON("test2" << BSON_ARRAY("readWrite"))))); + + // Must have "roles" to have "otherDBRoles". + ASSERT_NOT_OK(check("admin", + BSON("user" << "andy" << "pwd" << "a" << + "otherDBRoles" << BSON("test2" << BSON_ARRAY("readWrite"))))); + + ASSERT_OK(check("admin", + BSON("user" << "andy" << "pwd" << "a" << + "roles" << BSONArrayBuilder().arr() << + "otherDBRoles" << BSON("test2" << BSON_ARRAY("readWrite"))))); + + // "otherDBRoles" may be empty. + ASSERT_OK(check("admin", + BSON("user" << "andy" << "pwd" << "a" << + "roles" << BSONArrayBuilder().arr() << + "otherDBRoles" << BSONObjBuilder().obj()))); + + // Cannot omit "roles" if "userSource" is present. + ASSERT_NOT_OK(check("test", BSON("user" << "andy" << "userSource" << "test2"))); + + // Cannot have both "roles" and "readOnly". + ASSERT_NOT_OK(check("test", BSON("user" << "andy" << "pwd" << "a" << "readOnly" << 1 << + "roles" << BSON_ARRAY("read")))); + + // Roles must be strings, not empty. + ASSERT_NOT_OK(check("test", BSON("user" << "andy" << "pwd" << "a" << + "roles" << BSON_ARRAY("read" << "")))); + + ASSERT_NOT_OK(check("test", BSON("user" << "andy" << "pwd" << "a" << + "roles" << BSON_ARRAY(1 << "read")))); + + // Multiple roles OK. + ASSERT_OK(check("test", BSON("user" << "andy" << "pwd" << "a" << + "roles" << BSON_ARRAY("dbAdmin" << "read")))); + + // Empty roles list OK. + ASSERT_OK(check("test", BSON("user" << "andy" << "pwd" << "a" << + "roles" << BSONArrayBuilder().arr()))); + } + } // namespace } // namespace mongo diff --git a/src/mongo/db/ops/update.cpp b/src/mongo/db/ops/update.cpp index 236ab24b013..ccd19d470b3 100644 --- a/src/mongo/db/ops/update.cpp +++ b/src/mongo/db/ops/update.cpp @@ -356,7 +356,13 @@ namespace mongo { c->prepareToTouchEarlierIterate(); } - if ( modsIsIndexed <= 0 && mss->canApplyInPlace() ) { + // If we've made it this far, "ns" must contain a valid collection name, and so + // is of the form "db.collection". Therefore, the following expression must + // always be valid. "system.users" updates must never be done in place, in + // order to ensure that they are validated inside DataFileMgr::updateRecord(.). + bool isSystemUsersMod = (NamespaceString(ns).coll == "system.users"); + + if ( modsIsIndexed <= 0 && mss->canApplyInPlace() && !isSystemUsersMod ) { mss->applyModsInPlace( true );// const_cast<BSONObj&>(onDisk) ); DEBUGUPDATE( "\t\t\t doing in place update" ); diff --git a/src/mongo/db/pdfile.cpp b/src/mongo/db/pdfile.cpp index 8a7c3ef264a..7fb7501bd91 100644 --- a/src/mongo/db/pdfile.cpp +++ b/src/mongo/db/pdfile.cpp @@ -32,6 +32,7 @@ _ disallow system* manipulations from the database. #include <list> #include "mongo/base/counter.h" +#include "mongo/db/auth/authorization_manager.h" #include "mongo/db/pdfile_private.h" #include "mongo/db/background.h" #include "mongo/db/btree.h" @@ -1131,6 +1132,11 @@ namespace mongo { objNew = b.obj(); } + NamespaceString nsstring(ns); + if (nsstring.coll == "system.users") { + uassertStatusOK(AuthorizationManager::checkValidPrivilegeDocument(nsstring.db, objNew)); + } + /* duplicate key check. we descend the btree twice - once for this check, and once for the actual inserts, further below. that is suboptimal, but it's pretty complicated to do it the other way without rollbacks... */ @@ -1369,10 +1375,8 @@ namespace mongo { else if ( legalClientSystemNS( ns , true ) ) { if ( obuf && strstr( ns , ".system.users" ) ) { BSONObj t( reinterpret_cast<const char *>( obuf ) ); - uassert( 14051 , "system.users entry needs 'user' field to be a string" , t["user"].type() == String ); - uassert( 14052 , "system.users entry needs 'pwd' field to be a string" , t["pwd"].type() == String ); - uassert( 14053 , "system.users entry needs 'user' field to be non-empty" , t["user"].String().size() ); - uassert( 14054 , "system.users entry needs 'pwd' field to be non-empty" , t["pwd"].String().size() ); + uassertStatusOK(AuthorizationManager::checkValidPrivilegeDocument( + nsToDatabaseSubstring(ns), t)); } } else if ( !god ) { |