summaryrefslogtreecommitdiff
path: root/src/mongo/db
diff options
context:
space:
mode:
authorAndy Schwerin <schwerin@10gen.com>2012-12-20 12:47:09 -0500
committerAndy Schwerin <schwerin@10gen.com>2012-12-20 12:47:09 -0500
commitd920d6bf42a45926da0909854fe72e1d391e1611 (patch)
treee0a38d6945772e593c79e1f4234fa29a4e9707a2 /src/mongo/db
parent61659ebd62559c840a1df22ce9910d6a94cab5af (diff)
parente223993deff1dfd1f054acd01c29759b56a9afb1 (diff)
downloadmongo-d920d6bf42a45926da0909854fe72e1d391e1611.tar.gz
Merge branch 'accept_new_docs'
Diffstat (limited to 'src/mongo/db')
-rw-r--r--src/mongo/db/auth/authorization_manager.cpp114
-rw-r--r--src/mongo/db/auth/authorization_manager.h6
-rw-r--r--src/mongo/db/auth/authorization_manager_test.cpp99
-rw-r--r--src/mongo/db/ops/update.cpp8
-rw-r--r--src/mongo/db/pdfile.cpp12
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 ) {