summaryrefslogtreecommitdiff
path: root/src/mongo
diff options
context:
space:
mode:
authorSpencer T Brody <spencer@10gen.com>2013-08-26 20:02:22 -0400
committerSpencer T Brody <spencer@10gen.com>2013-09-06 11:29:57 -0400
commit25785ee485220aa468fbc3eedfec1f05b36d502a (patch)
tree65bc1cba1e163291b5596280ded1868dd08562fc /src/mongo
parentb9a1874e3e839aa130fe73d112470debb33e59b8 (diff)
downloadmongo-25785ee485220aa468fbc3eedfec1f05b36d502a.tar.gz
SERVER-6246 Change user management commands to use the new v2 style user documents
Diffstat (limited to 'src/mongo')
-rw-r--r--src/mongo/SConscript2
-rw-r--r--src/mongo/db/auth/authorization_manager.cpp85
-rw-r--r--src/mongo/db/auth/authorization_manager.h18
-rw-r--r--src/mongo/db/auth/authz_manager_external_state.h5
-rw-r--r--src/mongo/db/auth/authz_manager_external_state_d.cpp27
-rw-r--r--src/mongo/db/auth/authz_manager_external_state_d.h3
-rw-r--r--src/mongo/db/auth/authz_manager_external_state_mock.cpp4
-rw-r--r--src/mongo/db/auth/authz_manager_external_state_mock.h4
-rw-r--r--src/mongo/db/auth/authz_manager_external_state_s.cpp32
-rw-r--r--src/mongo/db/auth/authz_manager_external_state_s.h3
-rw-r--r--src/mongo/db/auth/privilege_document_parser.cpp66
-rw-r--r--src/mongo/db/auth/privilege_document_parser.h5
-rw-r--r--src/mongo/db/auth/role_name.cpp4
-rw-r--r--src/mongo/db/auth/role_name.h3
-rw-r--r--src/mongo/db/auth/user_name.cpp4
-rw-r--r--src/mongo/db/auth/user_name.h3
-rw-r--r--src/mongo/db/auth/user_set_test.cpp5
-rw-r--r--src/mongo/db/commands/SConscript14
-rw-r--r--src/mongo/db/commands/user_management_commands.cpp374
-rw-r--r--src/mongo/db/commands/user_management_commands_parser.cpp285
-rw-r--r--src/mongo/db/commands/user_management_commands_parser.h55
-rw-r--r--src/mongo/db/commands/user_management_commands_parser_test.cpp412
22 files changed, 1051 insertions, 362 deletions
diff --git a/src/mongo/SConscript b/src/mongo/SConscript
index 09d0d37fc2b..e67512f31d8 100644
--- a/src/mongo/SConscript
+++ b/src/mongo/SConscript
@@ -17,6 +17,7 @@ Import("darwin windows solaris linux nix")
env.SConscript(['base/SConscript',
'db/auth/SConscript',
+ 'db/commands/SConscript',
'db/exec/SConscript',
'db/fts/SConscript',
'db/ops/SConscript',
@@ -356,6 +357,7 @@ env.StaticLibrary("coredb", [
"s/shardconnection.cpp",
],
LIBDEPS=['db/auth/serverauth',
+ 'db/commands/usercommandsparser',
'db/common',
'server_parameters',
'geoparser',
diff --git a/src/mongo/db/auth/authorization_manager.cpp b/src/mongo/db/auth/authorization_manager.cpp
index 513be44b3f0..0158134251b 100644
--- a/src/mongo/db/auth/authorization_manager.cpp
+++ b/src/mongo/db/auth/authorization_manager.cpp
@@ -324,9 +324,9 @@ namespace {
return _externalState->updatePrivilegeDocument(user, updateObj);
}
- Status AuthorizationManager::removePrivilegeDocuments(const std::string& dbname,
- const BSONObj& query) const {
- return _externalState->removePrivilegeDocuments(dbname, query);
+ Status AuthorizationManager::removePrivilegeDocuments(const BSONObj& query,
+ int* numRemoved) const {
+ return _externalState->removePrivilegeDocuments(query, numRemoved);
}
ActionSet AuthorizationManager::getAllUserActions() const {
@@ -339,6 +339,54 @@ namespace {
return allActions;
}
+ bool AuthorizationManager::roleExists(const RoleName& role) const {
+ bool isAdminDB = role.getDB() == "admin";
+
+ if (role.getRole() == SYSTEM_ROLE_READ) {
+ return true;
+ }
+ else if (role.getRole() == SYSTEM_ROLE_READ_WRITE) {
+ return true;
+ }
+ else if (role.getRole() == SYSTEM_ROLE_USER_ADMIN) {
+ return true;
+ }
+ else if (role.getRole() == SYSTEM_ROLE_DB_ADMIN) {
+ return true;
+ }
+ else if (role.getRole() == AuthorizationManager::SYSTEM_ROLE_V0_READ) {
+ return true;
+ }
+ else if (role.getRole() == AuthorizationManager::SYSTEM_ROLE_V0_READ_WRITE) {
+ return true;
+ }
+ else if (isAdminDB && role.getRole() == SYSTEM_ROLE_READ_ANY_DB) {
+ return true;
+ }
+ else if (isAdminDB && role.getRole() == SYSTEM_ROLE_READ_WRITE_ANY_DB) {
+ return true;
+ }
+ else if (isAdminDB && role.getRole() == SYSTEM_ROLE_USER_ADMIN_ANY_DB) {
+ return true;
+ }
+ else if (isAdminDB && role.getRole() == SYSTEM_ROLE_DB_ADMIN_ANY_DB) {
+ return true;
+ }
+ else if (isAdminDB && role.getRole() == SYSTEM_ROLE_CLUSTER_ADMIN) {
+ return true;
+ }
+ else if (isAdminDB && role.getRole() == AuthorizationManager::SYSTEM_ROLE_V0_ADMIN_READ) {
+ return true;
+ }
+ else if (isAdminDB &&
+ role.getRole() == AuthorizationManager::SYSTEM_ROLE_V0_ADMIN_READ_WRITE) {
+ return true;
+ } else {
+ // TODO(spencer): Check the role graph to see if this is a valid user-defined-role
+ return false;
+ }
+
+ }
/**
* Adds to "outPrivileges" the privileges associated with having the named "role" on "dbname".
*
@@ -510,13 +558,42 @@ namespace {
unordered_map<UserName, User*>::iterator it = _userCache.find(user->getName());
massert(17052,
mongoutils::str::stream() <<
- "Invalidating cache for user " << user->getName().toString() <<
+ "Invalidating cache for user " << user->getName().getFullName() <<
" failed as it is not present in the user cache",
it != _userCache.end() && it->second == user);
_userCache.erase(it);
user->invalidate();
}
+ void AuthorizationManager::invalidateUserByName(const UserName& userName) {
+ boost::lock_guard<boost::mutex> lk(_lock);
+
+ unordered_map<UserName, User*>::iterator it = _userCache.find(userName);
+ if (it == _userCache.end()) {
+ return;
+ }
+
+ User* user = it->second;
+ _userCache.erase(it);
+ user->invalidate();
+ }
+
+ void AuthorizationManager::invalidateUsersFromDB(const std::string& dbname) {
+ boost::lock_guard<boost::mutex> lk(_lock);
+
+ unordered_map<UserName, User*>::iterator it = _userCache.begin();
+ while (it != _userCache.end()) {
+ User* user = it->second;
+ if (user->getName().getDB() == dbname) {
+ _userCache.erase(it++);
+ user->invalidate();
+ } else {
+ ++it;
+ }
+ }
+ }
+
+
void AuthorizationManager::addInternalUser(User* user) {
boost::lock_guard<boost::mutex> lk(_lock);
_userCache.insert(make_pair(user->getName(), user));
diff --git a/src/mongo/db/auth/authorization_manager.h b/src/mongo/db/auth/authorization_manager.h
index dee227114db..f637639247c 100644
--- a/src/mongo/db/auth/authorization_manager.h
+++ b/src/mongo/db/auth/authorization_manager.h
@@ -112,7 +112,8 @@ namespace mongo {
Status updatePrivilegeDocument(const UserName& user, const BSONObj& updateObj) const;
// Removes users for the given database matching the given query.
- Status removePrivilegeDocuments(const std::string& dbname, const BSONObj& query) const;
+ // Writes into *numRemoved the number of user documents that were modified.
+ Status removePrivilegeDocuments(const BSONObj& query, int* numRemoved) const;
// Checks to see if "doc" is a valid privilege document, assuming it is stored in the
// "system.users" collection of database "dbname".
@@ -152,12 +153,27 @@ namespace mongo {
void invalidateUser(User* user);
/**
+ * Marks the given user as invalid and removes it from the user cache.
+ */
+ void invalidateUserByName(const UserName& user);
+
+ /**
+ * Invalidates all users who's source is "dbname" and removes them from the user cache.
+ */
+ void invalidateUsersFromDB(const std::string& dbname);
+
+ /**
* Inserts the given user directly into the _userCache. Used to add the internalSecurity
* user into the cache at process startup.
*/
void addInternalUser(User* user);
/**
+ * Returns true if the role name given refers to a valid system or user defined role.
+ */
+ bool roleExists(const RoleName& role) const;
+
+ /**
* Initializes the user cache with User objects for every v0 and v1 user document in the
* system, by reading the system.users collection of every database. If this function
* returns a non-ok Status, the _userCache should be considered corrupt and must be
diff --git a/src/mongo/db/auth/authz_manager_external_state.h b/src/mongo/db/auth/authz_manager_external_state.h
index 89ecbc3603e..97b0aeb5797 100644
--- a/src/mongo/db/auth/authz_manager_external_state.h
+++ b/src/mongo/db/auth/authz_manager_external_state.h
@@ -62,9 +62,8 @@ namespace mongo {
const BSONObj& updateObj) = 0;
// Removes users for the given database matching the given query.
- // TODO(spencer): remove dbname argument once users are only written into the admin db
- virtual Status removePrivilegeDocuments(const std::string& dbname,
- const BSONObj& query) = 0;
+ // Writes into *numRemoved the number of user documents that were modified.
+ virtual Status removePrivilegeDocuments(const BSONObj& query, int* numRemoved) = 0;
/**
* Puts into the *dbnames vector the name of every database in the cluster.
diff --git a/src/mongo/db/auth/authz_manager_external_state_d.cpp b/src/mongo/db/auth/authz_manager_external_state_d.cpp
index fb7a999f268..5104ecef786 100644
--- a/src/mongo/db/auth/authz_manager_external_state_d.cpp
+++ b/src/mongo/db/auth/authz_manager_external_state_d.cpp
@@ -17,6 +17,7 @@
#include "mongo/db/auth/authz_manager_external_state_d.h"
#include "mongo/base/status.h"
+#include "mongo/db/auth/authorization_manager.h"
#include "mongo/db/auth/user_name.h"
#include "mongo/db/client.h"
#include "mongo/db/d_concurrency.h"
@@ -38,7 +39,7 @@ namespace {
Status AuthzManagerExternalStateMongod::insertPrivilegeDocument(const string& dbname,
const BSONObj& userObj) {
try {
- string userNS = dbname + ".system.users";
+ const std::string userNS = "admin.system.users";
DBDirectClient client;
{
Client::GodScope gs;
@@ -57,9 +58,11 @@ namespace {
return Status::OK();
}
if (res.hasField("code") && res["code"].Int() == ASSERT_ID_DUPKEY) {
+ std::string name = userObj[AuthorizationManager::USER_NAME_FIELD_NAME].String();
+ std::string source = userObj[AuthorizationManager::USER_SOURCE_FIELD_NAME].String();
return Status(ErrorCodes::DuplicateKey,
- mongoutils::str::stream() << "User \"" << userObj["user"].String() <<
- "\" already exists on database \"" << dbname << "\"");
+ mongoutils::str::stream() << "User \"" << name << "@" << source <<
+ "\" already exists");
}
return Status(ErrorCodes::UserModificationFailed, errstr);
} catch (const DBException& e) {
@@ -70,7 +73,7 @@ namespace {
Status AuthzManagerExternalStateMongod::updatePrivilegeDocument(
const UserName& user, const BSONObj& updateObj) {
try {
- string userNS = mongoutils::str::stream() << user.getDB() << ".system.users";
+ const std::string userNS = "admin.system.users";
DBDirectClient client;
{
Client::GodScope gs;
@@ -80,7 +83,8 @@ namespace {
Lock::GlobalWrite w;
// Client::WriteContext ctx(userNS);
client.update(userNS,
- QUERY("user" << user.getUser() << "userSource" << BSONNULL),
+ QUERY(AuthorizationManager::USER_NAME_FIELD_NAME << user.getUser() <<
+ AuthorizationManager::USER_SOURCE_FIELD_NAME << user.getDB()),
updateObj);
}
@@ -105,10 +109,10 @@ namespace {
}
}
- Status AuthzManagerExternalStateMongod::removePrivilegeDocuments(const string& dbname,
- const BSONObj& query) {
+ Status AuthzManagerExternalStateMongod::removePrivilegeDocuments(const BSONObj& query,
+ int* numRemoved) {
try {
- string userNS = dbname + ".system.users";
+ const std::string userNS = "admin.system.users";
DBDirectClient client;
{
Client::GodScope gs;
@@ -127,12 +131,7 @@ namespace {
return Status(ErrorCodes::UserModificationFailed, errstr);
}
- int numUpdated = res["n"].numberInt();
- if (numUpdated == 0) {
- return Status(ErrorCodes::UserNotFound,
- mongoutils::str::stream() << "No users found on database \"" << dbname
- << "\" matching query: " << query.toString());
- }
+ *numRemoved = res["n"].numberInt();
return Status::OK();
} catch (const DBException& e) {
return e.toStatus();
diff --git a/src/mongo/db/auth/authz_manager_external_state_d.h b/src/mongo/db/auth/authz_manager_external_state_d.h
index 1d0b0eb9d8e..e706fd40d2e 100644
--- a/src/mongo/db/auth/authz_manager_external_state_d.h
+++ b/src/mongo/db/auth/authz_manager_external_state_d.h
@@ -41,8 +41,7 @@ namespace mongo {
virtual Status updatePrivilegeDocument(const UserName& user,
const BSONObj& updateObj);
- virtual Status removePrivilegeDocuments(const std::string& dbname,
- const BSONObj& query);
+ virtual Status removePrivilegeDocuments(const BSONObj& query, int* numRemoved);
virtual Status getAllDatabaseNames(std::vector<std::string>* dbnames);
diff --git a/src/mongo/db/auth/authz_manager_external_state_mock.cpp b/src/mongo/db/auth/authz_manager_external_state_mock.cpp
index 0cdd2e20462..82afca81614 100644
--- a/src/mongo/db/auth/authz_manager_external_state_mock.cpp
+++ b/src/mongo/db/auth/authz_manager_external_state_mock.cpp
@@ -36,8 +36,8 @@ namespace mongo {
return Status(ErrorCodes::InternalError, "Not implemented in mock.");
}
- Status AuthzManagerExternalStateMock::removePrivilegeDocuments(const std::string& dbname,
- const BSONObj& query) {
+ Status AuthzManagerExternalStateMock::removePrivilegeDocuments(const BSONObj& query,
+ int* numRemoved) {
return Status(ErrorCodes::InternalError, "Not implemented in mock.");
}
diff --git a/src/mongo/db/auth/authz_manager_external_state_mock.h b/src/mongo/db/auth/authz_manager_external_state_mock.h
index e835966929e..4c045be3b50 100644
--- a/src/mongo/db/auth/authz_manager_external_state_mock.h
+++ b/src/mongo/db/auth/authz_manager_external_state_mock.h
@@ -44,8 +44,8 @@ namespace mongo {
virtual Status updatePrivilegeDocument(const UserName& user,
const BSONObj& updateObj);
- virtual Status removePrivilegeDocuments(const std::string& dbname,
- const BSONObj& query);
+ // no-op for the mock
+ virtual Status removePrivilegeDocuments(const BSONObj& query, int* numRemoved);
void clearPrivilegeDocuments();
diff --git a/src/mongo/db/auth/authz_manager_external_state_s.cpp b/src/mongo/db/auth/authz_manager_external_state_s.cpp
index ed6fd9e9828..d23edfac597 100644
--- a/src/mongo/db/auth/authz_manager_external_state_s.cpp
+++ b/src/mongo/db/auth/authz_manager_external_state_s.cpp
@@ -19,6 +19,7 @@
#include <string>
#include "mongo/client/dbclientinterface.h"
+#include "mongo/db/auth/authorization_manager.h"
#include "mongo/db/auth/user_name.h"
#include "mongo/db/jsobj.h"
#include "mongo/s/type_database.h"
@@ -69,7 +70,7 @@ namespace {
Status AuthzManagerExternalStateMongos::insertPrivilegeDocument(const string& dbname,
const BSONObj& userObj) {
try {
- string userNS = dbname + ".system.users";
+ const std::string userNS = "admin.system.users";
scoped_ptr<ScopedDbConnection> conn(getConnectionForUsersCollection(userNS));
conn->get()->insert(userNS, userObj);
@@ -83,9 +84,11 @@ namespace {
return Status::OK();
}
if (res.hasField("code") && res["code"].Int() == ASSERT_ID_DUPKEY) {
+ std::string name = userObj[AuthorizationManager::USER_NAME_FIELD_NAME].String();
+ std::string source = userObj[AuthorizationManager::USER_SOURCE_FIELD_NAME].String();
return Status(ErrorCodes::DuplicateKey,
- mongoutils::str::stream() << "User \"" << userObj["user"].String() <<
- "\" already exists on database \"" << dbname << "\"");
+ mongoutils::str::stream() << "User \"" << name << "@" << source <<
+ "\" already exists");
}
return Status(ErrorCodes::UserModificationFailed, errstr);
} catch (const DBException& e) {
@@ -96,12 +99,14 @@ namespace {
Status AuthzManagerExternalStateMongos::updatePrivilegeDocument(
const UserName& user, const BSONObj& updateObj) {
try {
- string userNS = mongoutils::str::stream() << user.getDB() << ".system.users";
+ const std::string userNS = "admin.system.users";
scoped_ptr<ScopedDbConnection> conn(getConnectionForUsersCollection(userNS));
- conn->get()->update(userNS,
- QUERY("user" << user.getUser() << "userSource" << BSONNULL),
- updateObj);
+ conn->get()->update(
+ userNS,
+ QUERY(AuthorizationManager::USER_NAME_FIELD_NAME << user.getUser() <<
+ AuthorizationManager::USER_SOURCE_FIELD_NAME << user.getDB()),
+ updateObj);
// 30 second timeout for w:majority
BSONObj res = conn->get()->getLastErrorDetailed(false, false, -1, 30*1000);
@@ -126,10 +131,10 @@ namespace {
}
}
- Status AuthzManagerExternalStateMongos::removePrivilegeDocuments(const string& dbname,
- const BSONObj& query) {
+ Status AuthzManagerExternalStateMongos::removePrivilegeDocuments(const BSONObj& query,
+ int* numRemoved) {
try {
- string userNS = dbname + ".system.users";
+ string userNS = "admin.system.users";
scoped_ptr<ScopedDbConnection> conn(getConnectionForUsersCollection(userNS));
conn->get()->remove(userNS, query);
@@ -143,12 +148,7 @@ namespace {
return Status(ErrorCodes::UserModificationFailed, err);
}
- int numUpdated = res["n"].numberInt();
- if (numUpdated == 0) {
- return Status(ErrorCodes::UserNotFound,
- mongoutils::str::stream() << "No users found on database \"" << dbname
- << "\" matching query: " << query.toString());
- }
+ *numRemoved = res["n"].numberInt();
return Status::OK();
} catch (const DBException& e) {
return e.toStatus();
diff --git a/src/mongo/db/auth/authz_manager_external_state_s.h b/src/mongo/db/auth/authz_manager_external_state_s.h
index 12cbfb667f7..ab711ef3123 100644
--- a/src/mongo/db/auth/authz_manager_external_state_s.h
+++ b/src/mongo/db/auth/authz_manager_external_state_s.h
@@ -41,8 +41,7 @@ namespace mongo {
virtual Status updatePrivilegeDocument(const UserName& user,
const BSONObj& updateObj);
- virtual Status removePrivilegeDocuments(const std::string& dbname,
- const BSONObj& query);
+ virtual Status removePrivilegeDocuments(const BSONObj& query, int* numRemoved);
virtual Status getAllDatabaseNames(std::vector<std::string>* dbnames);
diff --git a/src/mongo/db/auth/privilege_document_parser.cpp b/src/mongo/db/auth/privilege_document_parser.cpp
index b9ebb9b3386..c30f2e0b89c 100644
--- a/src/mongo/db/auth/privilege_document_parser.cpp
+++ b/src/mongo/db/auth/privilege_document_parser.cpp
@@ -452,6 +452,41 @@ namespace {
return Status::OK();
}
+ Status V2PrivilegeDocumentParser::checkValidRoleObject(
+ const BSONObj& roleObject) const {
+ BSONElement roleNameElement = roleObject[ROLE_NAME_FIELD_NAME];
+ BSONElement roleSourceElement = roleObject[ROLE_SOURCE_FIELD_NAME];
+ BSONElement canDelegateElement = roleObject[ROLE_CAN_DELEGATE_FIELD_NAME];
+ BSONElement hasRoleElement = roleObject[ROLE_HAS_ROLE_FIELD_NAME];
+
+ if (roleNameElement.type() != String ||
+ makeStringDataFromBSONElement(roleNameElement).empty()) {
+ return Status(ErrorCodes::UnsupportedFormat,
+ "Role names must be non-empty strings");
+ }
+ if (roleSourceElement.type() != String ||
+ makeStringDataFromBSONElement(roleSourceElement).empty()) {
+ return Status(ErrorCodes::UnsupportedFormat,
+ "Role source must be non-empty strings");
+ }
+ if (canDelegateElement.type() != Bool) {
+ return Status(ErrorCodes::UnsupportedFormat,
+ "Entries in 'roles' array need a 'canDelegate' boolean field");
+ }
+ if (hasRoleElement.type() != Bool) {
+ return Status(ErrorCodes::UnsupportedFormat,
+ "Entries in 'roles' array need a 'hasRole' boolean field");
+ }
+
+ if (!hasRoleElement.Bool() && !canDelegateElement.Bool()) {
+ return Status(ErrorCodes::UnsupportedFormat,
+ "At least one of 'canDelegate' and 'hasRole' must be true for "
+ "every role in the 'roles' array");
+ }
+
+ return Status::OK();
+ }
+
Status V2PrivilegeDocumentParser::initializeUserRolesFromPrivilegeDocument(
User* user, const BSONObj& privDoc, const StringData&) const {
@@ -469,38 +504,21 @@ namespace {
}
BSONObj roleObject = (*it).Obj();
+ Status status = checkValidRoleObject(roleObject);
+ if (!status.isOK()) {
+ return status;
+ }
+
BSONElement roleNameElement = roleObject[ROLE_NAME_FIELD_NAME];
BSONElement roleSourceElement = roleObject[ROLE_SOURCE_FIELD_NAME];
BSONElement canDelegateElement = roleObject[ROLE_CAN_DELEGATE_FIELD_NAME];
BSONElement hasRoleElement = roleObject[ROLE_HAS_ROLE_FIELD_NAME];
- if (roleNameElement.type() != String ||
- makeStringDataFromBSONElement(roleNameElement).empty()) {
- return Status(ErrorCodes::UnsupportedFormat,
- "Role names must be non-empty strings");
- }
- if (roleSourceElement.type() != String ||
- makeStringDataFromBSONElement(roleSourceElement).empty()) {
- return Status(ErrorCodes::UnsupportedFormat,
- "Role source must be non-empty strings");
- }
- if (canDelegateElement.type() != Bool) {
- return Status(ErrorCodes::UnsupportedFormat,
- "Entries in 'roles' array need a 'canDelegate' boolean field");
- }
- if (hasRoleElement.type() != Bool) {
- return Status(ErrorCodes::UnsupportedFormat,
- "Entries in 'roles' array need a 'hasRole' boolean field");
- }
-
if (hasRoleElement.Bool()) {
user->addRole(RoleName(roleNameElement.String(), roleSourceElement.String()));
- } else if (canDelegateElement.Bool()) {
+ }
+ if (canDelegateElement.Bool()) {
// TODO(spencer): record the fact that this user can delegate this role
- } else {
- return Status(ErrorCodes::UnsupportedFormat,
- "At least one of 'canDelegate' and 'hasRole' must be true for "
- "every role in the 'roles' array");
}
}
return Status::OK();
diff --git a/src/mongo/db/auth/privilege_document_parser.h b/src/mongo/db/auth/privilege_document_parser.h
index 19aa0d12ac5..ee24c79267b 100644
--- a/src/mongo/db/auth/privilege_document_parser.h
+++ b/src/mongo/db/auth/privilege_document_parser.h
@@ -90,6 +90,11 @@ namespace mongo {
virtual Status checkValidPrivilegeDocument(const StringData& dbname,
const BSONObj& doc) const;
+ /**
+ * Returns Status::OK() iff the given BSONObj describes a valid element from a roles array.
+ */
+ Status checkValidRoleObject(const BSONObj& roleObject) const;
+
virtual std::string extractUserNameFromPrivilegeDocument(const BSONObj& doc) const;
virtual Status initializeUserCredentialsFromPrivilegeDocument(User* user,
diff --git a/src/mongo/db/auth/role_name.cpp b/src/mongo/db/auth/role_name.cpp
index 8c66232abb7..28bf342bd18 100644
--- a/src/mongo/db/auth/role_name.cpp
+++ b/src/mongo/db/auth/role_name.cpp
@@ -60,4 +60,8 @@ namespace mongo {
return new RoleNameSetIterator(_begin, _end);
}
+ std::ostream& operator<<(std::ostream& os, const RoleName& name) {
+ return os << name.getFullName();
+ }
+
} // namespace mongo
diff --git a/src/mongo/db/auth/role_name.h b/src/mongo/db/auth/role_name.h
index 130f7a98e70..9b0fc796631 100644
--- a/src/mongo/db/auth/role_name.h
+++ b/src/mongo/db/auth/role_name.h
@@ -16,6 +16,7 @@
#pragma once
+#include <iosfwd>
#include <string>
#include <boost/scoped_ptr.hpp>
@@ -77,6 +78,8 @@ namespace mongo {
return lhs.getFullName() < rhs.getFullName();
}
+ std::ostream& operator<<(std::ostream& os, const RoleName& name);
+
/**
* Iterator over an unspecified container of RoleName objects.
diff --git a/src/mongo/db/auth/user_name.cpp b/src/mongo/db/auth/user_name.cpp
index 59e7984e1a8..db3d25a69ec 100644
--- a/src/mongo/db/auth/user_name.cpp
+++ b/src/mongo/db/auth/user_name.cpp
@@ -35,4 +35,8 @@ namespace mongo {
_splitPoint = user.size();
}
+ std::ostream& operator<<(std::ostream& os, const UserName& name) {
+ return os << name.getFullName();
+ }
+
} // namespace mongo
diff --git a/src/mongo/db/auth/user_name.h b/src/mongo/db/auth/user_name.h
index 6efa7b695ed..acee87f8ada 100644
--- a/src/mongo/db/auth/user_name.h
+++ b/src/mongo/db/auth/user_name.h
@@ -15,6 +15,7 @@
#pragma once
+#include <iosfwd>
#include <string>
#include "mongo/base/string_data.h"
@@ -68,4 +69,6 @@ namespace mongo {
return lhs.getFullName() < rhs.getFullName();
}
+ std::ostream& operator<<(std::ostream& os, const UserName& name);
+
} // namespace mongo
diff --git a/src/mongo/db/auth/user_set_test.cpp b/src/mongo/db/auth/user_set_test.cpp
index 3a2141855c6..fb30d00bee7 100644
--- a/src/mongo/db/auth/user_set_test.cpp
+++ b/src/mongo/db/auth/user_set_test.cpp
@@ -25,11 +25,6 @@
#define ASSERT_NULL(EXPR) ASSERT_FALSE((EXPR))
namespace mongo {
-
- static inline std::ostream& operator<<(std::ostream& os, const UserName& uname) {
- return os << uname.toString();
- }
-
namespace {
TEST(UserSetTest, BasicTest) {
diff --git a/src/mongo/db/commands/SConscript b/src/mongo/db/commands/SConscript
new file mode 100644
index 00000000000..0e01774275c
--- /dev/null
+++ b/src/mongo/db/commands/SConscript
@@ -0,0 +1,14 @@
+# -*- mode: python -*-
+
+Import("env")
+
+env.StaticLibrary('usercommandsparser',
+ ['user_management_commands_parser.cpp'],
+ LIBDEPS=['$BUILD_DIR/mongo/db/auth/authcore',
+ '$BUILD_DIR/mongo/bson'])
+
+env.CppUnitTest('user_management_commands_parser_test', 'user_management_commands_parser_test.cpp',
+ LIBDEPS=['usercommandsparser',
+ '$BUILD_DIR/mongo/mongocommon',
+ '$BUILD_DIR/mongo/db/auth/authmocks'],
+ NO_CRUTCH=True)
diff --git a/src/mongo/db/commands/user_management_commands.cpp b/src/mongo/db/commands/user_management_commands.cpp
index 2a40a3a4e71..48409a43bf5 100644
--- a/src/mongo/db/commands/user_management_commands.cpp
+++ b/src/mongo/db/commands/user_management_commands.cpp
@@ -27,7 +27,9 @@
#include "mongo/db/auth/authorization_manager.h"
#include "mongo/db/auth/authorization_manager_global.h"
#include "mongo/db/auth/privilege.h"
+#include "mongo/db/auth/user.h"
#include "mongo/db/commands.h"
+#include "mongo/db/commands/user_management_commands_parser.h"
#include "mongo/db/jsobj.h"
#include "mongo/platform/unordered_set.h"
#include "mongo/util/mongoutils/str.h"
@@ -83,89 +85,28 @@ namespace mongo {
out->push_back(Privilege(dbname, actions));
}
- struct CreateUserArgs {
- std::string userName;
- std::string clearTextPassword;
- std::string userSource; // TODO(spencer): remove this once we're using v2 user format
- bool readOnly; // TODO(spencer): remove this once we're using the new v2 user format
- BSONObj extraData; // Owned by the owner of the command object used to call createUser
- BSONArray roles; // Owned by the owner of the command object used to call createUser
- // Owned by the owner of the command object used to call createUser
- // TODO(spencer): remove otherDBRoles once we're using the new v2 user format
- BSONObj otherDBRoles;
- bool hasPassword;
- bool hasUserSource;
- bool hasReadOnly;
- bool hasExtraData;
- bool hasRoles;
- bool hasOtherDBRoles;
- CreateUserArgs() : readOnly(false), hasPassword(false), hasUserSource(false),
- hasReadOnly(false), hasExtraData(false), hasRoles(false),
- hasOtherDBRoles(false) {}
- };
-
- // TODO: The bulk of the implementation of this will need to change once we're using the
- // new v2 authorization storage format.
bool run(const string& dbname,
BSONObj& cmdObj,
int options,
string& errmsg,
BSONObjBuilder& result,
bool fromRepl) {
- CreateUserArgs args;
- Status status = _parseAndValidateInput(dbname, cmdObj, &args);
- if (!status.isOK()) {
- addStatus(status, result);
- return false;
- }
-
- std::string password = DBClientWithCommands::createPasswordDigest(
- args.userName, args.clearTextPassword);
-
-
- BSONObjBuilder userObjBuilder;
- userObjBuilder.append("_id", OID::gen());
- userObjBuilder.append("user", args.userName);
- if (args.hasPassword) {
- userObjBuilder.append("pwd", password);
- }
-
- if (args.hasUserSource) {
- userObjBuilder.append("userSource", args.userSource);
- }
-
- if (args.hasReadOnly) {
- userObjBuilder.append("readOnly", args.readOnly);
- }
-
- if (args.hasExtraData) {
- userObjBuilder.append("extraData", args.extraData);
- }
-
- if (args.hasRoles) {
- userObjBuilder.append("roles", args.roles);
- }
-
- if (args.hasOtherDBRoles) {
- userObjBuilder.append("otherDBRoles", args.otherDBRoles);
- }
-
AuthorizationManager* authzManager = getGlobalAuthorizationManager();
- status = authzManager->insertPrivilegeDocument(dbname, userObjBuilder.obj());
+ BSONObj userObj;
+ Status status = auth::parseAndValidateCreateUserCommand(cmdObj,
+ dbname,
+ authzManager,
+ &userObj);
if (!status.isOK()) {
addStatus(status, result);
return false;
}
- // Rebuild full user cache on every user modification.
- // TODO(spencer): Remove this once we update user cache on-demand for each user
- // modification.
- status = authzManager->initializeAllV1UserData();
+ status = authzManager->insertPrivilegeDocument(dbname, userObj);
if (!status.isOK()) {
addStatus(status, result);
return false;
}
-
return true;
}
@@ -173,122 +114,74 @@ namespace mongo {
redactPasswordData(cmdObj->root());
}
- private:
-
- Status _parseAndValidateInput(const std::string& dbname,
- const BSONObj& cmdObj,
- CreateUserArgs* parsedArgs) const {
- unordered_set<std::string> validFieldNames;
- validFieldNames.insert("createUser");
- validFieldNames.insert("user");
- validFieldNames.insert("pwd");
- validFieldNames.insert("userSource");
- validFieldNames.insert("roles");
- validFieldNames.insert("readOnly");
- validFieldNames.insert("otherDBRoles");
-
-
- // Iterate through all fields in command object and make sure there are no unexpected
- // ones.
- for (BSONObjIterator iter(cmdObj); iter.more(); iter.next()) {
- StringData fieldName = (*iter).fieldNameStringData();
- if (!validFieldNames.count(fieldName.toString())) {
- return Status(ErrorCodes::BadValue,
- mongoutils::str::stream() << "\"" << fieldName << "\" is not "
- "a valid argument to createUser");
- }
- }
+ } cmdCreateUser;
- Status status = bsonExtractStringField(cmdObj, "user", &parsedArgs->userName);
- if (!status.isOK()) {
- return status;
- }
+ class CmdUpdateUser : public Command {
+ public:
- if (cmdObj.hasField("pwd")) {
- parsedArgs->hasPassword = true;
- status = bsonExtractStringField(cmdObj, "pwd", &parsedArgs->clearTextPassword);
- if (!status.isOK()) {
- return status;
- }
- }
+ virtual bool logTheOp() {
+ return false;
+ }
+ virtual bool slaveOk() const {
+ return false;
+ }
- if (cmdObj.hasField("userSource")) {
- parsedArgs->hasUserSource = true;
- status = bsonExtractStringField(cmdObj, "userSource", &parsedArgs->userSource);
- if (!status.isOK()) {
- return status;
- }
- }
+ virtual LockType locktype() const {
+ return NONE;
+ }
- if (cmdObj.hasField("readOnly")) {
- parsedArgs->hasReadOnly = true;
- status = bsonExtractBooleanField(cmdObj, "readOnly", &parsedArgs->readOnly);
- if (!status.isOK()) {
- return status;
- }
- }
+ CmdUpdateUser() : Command("updateUser") {}
- if (cmdObj.hasField("extraData")) {
- parsedArgs->hasExtraData = true;
- BSONElement element;
- status = bsonExtractTypedField(cmdObj, "extraData", Object, &element);
- if (!status.isOK()) {
- return status;
- }
- parsedArgs->extraData = element.Obj();
- }
+ virtual void help(stringstream& ss) const {
+ ss << "Used to update a user, for example to change its password" << endl;
+ }
- if (cmdObj.hasField("roles")) {
- parsedArgs->hasRoles = true;
- BSONElement element;
- status = bsonExtractTypedField(cmdObj, "roles", Array, &element);
- if (!status.isOK()) {
- return status;
- }
- parsedArgs->roles = BSONArray(element.Obj());
- }
+ virtual void addRequiredPrivileges(const std::string& dbname,
+ const BSONObj& cmdObj,
+ std::vector<Privilege>* out) {
+ // TODO: update this with the new rules around user creation in 2.6.
+ ActionSet actions;
+ actions.addAction(ActionType::userAdmin);
+ out->push_back(Privilege(dbname, actions));
+ }
- if (cmdObj.hasField("otherDBRoles")) {
- parsedArgs->hasOtherDBRoles = true;
- BSONElement element;
- status = bsonExtractTypedField(cmdObj, "otherDBRoles", Object, &element);
- if (!status.isOK()) {
- return status;
- }
- parsedArgs->otherDBRoles = element.Obj();
+ bool run(const string& dbname,
+ BSONObj& cmdObj,
+ int options,
+ string& errmsg,
+ BSONObjBuilder& result,
+ bool fromRepl) {
+ AuthorizationManager* authzManager = getGlobalAuthorizationManager();
+ BSONObj updateObj;
+ UserName userName;
+ Status status = auth::parseAndValidateUpdateUserCommand(cmdObj,
+ dbname,
+ authzManager,
+ &updateObj,
+ &userName);
+ if (!status.isOK()) {
+ addStatus(status, result);
+ return false;
}
- if (parsedArgs->hasPassword && parsedArgs->hasUserSource) {
- return Status(ErrorCodes::BadValue,
- "User objects can't have both 'pwd' and 'userSource'");
- }
- if (!parsedArgs->hasPassword && !parsedArgs->hasUserSource) {
- return Status(ErrorCodes::BadValue,
- "User objects must have one of 'pwd' and 'userSource'");
- }
- if (parsedArgs->hasRoles && parsedArgs->hasReadOnly) {
- return Status(ErrorCodes::BadValue,
- "User objects can't have both 'roles' and 'readOnly'");
+ status = authzManager->updatePrivilegeDocument(userName, updateObj);
+ if (!status.isOK()) {
+ addStatus(status, result);
+ return false;
}
- // Prevent creating a __system user on the local database, and also prevent creating
- // privilege documents in other datbases for the __system@local user.
- // TODO(spencer): The second part will go away once we use the new V2 user doc format
- // as it doesn't have the same userSource notion.
- if (parsedArgs->userName == internalSecurity.user->getName().getUser() &&
- ((!parsedArgs->hasUserSource && dbname == "local") ||
- parsedArgs->userSource == "local")) {
- return Status(ErrorCodes::BadValue,
- "Cannot create user document for the internal user");
- }
+ authzManager->invalidateUserByName(userName);
+ return true;
+ }
- return Status::OK();
+ virtual void redactForLogging(mutablebson::Document* cmdObj) {
+ redactPasswordData(cmdObj->root());
}
- } cmdCreateUser;
+ } cmdUpdateUser;
- class CmdUpdateUser : public Command {
+ class CmdRemoveUser : public Command {
public:
virtual bool logTheOp() {
@@ -303,10 +196,10 @@ namespace mongo {
return NONE;
}
- CmdUpdateUser() : Command("updateUser") {}
+ CmdRemoveUser() : Command("removeUser") {}
virtual void help(stringstream& ss) const {
- ss << "Used to update a user, for example to change its password" << endl;
+ ss << "Removes a single user." << endl;
}
virtual void addRequiredPrivileges(const std::string& dbname,
@@ -318,119 +211,46 @@ namespace mongo {
out->push_back(Privilege(dbname, actions));
}
- struct UpdateUserArgs {
- std::string userName;
- std::string clearTextPassword;
- BSONObj extraData; // Owned by the owner of the command object given to updateUser
- bool hasPassword;
- bool hasExtraData;
- UpdateUserArgs() : hasPassword(false), hasExtraData(false) {}
- };
-
bool run(const string& dbname,
BSONObj& cmdObj,
int options,
string& errmsg,
BSONObjBuilder& result,
bool fromRepl) {
- UpdateUserArgs args;
- Status status = _parseAndValidateInput(cmdObj, &args);
+ std::string user;
+
+ Status status = bsonExtractStringField(cmdObj, "removeUser", &user);
if (!status.isOK()) {
addStatus(status, result);
return false;
}
- // TODO: This update will have to change once we're using the new v2 user
- // storage format.
- BSONObjBuilder setBuilder;
- if (args.hasPassword) {
- std::string password = DBClientWithCommands::createPasswordDigest(
- args.userName, args.clearTextPassword);
- setBuilder.append("pwd", password);
- }
- if (args.hasExtraData) {
- setBuilder.append("extraData", args.extraData);
- }
- BSONObj updateObj = BSON("$set" << setBuilder.obj());
-
+ int numUpdated;
AuthorizationManager* authzManager = getGlobalAuthorizationManager();
- status = authzManager->updatePrivilegeDocument(UserName(args.userName, dbname),
- updateObj);
-
+ status = authzManager->removePrivilegeDocuments(
+ BSON(AuthorizationManager::USER_NAME_FIELD_NAME << user <<
+ AuthorizationManager::USER_SOURCE_FIELD_NAME << dbname),
+ &numUpdated);
if (!status.isOK()) {
addStatus(status, result);
return false;
}
- // Rebuild full user cache on every user modification.
- // TODO(spencer): Remove this once we update user cache on-demand for each user
- // modification.
- status = authzManager->initializeAllV1UserData();
- if (!status.isOK()) {
- addStatus(status, result);
+ if (numUpdated == 0) {
+ addStatus(Status(ErrorCodes::UserNotFound,
+ mongoutils::str::stream() << "User '" << user << "@" <<
+ dbname << "' not found"),
+ result);
return false;
}
+ authzManager->invalidateUserByName(UserName(user, dbname));
return true;
}
- virtual void redactForLogging(mutablebson::Document* cmdObj) {
- redactPasswordData(cmdObj->root());
- }
-
- private:
-
- Status _parseAndValidateInput(BSONObj cmdObj, UpdateUserArgs* parsedArgs) const {
- unordered_set<std::string> validFieldNames;
- validFieldNames.insert("updateUser");
- validFieldNames.insert("user");
- validFieldNames.insert("pwd");
- validFieldNames.insert("extraData");
-
- // Iterate through all fields in command object and make sure there are no
- // unexpected ones.
- for (BSONObjIterator iter(cmdObj); iter.more(); iter.next()) {
- StringData fieldName = (*iter).fieldNameStringData();
- if (!validFieldNames.count(fieldName.toString())) {
- return Status(ErrorCodes::BadValue,
- mongoutils::str::stream() << "\"" << fieldName << "\" is not "
- "a valid argument to updateUser");
- }
- }
-
- Status status = bsonExtractStringField(cmdObj, "user", &parsedArgs->userName);
- if (!status.isOK()) {
- return status;
- }
-
- if (cmdObj.hasField("pwd")) {
- parsedArgs->hasPassword = true;
- status = bsonExtractStringField(cmdObj, "pwd", &parsedArgs->clearTextPassword);
- if (!status.isOK()) {
- return status;
- }
- }
-
- if (cmdObj.hasField("extraData")) {
- parsedArgs->hasExtraData = true;
- BSONElement element;
- status = bsonExtractTypedField(cmdObj, "extraData", Object, &element);
- if (!status.isOK()) {
- return status;
- }
- parsedArgs->extraData = element.Obj();
- }
-
-
- if (!parsedArgs->hasPassword && !parsedArgs->hasExtraData) {
- return Status(ErrorCodes::BadValue,
- "Must specify at least one of 'pwd' and 'extraData'");
- }
- return Status::OK();
- }
- } cmdUpdateUser;
+ } cmdRemoveUser;
- class CmdRemoveUsers : public Command {
+ class CmdRemoveUsersFromDatabase : public Command {
public:
virtual bool logTheOp() {
@@ -445,11 +265,10 @@ namespace mongo {
return NONE;
}
- CmdRemoveUsers() : Command("removeUsers") {}
+ CmdRemoveUsersFromDatabase() : Command("removeUsersFromDatabase") {}
virtual void help(stringstream& ss) const {
- ss << "By default, removes all users for this database. If given a \"user\""
- " argument, removes only that user."<< endl;
+ ss << "Removes all users for a single database." << endl;
}
virtual void addRequiredPrivileges(const std::string& dbname,
@@ -461,46 +280,27 @@ namespace mongo {
out->push_back(Privilege(dbname, actions));
}
- // TODO: The bulk of the implementation of this will need to change once we're using the
- // new v2 authorization storage format.
bool run(const string& dbname,
BSONObj& cmdObj,
int options,
string& errmsg,
BSONObjBuilder& result,
bool fromRepl) {
- std::string user;
-
- if (cmdObj.hasField("user")) {
- Status status = bsonExtractStringField(cmdObj, "user", &user);
- if (!status.isOK()) {
- addStatus(status, result);
- return false;
- }
- }
-
- BSONObj query;
- if (!user.empty()) {
- query = BSON("user" << user);
- }
+ int numRemoved;
AuthorizationManager* authzManager = getGlobalAuthorizationManager();
- Status status = authzManager->removePrivilegeDocuments(dbname, query);
+ Status status = authzManager->removePrivilegeDocuments(
+ BSON(AuthorizationManager::USER_SOURCE_FIELD_NAME << dbname),
+ &numRemoved);
if (!status.isOK()) {
addStatus(status, result);
return false;
}
- // Rebuild full user cache on every user modification.
- // TODO(spencer): Remove this once we update user cache on-demand for each user
- // modification.
- status = authzManager->initializeAllV1UserData();
- if (!status.isOK()) {
- addStatus(status, result);
- return false;
- }
+ result.append("n", numRemoved);
+ authzManager->invalidateUsersFromDB(dbname);
return true;
}
- } cmdRemoveUser;
+ } cmdRemoveUsersFromDatabase;
}
diff --git a/src/mongo/db/commands/user_management_commands_parser.cpp b/src/mongo/db/commands/user_management_commands_parser.cpp
new file mode 100644
index 00000000000..81a223a3a4e
--- /dev/null
+++ b/src/mongo/db/commands/user_management_commands_parser.cpp
@@ -0,0 +1,285 @@
+/**
+* Copyright (C) 2013 10gen 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 <http://www.gnu.org/licenses/>.
+*/
+
+#include "mongo/db/commands/user_management_commands_parser.h"
+
+#include <string>
+
+#include "mongo/base/status.h"
+#include "mongo/bson/util/bson_extract.h"
+#include "mongo/client/auth_helpers.h"
+#include "mongo/db/auth/authorization_manager.h"
+#include "mongo/db/auth/privilege_document_parser.h"
+#include "mongo/db/auth/user_name.h"
+#include "mongo/db/jsobj.h"
+#include "mongo/platform/unordered_set.h"
+#include "mongo/util/mongoutils/str.h"
+
+namespace mongo {
+namespace auth {
+
+ /**
+ * Validates that the roles array described by rolesElement is valid.
+ * Also returns a new roles array (via the modifiedRolesArray output param) where any roles
+ * from the input array that were listed as strings have been expanded to a full role document.
+ */
+ Status _validateAndModifyRolesArray(const BSONElement& rolesElement,
+ const std::string& dbname,
+ const AuthorizationManager* authzManager,
+ BSONArray* modifiedRolesArray) {
+ BSONArrayBuilder rolesBuilder;
+
+ for (BSONObjIterator it(rolesElement.Obj()); it.more(); it.next()) {
+ BSONElement element = *it;
+ if (element.type() == String) {
+ RoleName roleName(element.String(), dbname);
+ if (!authzManager->roleExists(roleName)) {
+ return Status(ErrorCodes::RoleNotFound,
+ mongoutils::str::stream() << roleName.toString() <<
+ " does not name an existing role");
+ }
+
+ rolesBuilder.append(BSON("name" << element.String() <<
+ "source" << dbname <<
+ "hasRole" << true <<
+ "canDelegate" << false));
+ } else if (element.type() == Object) {
+ // Check that the role object is valid
+ V2PrivilegeDocumentParser parser;
+ BSONObj roleObj = element.Obj();
+ Status status = parser.checkValidRoleObject(roleObj);
+ if (!status.isOK()) {
+ return status;
+ }
+
+ // Check that the role actually exists
+ std::string roleNameString;
+ std::string roleSource;
+ status = bsonExtractStringField(roleObj, "name", &roleNameString);
+ if (!status.isOK()) {
+ return status;
+ }
+ status = bsonExtractStringField(roleObj, "source", &roleSource);
+ if (!status.isOK()) {
+ return status;
+ }
+
+ RoleName roleName(roleNameString, roleSource);
+ if (!authzManager->roleExists(roleName)) {
+ return Status(ErrorCodes::RoleNotFound,
+ mongoutils::str::stream() << roleName.toString() <<
+ " does not name an existing role");
+ }
+
+ rolesBuilder.append(element);
+ } else {
+ return Status(ErrorCodes::UnsupportedFormat,
+ "Values in 'roles' array must be sub-documents or strings");
+ }
+ }
+
+ *modifiedRolesArray = rolesBuilder.arr();
+ return Status::OK();
+ }
+
+ Status parseAndValidateCreateUserCommand(const BSONObj& cmdObj,
+ const std::string& dbname,
+ const AuthorizationManager* authzManager,
+ BSONObj* parsedUserObj) {
+ unordered_set<std::string> validFieldNames;
+ validFieldNames.insert("createUser");
+ validFieldNames.insert("customData");
+ validFieldNames.insert("pwd");
+ validFieldNames.insert("roles");
+
+ // Iterate through all fields in command object and make sure there are no unexpected
+ // ones.
+ for (BSONObjIterator iter(cmdObj); iter.more(); iter.next()) {
+ StringData fieldName = (*iter).fieldNameStringData();
+ if (!validFieldNames.count(fieldName.toString())) {
+ return Status(ErrorCodes::BadValue,
+ mongoutils::str::stream() << "\"" << fieldName << "\" is not "
+ "a valid argument to createUser");
+ }
+ }
+
+ BSONObjBuilder userObjBuilder;
+ userObjBuilder.append("_id", OID::gen());
+
+ // Parse user name
+ std::string userName;
+ Status status = bsonExtractStringField(cmdObj, "createUser", &userName);
+ if (!status.isOK()) {
+ return status;
+ }
+
+ // Prevent creating users in the local database
+ if (dbname == "local") {
+ return Status(ErrorCodes::BadValue, "Cannot create users in the local database");
+ }
+
+ userObjBuilder.append(AuthorizationManager::USER_NAME_FIELD_NAME, userName);
+ userObjBuilder.append(AuthorizationManager::USER_SOURCE_FIELD_NAME, dbname);
+
+
+ // Parse password
+ if (cmdObj.hasField("pwd")) {
+ std::string clearTextPassword;
+ status = bsonExtractStringField(cmdObj, "pwd", &clearTextPassword);
+ if (!status.isOK()) {
+ return status;
+ }
+
+ std::string password = auth::createPasswordDigest(userName, clearTextPassword);
+ userObjBuilder.append("credentials", BSON("MONGODB-CR" << password));
+ } else {
+ if (dbname != "$external") {
+ return Status(ErrorCodes::BadValue,
+ "Must provide a 'pwd' field for all user documents, except those"
+ " with '$external' as the user's source");
+ }
+ }
+
+
+ // Parse custom data
+ if (cmdObj.hasField("customData")) {
+ BSONElement element;
+ status = bsonExtractTypedField(cmdObj, "customData", Object, &element);
+ if (!status.isOK()) {
+ return status;
+ }
+ userObjBuilder.append("customData", element.Obj());
+ }
+
+ // Parse roles
+ if (cmdObj.hasField("roles")) {
+ BSONElement rolesElement;
+ status = bsonExtractTypedField(cmdObj, "roles", Array, &rolesElement);
+ if (!status.isOK()) {
+ return status;
+ }
+ BSONArray modifiedRolesArray;
+ status = _validateAndModifyRolesArray(rolesElement,
+ dbname,
+ authzManager,
+ &modifiedRolesArray);
+ if (!status.isOK()) {
+ return status;
+ }
+
+ userObjBuilder.append("roles", modifiedRolesArray);
+ }
+
+ *parsedUserObj = userObjBuilder.obj();
+
+ // Make sure document to insert is valid
+ V2PrivilegeDocumentParser parser;
+ status = parser.checkValidPrivilegeDocument(dbname, *parsedUserObj);
+ if (!status.isOK()) {
+ return status;
+ }
+
+ return Status::OK();
+ }
+
+
+ Status parseAndValidateUpdateUserCommand(const BSONObj& cmdObj,
+ const std::string& dbname,
+ const AuthorizationManager* authzManager,
+ BSONObj* parsedUpdateObj,
+ UserName* parsedUserName) {
+ unordered_set<std::string> validFieldNames;
+ validFieldNames.insert("updateUser");
+ validFieldNames.insert("customData");
+ validFieldNames.insert("pwd");
+ validFieldNames.insert("roles");
+
+ // Iterate through all fields in command object and make sure there are no unexpected
+ // ones.
+ for (BSONObjIterator iter(cmdObj); iter.more(); iter.next()) {
+ StringData fieldName = (*iter).fieldNameStringData();
+ if (!validFieldNames.count(fieldName.toString())) {
+ return Status(ErrorCodes::BadValue,
+ mongoutils::str::stream() << "\"" << fieldName << "\" is not "
+ "a valid argument to createUser");
+ }
+ }
+
+ BSONObjBuilder updateSetBuilder;
+
+ // Parse user name
+ std::string userName;
+ Status status = bsonExtractStringField(cmdObj, "updateUser", &userName);
+ if (!status.isOK()) {
+ return status;
+ }
+ *parsedUserName = UserName(userName, dbname);
+
+ // Parse password
+ if (cmdObj.hasField("pwd")) {
+ std::string clearTextPassword;
+ status = bsonExtractStringField(cmdObj, "pwd", &clearTextPassword);
+ if (!status.isOK()) {
+ return status;
+ }
+
+ std::string password = auth::createPasswordDigest(userName, clearTextPassword);
+ updateSetBuilder.append("credentials.MONGODB-CR", password);
+ }
+
+
+ // Parse custom data
+ if (cmdObj.hasField("customData")) {
+ BSONElement element;
+ status = bsonExtractTypedField(cmdObj, "customData", Object, &element);
+ if (!status.isOK()) {
+ return status;
+ }
+ updateSetBuilder.append("customData", element.Obj());
+ }
+
+ // Parse roles
+ if (cmdObj.hasField("roles")) {
+ BSONElement rolesElement;
+ Status status = bsonExtractTypedField(cmdObj, "roles", Array, &rolesElement);
+ if (!status.isOK()) {
+ return status;
+ }
+
+ BSONArray modifiedRolesObj;
+ status = _validateAndModifyRolesArray(rolesElement,
+ dbname,
+ authzManager,
+ &modifiedRolesObj);
+ if (!status.isOK()) {
+ return status;
+ }
+
+ updateSetBuilder.append("roles", modifiedRolesObj);
+ }
+
+ BSONObj updateSet = updateSetBuilder.obj();
+ if (updateSet.isEmpty()) {
+ return Status(ErrorCodes::UserModificationFailed,
+ "Must specify at least one field to update in updateUser");
+ }
+
+ *parsedUpdateObj = BSON("$set" << updateSet);
+ return Status::OK();
+ }
+
+} // namespace auth
+} // namespace mongo
diff --git a/src/mongo/db/commands/user_management_commands_parser.h b/src/mongo/db/commands/user_management_commands_parser.h
new file mode 100644
index 00000000000..ce4b20b0118
--- /dev/null
+++ b/src/mongo/db/commands/user_management_commands_parser.h
@@ -0,0 +1,55 @@
+/**
+* Copyright (C) 2013 10gen 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 <http://www.gnu.org/licenses/>.
+*/
+
+#include <string>
+
+#include "mongo/base/status.h"
+#include "mongo/base/disallow_copying.h"
+#include "mongo/db/auth/user_name.h"
+#include "mongo/db/jsobj.h"
+
+namespace mongo {
+
+ class AuthorizationManager;
+
+namespace auth {
+
+ /**
+ * Takes a command object describing an invocation of the "createUser" command on the database
+ * "dbname", and returns (via the output param "parsedUserObj") a user object that can be
+ * inserted into admin.system.users to create the user as described by the command object.
+ * Also validates the input and returns a non-ok Status if there is anything wrong.
+ */
+ Status parseAndValidateCreateUserCommand(const BSONObj& cmdObj,
+ const std::string& dbname,
+ const AuthorizationManager* authzManager,
+ BSONObj* parsedUserObj);
+
+ /**
+ * Takes a command object describing an invocation of the "updateUser" command on the database
+ * "dbname", and returns (via the output param "parsedUpdateObj") an update specifier that can
+ * be used to update the user document in admin.system.users as described by the command object,
+ * as well as the user name of the user being updated (via the "parsedUserName" output param).
+ * Also validates the input and returns a non-ok Status if there is anything wrong.
+ */
+ Status parseAndValidateUpdateUserCommand(const BSONObj& cmdObj,
+ const std::string& dbname,
+ const AuthorizationManager* authzManager,
+ BSONObj* parsedUpdateObj,
+ UserName* parsedUserName);
+
+} // namespace auth
+} // namespace mongo
diff --git a/src/mongo/db/commands/user_management_commands_parser_test.cpp b/src/mongo/db/commands/user_management_commands_parser_test.cpp
new file mode 100644
index 00000000000..62e20442506
--- /dev/null
+++ b/src/mongo/db/commands/user_management_commands_parser_test.cpp
@@ -0,0 +1,412 @@
+/* Copyright 2013 10gen Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Unit tests of the functions used for parsing the commands used for user management.
+ */
+
+#include <vector>
+
+#include "mongo/base/status.h"
+#include "mongo/client/auth_helpers.h"
+#include "mongo/db/auth/authz_manager_external_state_mock.h"
+#include "mongo/db/auth/authorization_manager.h"
+#include "mongo/db/cmdline.h"
+#include "mongo/db/commands/user_management_commands_parser.h"
+#include "mongo/db/jsobj.h"
+#include "mongo/unittest/unittest.h"
+
+namespace mongo {
+
+ // Crutches to make the test compile
+ CmdLine cmdLine;
+ bool inShutdown() {
+ return false;
+ }
+
+namespace {
+
+ class UserManagementCommandsParserTest : public ::mongo::unittest::Test {
+ public:
+ scoped_ptr<AuthorizationManager> authzManager;
+ AuthzManagerExternalStateMock* externalState;
+ void setUp() {
+ externalState = new AuthzManagerExternalStateMock();
+ authzManager.reset(new AuthorizationManager(externalState));
+ }
+ };
+
+ TEST_F(UserManagementCommandsParserTest, CreateUserCommandParsing) {
+ BSONArray emptyArray = BSONArrayBuilder().arr();
+ BSONObj parsedUserObj;
+
+ // Must have password
+ ASSERT_NOT_OK(auth::parseAndValidateCreateUserCommand(BSON("createUser" << "spencer" <<
+ "roles" << emptyArray),
+ "test",
+ authzManager.get(),
+ &parsedUserObj));
+
+ // Must have roles array
+ ASSERT_NOT_OK(auth::parseAndValidateCreateUserCommand(BSON("createUser" << "spencer" <<
+ "pwd" << "password"),
+ "test",
+ authzManager.get(),
+ &parsedUserObj));
+
+ // Cannot create users in the local db
+ ASSERT_NOT_OK(auth::parseAndValidateCreateUserCommand(BSON("createUser" << "spencer" <<
+ "pwd" << "password" <<
+ "roles" << emptyArray),
+ "local",
+ authzManager.get(),
+ &parsedUserObj));
+
+ // Cannot have extra fields
+ ASSERT_NOT_OK(auth::parseAndValidateCreateUserCommand(BSON("createUser" << "spencer" <<
+ "pwd" << "password" <<
+ "roles" << emptyArray <<
+ "anotherField" << "garbage"),
+ "test",
+ authzManager.get(),
+ &parsedUserObj));
+
+ // Role must exist (string role)
+ ASSERT_NOT_OK(auth::parseAndValidateCreateUserCommand(
+ BSON("createUser" << "spencer" <<
+ "pwd" << "password" <<
+ "roles" << BSON_ARRAY("fakeRole")),
+ "test",
+ authzManager.get(),
+ &parsedUserObj));
+
+ // Role must exist (object role)
+ ASSERT_NOT_OK(auth::parseAndValidateCreateUserCommand(
+ BSON("createUser" << "spencer" <<
+ "pwd" << "password" <<
+ "roles" << BSON_ARRAY(BSON("name" << "fakeRole" <<
+ "source" << "test" <<
+ "hasRole" << true <<
+ "canDelegate" << true))),
+ "test",
+ authzManager.get(),
+ &parsedUserObj));
+
+ // Role must have name
+ ASSERT_NOT_OK(auth::parseAndValidateCreateUserCommand(
+ BSON("createUser" << "spencer" <<
+ "pwd" << "password" <<
+ "roles" << BSON_ARRAY(BSON("source" << "test" <<
+ "hasRole" << true <<
+ "canDelegate" << true))),
+ "test",
+ authzManager.get(),
+ &parsedUserObj));
+
+ // Role must have source
+ ASSERT_NOT_OK(auth::parseAndValidateCreateUserCommand(
+ BSON("createUser" << "spencer" <<
+ "pwd" << "password" <<
+ "roles" << BSON_ARRAY(BSON("name" << "read" <<
+ "hasRole" << true <<
+ "canDelegate" << true))),
+ "test",
+ authzManager.get(),
+ &parsedUserObj));
+
+ // Role must have hasRole
+ ASSERT_NOT_OK(auth::parseAndValidateCreateUserCommand(
+ BSON("createUser" << "spencer" <<
+ "pwd" << "password" <<
+ "roles" << BSON_ARRAY(BSON("name" << "read" <<
+ "source" << "test" <<
+ "canDelegate" << true))),
+ "test",
+ authzManager.get(),
+ &parsedUserObj));
+
+ // Role must have canDelegate
+ ASSERT_NOT_OK(auth::parseAndValidateCreateUserCommand(
+ BSON("createUser" << "spencer" <<
+ "pwd" << "password" <<
+ "roles" << BSON_ARRAY(BSON("name" << "read" <<
+ "source" << "test" <<
+ "hasRole" << true))),
+ "test",
+ authzManager.get(),
+ &parsedUserObj));
+
+ // canDelegate and hasRole can't both be false
+ ASSERT_NOT_OK(auth::parseAndValidateCreateUserCommand(
+ BSON("createUser" << "spencer" <<
+ "pwd" << "password" <<
+ "roles" << BSON_ARRAY(BSON("name" << "read" <<
+ "source" << "test" <<
+ "hasRole" << false <<
+ "canDelegate" << false))),
+ "test",
+ authzManager.get(),
+ &parsedUserObj));
+
+ // Empty roles array OK
+ ASSERT_OK(auth::parseAndValidateCreateUserCommand(BSON("createUser" << "spencer" <<
+ "pwd" << "password" <<
+ "roles" << emptyArray),
+ "test",
+ authzManager.get(),
+ &parsedUserObj));
+
+
+ // Missing password OK if source is $external
+ ASSERT_OK(auth::parseAndValidateCreateUserCommand(BSON("createUser" << "spencer" <<
+ "roles" << emptyArray),
+ "$external",
+ authzManager.get(),
+ &parsedUserObj));
+
+ // String role names OK
+ ASSERT_OK(auth::parseAndValidateCreateUserCommand(
+ BSON("createUser" << "spencer" <<
+ "pwd" << "password" <<
+ "roles" << BSON_ARRAY("read" << "dbAdmin")),
+ "test",
+ authzManager.get(),
+ &parsedUserObj));
+
+ ASSERT_EQUALS("spencer", parsedUserObj["name"].String());
+ ASSERT_EQUALS("test", parsedUserObj["source"].String());
+ std::string hashedPassword = auth::createPasswordDigest("spencer", "password");
+ ASSERT_EQUALS(hashedPassword, parsedUserObj["credentials"].Obj()["MONGODB-CR"].String());
+ std::vector<BSONElement> rolesArray = parsedUserObj["roles"].Array();
+ ASSERT_EQUALS((size_t)2, rolesArray.size());
+ ASSERT_EQUALS("read", rolesArray[0].Obj()["name"].String());
+ ASSERT_EQUALS("test", rolesArray[0].Obj()["source"].String());
+ ASSERT_EQUALS(true, rolesArray[0].Obj()["hasRole"].Bool());
+ ASSERT_EQUALS(false, rolesArray[0].Obj()["canDelegate"].Bool());
+ ASSERT_EQUALS("dbAdmin", rolesArray[1].Obj()["name"].String());
+ ASSERT_EQUALS("test", rolesArray[1].Obj()["source"].String());
+ ASSERT_EQUALS(true, rolesArray[1].Obj()["hasRole"].Bool());
+ ASSERT_EQUALS(false, rolesArray[1].Obj()["canDelegate"].Bool());
+
+
+ // Basic valid createUser command OK
+ ASSERT_OK(auth::parseAndValidateCreateUserCommand(
+ BSON("createUser" << "spencer" <<
+ "pwd" << "password" <<
+ "roles" << BSON_ARRAY(BSON("name" << "read" <<
+ "source" << "test" <<
+ "hasRole" << true <<
+ "canDelegate" << false) <<
+ BSON("name" << "dbAdminAnyDatabase" <<
+ "source" << "admin" <<
+ "hasRole" << false <<
+ "canDelegate" << true)) <<
+ "customData" << BSON("foo" << "bar")),
+ "test",
+ authzManager.get(),
+ &parsedUserObj));
+
+ ASSERT_EQUALS("spencer", parsedUserObj["name"].String());
+ ASSERT_EQUALS("test", parsedUserObj["source"].String());
+ hashedPassword = auth::createPasswordDigest("spencer", "password");
+ ASSERT_EQUALS(hashedPassword, parsedUserObj["credentials"].Obj()["MONGODB-CR"].String());
+ ASSERT_EQUALS("bar", parsedUserObj["customData"].Obj()["foo"].String());
+ rolesArray = parsedUserObj["roles"].Array();
+ ASSERT_EQUALS((size_t)2, rolesArray.size());
+ ASSERT_EQUALS("read", rolesArray[0].Obj()["name"].String());
+ ASSERT_EQUALS("test", rolesArray[0].Obj()["source"].String());
+ ASSERT_EQUALS(true, rolesArray[0].Obj()["hasRole"].Bool());
+ ASSERT_EQUALS(false, rolesArray[0].Obj()["canDelegate"].Bool());
+ ASSERT_EQUALS("dbAdminAnyDatabase", rolesArray[1].Obj()["name"].String());
+ ASSERT_EQUALS("admin", rolesArray[1].Obj()["source"].String());
+ ASSERT_EQUALS(false, rolesArray[1].Obj()["hasRole"].Bool());
+ ASSERT_EQUALS(true, rolesArray[1].Obj()["canDelegate"].Bool());
+
+ }
+
+
+ TEST_F(UserManagementCommandsParserTest, UpdateUserCommandParsing) {
+ BSONArray emptyArray = BSONArrayBuilder().arr();
+ BSONObj parsedUpdateObj;
+ UserName parsedUserName;
+
+ // Cannot have extra fields
+ ASSERT_NOT_OK(auth::parseAndValidateUpdateUserCommand(BSON("updateUser" << "spencer" <<
+ "pwd" << "password" <<
+ "roles" << emptyArray <<
+ "anotherField" << "garbage"),
+ "test",
+ authzManager.get(),
+ &parsedUpdateObj,
+ &parsedUserName));
+
+ // Role must exist (string role)
+ ASSERT_NOT_OK(auth::parseAndValidateUpdateUserCommand(
+ BSON("updateUser" << "spencer" <<
+ "pwd" << "password" <<
+ "roles" << BSON_ARRAY("fakeRole")),
+ "test",
+ authzManager.get(),
+ &parsedUpdateObj,
+ &parsedUserName));
+
+ // Role must exist (object role)
+ ASSERT_NOT_OK(auth::parseAndValidateUpdateUserCommand(
+ BSON("updateUser" << "spencer" <<
+ "pwd" << "password" <<
+ "roles" << BSON_ARRAY(BSON("name" << "fakeRole" <<
+ "source" << "test" <<
+ "hasRole" << true <<
+ "canDelegate" << true))),
+ "test",
+ authzManager.get(),
+ &parsedUpdateObj,
+ &parsedUserName));
+
+ // Role must have name
+ ASSERT_NOT_OK(auth::parseAndValidateUpdateUserCommand(
+ BSON("updateUser" << "spencer" <<
+ "pwd" << "password" <<
+ "roles" << BSON_ARRAY(BSON("source" << "test" <<
+ "hasRole" << true <<
+ "canDelegate" << true))),
+ "test",
+ authzManager.get(),
+ &parsedUpdateObj,
+ &parsedUserName));
+
+ // Role must have source
+ ASSERT_NOT_OK(auth::parseAndValidateUpdateUserCommand(
+ BSON("updateUser" << "spencer" <<
+ "pwd" << "password" <<
+ "roles" << BSON_ARRAY(BSON("name" << "read" <<
+ "hasRole" << true <<
+ "canDelegate" << true))),
+ "test",
+ authzManager.get(),
+ &parsedUpdateObj,
+ &parsedUserName));
+
+ // Role must have hasRole
+ ASSERT_NOT_OK(auth::parseAndValidateUpdateUserCommand(
+ BSON("updateUser" << "spencer" <<
+ "pwd" << "password" <<
+ "roles" << BSON_ARRAY(BSON("name" << "read" <<
+ "source" << "test" <<
+ "canDelegate" << true))),
+ "test",
+ authzManager.get(),
+ &parsedUpdateObj,
+ &parsedUserName));
+
+ // Role must have canDelegate
+ ASSERT_NOT_OK(auth::parseAndValidateUpdateUserCommand(
+ BSON("updateUser" << "spencer" <<
+ "pwd" << "password" <<
+ "roles" << BSON_ARRAY(BSON("name" << "read" <<
+ "source" << "test" <<
+ "hasRole" << true))),
+ "test",
+ authzManager.get(),
+ &parsedUpdateObj,
+ &parsedUserName));
+
+ // canDelegate and hasRole can't both be false
+ ASSERT_NOT_OK(auth::parseAndValidateUpdateUserCommand(
+ BSON("updateUser" << "spencer" <<
+ "pwd" << "password" <<
+ "roles" << BSON_ARRAY(BSON("name" << "read" <<
+ "source" << "test" <<
+ "hasRole" << false <<
+ "canDelegate" << false))),
+ "test",
+ authzManager.get(),
+ &parsedUpdateObj,
+ &parsedUserName));
+
+ // Empty roles array OK
+ ASSERT_OK(auth::parseAndValidateUpdateUserCommand(BSON("updateUser" << "spencer" <<
+ "pwd" << "password" <<
+ "roles" << emptyArray),
+ "test",
+ authzManager.get(),
+ &parsedUpdateObj,
+ &parsedUserName));
+
+ // String role names OK
+ ASSERT_OK(auth::parseAndValidateUpdateUserCommand(
+ BSON("updateUser" << "spencer" <<
+ "pwd" << "password" <<
+ "roles" << BSON_ARRAY("read" << "dbAdmin")),
+ "test",
+ authzManager.get(),
+ &parsedUpdateObj,
+ &parsedUserName));
+
+ BSONObj setObj = parsedUpdateObj["$set"].Obj();
+
+ ASSERT_EQUALS(UserName("spencer", "test"), parsedUserName);
+ std::string hashedPassword = auth::createPasswordDigest("spencer", "password");
+ ASSERT_EQUALS(hashedPassword, setObj["credentials.MONGODB-CR"].String());
+ std::vector<BSONElement> rolesArray = setObj["roles"].Array();
+ ASSERT_EQUALS((size_t)2, rolesArray.size());
+ ASSERT_EQUALS("read", rolesArray[0].Obj()["name"].String());
+ ASSERT_EQUALS("test", rolesArray[0].Obj()["source"].String());
+ ASSERT_EQUALS(true, rolesArray[0].Obj()["hasRole"].Bool());
+ ASSERT_EQUALS(false, rolesArray[0].Obj()["canDelegate"].Bool());
+ ASSERT_EQUALS("dbAdmin", rolesArray[1].Obj()["name"].String());
+ ASSERT_EQUALS("test", rolesArray[1].Obj()["source"].String());
+ ASSERT_EQUALS(true, rolesArray[1].Obj()["hasRole"].Bool());
+ ASSERT_EQUALS(false, rolesArray[1].Obj()["canDelegate"].Bool());
+
+
+ // Basic valid updateUser command OK
+ ASSERT_OK(auth::parseAndValidateUpdateUserCommand(
+ BSON("updateUser" << "spencer" <<
+ "pwd" << "password" <<
+ "roles" << BSON_ARRAY(BSON("name" << "read" <<
+ "source" << "test" <<
+ "hasRole" << true <<
+ "canDelegate" << false) <<
+ BSON("name" << "dbAdminAnyDatabase" <<
+ "source" << "admin" <<
+ "hasRole" << false <<
+ "canDelegate" << true)) <<
+ "customData" << BSON("foo" << "bar")),
+ "test",
+ authzManager.get(),
+ &parsedUpdateObj,
+ &parsedUserName));
+
+ setObj = parsedUpdateObj["$set"].Obj();
+
+ ASSERT_EQUALS(UserName("spencer", "test"), parsedUserName);
+ hashedPassword = auth::createPasswordDigest("spencer", "password");
+ ASSERT_EQUALS(hashedPassword, setObj["credentials.MONGODB-CR"].String());
+ ASSERT_EQUALS("bar", setObj["customData"].Obj()["foo"].String());
+ rolesArray = setObj["roles"].Array();
+ ASSERT_EQUALS((size_t)2, rolesArray.size());
+ ASSERT_EQUALS("read", rolesArray[0].Obj()["name"].String());
+ ASSERT_EQUALS("test", rolesArray[0].Obj()["source"].String());
+ ASSERT_EQUALS(true, rolesArray[0].Obj()["hasRole"].Bool());
+ ASSERT_EQUALS(false, rolesArray[0].Obj()["canDelegate"].Bool());
+ ASSERT_EQUALS("dbAdminAnyDatabase", rolesArray[1].Obj()["name"].String());
+ ASSERT_EQUALS("admin", rolesArray[1].Obj()["source"].String());
+ ASSERT_EQUALS(false, rolesArray[1].Obj()["hasRole"].Bool());
+ ASSERT_EQUALS(true, rolesArray[1].Obj()["canDelegate"].Bool());
+ }
+
+} // namespace
+} // namespace mongo