diff options
-rw-r--r-- | src/mongo/db/auth/SConscript | 3 | ||||
-rw-r--r-- | src/mongo/db/auth/user.h | 2 | ||||
-rw-r--r-- | src/mongo/db/auth/user_management_commands_parser.cpp | 329 | ||||
-rw-r--r-- | src/mongo/db/auth/user_management_commands_parser.h | 83 | ||||
-rw-r--r-- | src/mongo/db/auth/user_management_commands_parser_test.cpp | 785 | ||||
-rw-r--r-- | src/mongo/db/commands/user_management_commands.cpp | 360 |
6 files changed, 457 insertions, 1105 deletions
diff --git a/src/mongo/db/auth/SConscript b/src/mongo/db/auth/SConscript index 5d699f6a4a3..61ed510c6ea 100644 --- a/src/mongo/db/auth/SConscript +++ b/src/mongo/db/auth/SConscript @@ -68,6 +68,3 @@ env.CppUnitTest('authorization_manager_test', 'authorization_manager_test.cpp', LIBDEPS=['authcore', 'authmocks']) env.CppUnitTest('authorization_session_test', 'authorization_session_test.cpp', LIBDEPS=['authcore', 'authmocks']) -env.CppUnitTest('user_management_commands_parser_test', 'user_management_commands_parser_test.cpp', - LIBDEPS=['usercommandsparser', 'authmocks'], - NO_CRUTCH=True) diff --git a/src/mongo/db/auth/user.h b/src/mongo/db/auth/user.h index 2d6e895b500..24f588cd335 100644 --- a/src/mongo/db/auth/user.h +++ b/src/mongo/db/auth/user.h @@ -55,6 +55,8 @@ namespace mongo { bool hasRole; bool canDelegate; RoleData() : hasRole(false), canDelegate(false) {} + RoleData(const RoleName& _name, bool _hasRole, bool _canDelegate) : + name(_name), hasRole(_hasRole), canDelegate(_canDelegate) {} }; typedef unordered_map<RoleName, RoleData> RoleDataMap; diff --git a/src/mongo/db/auth/user_management_commands_parser.cpp b/src/mongo/db/auth/user_management_commands_parser.cpp index 19526fe7210..2678cbd9b0b 100644 --- a/src/mongo/db/auth/user_management_commands_parser.cpp +++ b/src/mongo/db/auth/user_management_commands_parser.cpp @@ -84,22 +84,15 @@ namespace auth { /** * Takes a BSONArray of name,source pair documents, parses that array and returns (via the * output param parsedRoleNames) a list of the role names in the input array. - * Also validates the input array and returns a non-OK status if there is anything wrong. */ - Status _extractRoleNamesFromBSONArray(const BSONArray rolesArray, - const std::string& dbname, - AuthorizationManager* authzManager, - std::vector<RoleName>* parsedRoleNames) { + Status _extractRoleNamesFromBSONArray(const BSONArray& rolesArray, + const std::string& dbname, + const StringData& rolesFieldName, + std::vector<RoleName>* parsedRoleNames) { for (BSONObjIterator it(rolesArray); 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.getFullName() << - " does not name an existing role"); - } - parsedRoleNames->push_back(roleName); + parsedRoleNames->push_back(RoleName(element.String(), dbname)); } else if (element.type() == Object) { BSONObj roleObj = element.Obj(); @@ -114,28 +107,73 @@ namespace auth { return status; } - RoleName roleName(roleNameString, roleSource); - if (!authzManager->roleExists(roleName)) { - return Status(ErrorCodes::RoleNotFound, - mongoutils::str::stream() << roleName.getFullName() << - " does not name an existing role"); - } - parsedRoleNames->push_back(roleName); + parsedRoleNames->push_back(RoleName(roleNameString, roleSource)); } else { - return Status(ErrorCodes::UnsupportedFormat, - "Values in 'roles' array must be sub-documents or strings"); + return Status(ErrorCodes::BadValue, + mongoutils::str::stream() << "Values in \"" << rolesFieldName << + "\" array must be sub-documents or strings"); } } return Status::OK(); } - Status parseUserRoleManipulationCommand(const BSONObj& cmdObj, - const StringData& cmdName, - const std::string& dbname, - AuthorizationManager* authzManager, - UserName* parsedUserName, - vector<RoleName>* parsedRoleNames, - BSONObj* parsedWriteConcern) { + Status _extractRoleDataFromBSONArray(const BSONElement& rolesElement, + const std::string& dbname, + std::vector<User::RoleData> *parsedRoleData) { + for (BSONObjIterator it(rolesElement.Obj()); it.more(); it.next()) { + BSONElement element = *it; + if (element.type() == String) { + RoleName roleName(element.String(), dbname); + parsedRoleData->push_back(User::RoleData(roleName, true, false)); + } else if (element.type() == Object) { + // Check that the role object is valid + V2UserDocumentParser parser; + BSONObj roleObj = element.Obj(); + Status status = parser.checkValidRoleObject(roleObj, true); + if (!status.isOK()) { + return status; + } + + std::string roleName; + std::string roleSource; + bool hasRole; + bool canDelegate; + status = bsonExtractStringField(roleObj, "name", &roleName); + if (!status.isOK()) { + return status; + } + status = bsonExtractStringField(roleObj, "source", &roleSource); + if (!status.isOK()) { + return status; + } + status = bsonExtractBooleanField(roleObj, "hasRole", &hasRole); + if (!status.isOK()) { + return status; + } + status = bsonExtractBooleanField(roleObj, "canDelegate", &canDelegate); + if (!status.isOK()) { + return status; + } + + parsedRoleData->push_back(User::RoleData(RoleName(roleName, roleSource), + hasRole, + canDelegate)); + } else { + return Status(ErrorCodes::UnsupportedFormat, + "Values in 'roles' array must be sub-documents or strings"); + } + } + + return Status::OK(); + } + + Status parseRolePossessionManipulationCommands(const BSONObj& cmdObj, + const StringData& cmdName, + const StringData& rolesFieldName, + const std::string& dbname, + std::string* parsedName, + vector<RoleName>* parsedRoleNames, + BSONObj* parsedWriteConcern) { unordered_set<std::string> validFieldNames; validFieldNames.insert(cmdName.toString()); validFieldNames.insert("roles"); @@ -156,7 +194,7 @@ namespace auth { if (!status.isOK()) { return status; } - *parsedUserName = UserName(userNameStr, dbname); + *parsedName = userNameStr; BSONElement rolesElement; status = bsonExtractTypedField(cmdObj, "roles", Array, &rolesElement); @@ -166,7 +204,7 @@ namespace auth { status = _extractRoleNamesFromBSONArray(BSONArray(rolesElement.Obj()), dbname, - authzManager, + rolesFieldName, parsedRoleNames); if (!status.isOK()) { return status; @@ -253,24 +291,23 @@ namespace auth { return Status::OK(); } - Status parseAndValidateCreateUserCommand(const BSONObj& cmdObj, - const std::string& dbname, - AuthorizationManager* authzManager, - BSONObj* parsedUserObj, - BSONObj* parsedWriteConcern) { + Status parseCreateOrUpdateUserCommands(const BSONObj& cmdObj, + const StringData& cmdName, + const std::string& dbname, + CreateOrUpdateUserArgs* parsedArgs) { unordered_set<std::string> validFieldNames; - validFieldNames.insert("createUser"); + validFieldNames.insert(cmdName.toString()); validFieldNames.insert("customData"); validFieldNames.insert("pwd"); validFieldNames.insert("roles"); validFieldNames.insert("writeConcern"); - Status status = _checkNoExtraFields(cmdObj, "createUser", validFieldNames); + Status status = _checkNoExtraFields(cmdObj, cmdName, validFieldNames); if (!status.isOK()) { return status; } - status = _extractWriteConcern(cmdObj, parsedWriteConcern); + status = _extractWriteConcern(cmdObj, &parsedArgs->writeConcern); if (!status.isOK()) { return status; } @@ -279,20 +316,12 @@ namespace auth { // Parse user name std::string userName; - status = bsonExtractStringField(cmdObj, "createUser", &userName); + status = bsonExtractStringField(cmdObj, cmdName, &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("_id", dbname + "." + userName); - userObjBuilder.append(AuthorizationManager::USER_NAME_FIELD_NAME, userName); - userObjBuilder.append(AuthorizationManager::USER_SOURCE_FIELD_NAME, dbname); - + parsedArgs->userName = UserName(userName, dbname); // Parse password if (cmdObj.hasField("pwd")) { @@ -302,17 +331,10 @@ namespace auth { 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"); - } + parsedArgs->hashedPassword = auth::createPasswordDigest(userName, clearTextPassword); + parsedArgs->hasHashedPassword = true; } - // Parse custom data if (cmdObj.hasField("customData")) { BSONElement element; @@ -320,7 +342,8 @@ namespace auth { if (!status.isOK()) { return status; } - userObjBuilder.append("customData", element.Obj()); + parsedArgs->customData = element.Obj(); + parsedArgs->hasCustomData = true; } // Parse roles @@ -330,116 +353,13 @@ namespace auth { if (!status.isOK()) { return status; } - BSONArray modifiedRolesArray; - status = _validateAndModifyRolesArray(rolesElement, - dbname, - authzManager, - true, - &modifiedRolesArray); - if (!status.isOK()) { - return status; - } - - userObjBuilder.append("roles", modifiedRolesArray); - } - - *parsedUserObj = userObjBuilder.obj(); - - // Make sure document to insert is valid - V2UserDocumentParser parser; - status = parser.checkValidUserDocument(*parsedUserObj); - if (!status.isOK()) { - return status; - } - - return Status::OK(); - } - - - Status parseAndValidateUpdateUserCommand(const BSONObj& cmdObj, - const std::string& dbname, - AuthorizationManager* authzManager, - BSONObj* parsedUpdateObj, - UserName* parsedUserName, - BSONObj* parsedWriteConcern) { - unordered_set<std::string> validFieldNames; - validFieldNames.insert("updateUser"); - validFieldNames.insert("customData"); - validFieldNames.insert("pwd"); - validFieldNames.insert("roles"); - validFieldNames.insert("writeConcern"); - - Status status = _checkNoExtraFields(cmdObj, "updateUser", validFieldNames); - if (!status.isOK()) { - return status; - } - - status = _extractWriteConcern(cmdObj, parsedWriteConcern); - if (!status.isOK()) { - return status; - } - - BSONObjBuilder updateSetBuilder; - - // Parse user name - std::string userName; - 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, - true, - &modifiedRolesObj); + status = _extractRoleDataFromBSONArray(rolesElement, dbname, &parsedArgs->roles); if (!status.isOK()) { return status; } - - updateSetBuilder.append("roles", modifiedRolesObj); + parsedArgs->hasRoles = true; } - 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(); } @@ -559,82 +479,69 @@ namespace auth { return Status(ErrorCodes::FailedToParse, errmsg); } - if (parsedPrivileges) { - parsedPrivileges->push_back(privilege); - } + parsedPrivileges->push_back(privilege); } return Status::OK(); } - Status parseAndValidateCreateRoleCommand(const BSONObj& cmdObj, - const std::string& dbname, - AuthorizationManager* authzManager, - BSONObj* parsedRoleObj, - BSONObj* parsedWriteConcern) { + Status parseCreateOrUpdateRoleCommands(const BSONObj& cmdObj, + const StringData& cmdName, + const std::string& dbname, + CreateOrUpdateRoleArgs* parsedArgs) { unordered_set<std::string> validFieldNames; - validFieldNames.insert("createRole"); + validFieldNames.insert(cmdName.toString()); validFieldNames.insert("privileges"); validFieldNames.insert("roles"); validFieldNames.insert("writeConcern"); - Status status = _checkNoExtraFields(cmdObj, "createRole", validFieldNames); + Status status = _checkNoExtraFields(cmdObj, cmdName, validFieldNames); if (!status.isOK()) { return status; } - status = _extractWriteConcern(cmdObj, parsedWriteConcern); + status = _extractWriteConcern(cmdObj, &parsedArgs->writeConcern); if (!status.isOK()) { return status; } - BSONObjBuilder roleObjBuilder; - - // Parse role name std::string roleName; status = bsonExtractStringField(cmdObj, "createRole", &roleName); if (!status.isOK()) { return status; } - - // Prevent creating roles in the local database - if (dbname == "local") { - return Status(ErrorCodes::BadValue, "Cannot create roles in the local database"); - } - - roleObjBuilder.append("_id", dbname + "." + roleName); - roleObjBuilder.append(AuthorizationManager::ROLE_NAME_FIELD_NAME, roleName); - roleObjBuilder.append(AuthorizationManager::ROLE_SOURCE_FIELD_NAME, dbname); + parsedArgs->roleName = RoleName(roleName, dbname); // Parse privileges - BSONElement privilegesElement; - status = bsonExtractTypedField(cmdObj, "privileges", Array, &privilegesElement); - if (!status.isOK()) { - return status; - } - status = _parseAndValidatePrivilegeArray(BSONArray(privilegesElement.Obj()), NULL); - if (!status.isOK()) { - return status; + if (cmdObj.hasField("privileges")) { + BSONElement privilegesElement; + status = bsonExtractTypedField(cmdObj, "privileges", Array, &privilegesElement); + if (!status.isOK()) { + return status; + } + status = _parseAndValidatePrivilegeArray(BSONArray(privilegesElement.Obj()), + &parsedArgs->privileges); + if (!status.isOK()) { + return status; + } + parsedArgs->hasPrivileges = true; } - roleObjBuilder.append(privilegesElement); // Parse roles - BSONElement rolesElement; - status = bsonExtractTypedField(cmdObj, "roles", Array, &rolesElement); - if (!status.isOK()) { - return status; - } - BSONArray modifiedRolesArray; - status = _validateAndModifyRolesArray(rolesElement, - dbname, - authzManager, - false, - &modifiedRolesArray); - if (!status.isOK()) { - return status; + if (cmdObj.hasField("roles")) { + BSONElement rolesElement; + status = bsonExtractTypedField(cmdObj, "roles", Array, &rolesElement); + if (!status.isOK()) { + return status; + } + status = _extractRoleNamesFromBSONArray(BSONArray(rolesElement.Obj()), + dbname, + "roles", + &parsedArgs->roles); + if (!status.isOK()) { + return status; + } + parsedArgs->hasRoles = true; } - roleObjBuilder.append("roles", modifiedRolesArray); - - *parsedRoleObj = roleObjBuilder.obj(); return Status::OK(); } diff --git a/src/mongo/db/auth/user_management_commands_parser.h b/src/mongo/db/auth/user_management_commands_parser.h index 9407f004514..d1bca97c5ad 100644 --- a/src/mongo/db/auth/user_management_commands_parser.h +++ b/src/mongo/db/auth/user_management_commands_parser.h @@ -34,6 +34,7 @@ #include "mongo/base/disallow_copying.h" #include "mongo/db/auth/privilege.h" #include "mongo/db/auth/role_name.h" +#include "mongo/db/auth/user.h" #include "mongo/db/auth/user_name.h" #include "mongo/db/jsobj.h" @@ -43,31 +44,28 @@ namespace mongo { 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, - AuthorizationManager* authzManager, - BSONObj* parsedUserObj, - BSONObj* parsedWriteConcern); + struct CreateOrUpdateUserArgs { + UserName userName; + bool hasHashedPassword; + std::string hashedPassword; + bool hasCustomData; + BSONObj customData; + bool hasRoles; + std::vector<User::RoleData> roles; + BSONObj writeConcern; + CreateOrUpdateUserArgs() : + hasHashedPassword(false), hasCustomData(false), hasRoles(false) {} + }; /** - * 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. + * Takes a command object describing an invocation of the "createUser" or "updateUser" commands + * (which command it is is specified in "cmdName") on the database "dbname", and parses out all + * the arguments into the "parsedArgs" output param. */ - Status parseAndValidateUpdateUserCommand(const BSONObj& cmdObj, - const std::string& dbname, - AuthorizationManager* authzManager, - BSONObj* parsedUpdateObj, - UserName* parsedUserName, - BSONObj* parsedWriteConcern); + Status parseCreateOrUpdateUserCommands(const BSONObj& cmdObj, + const StringData& cmdName, + const std::string& dbname, + CreateOrUpdateUserArgs* parsedArgs); /** * Takes a command object describing an invocation of one of "grantRolesToUser", @@ -75,13 +73,13 @@ namespace auth { * command it is is specified in the "cmdName" argument), and parses out the user name of the * user being modified, the roles being granted or revoked, and the write concern to use. */ - Status parseUserRoleManipulationCommand(const BSONObj& cmdObj, - const StringData& cmdName, - const std::string& dbname, - AuthorizationManager* authzManager, - UserName* parsedUserName, - vector<RoleName>* parsedRoleNames, - BSONObj* parsedWriteConcern); + Status parseRolePossessionManipulationCommands(const BSONObj& cmdObj, + const StringData& cmdName, + const StringData& rolesFieldName, + const std::string& dbname, + std::string* parsedName, + vector<RoleName>* parsedRoleNames, + BSONObj* parsedWriteConcern); /** * Takes a command object describing an invocation of the "removeUser" command and parses out @@ -113,17 +111,26 @@ namespace auth { const std::string& dbname, bool* parsedAnyDb, BSONElement* parsedNameFilter); + + struct CreateOrUpdateRoleArgs { + RoleName roleName; + bool hasRoles; + std::vector<RoleName> roles; + bool hasPrivileges; + PrivilegeVector privileges; + BSONObj writeConcern; + CreateOrUpdateRoleArgs() : hasRoles(false), hasPrivileges(false) {} + }; + /** - * Takes a command object describing an invocation of the "createRole" command on the database - * "dbname", and returns (via the output param "parsedRoleObj") a role object that can be - * inserted into admin.system.roles to create the role as described by the command object. - * Also validates the input and returns a non-ok Status if there is anything wrong. + * Takes a command object describing an invocation of the "createRole" or "updateRole" commands + * (which command it is is specified in "cmdName") on the database "dbname", and parses out all + * the arguments into the "parsedArgs" output param. */ - Status parseAndValidateCreateRoleCommand(const BSONObj& cmdObj, - const std::string& dbname, - AuthorizationManager* authzManager, - BSONObj* parsedRoleObj, - BSONObj* parsedWriteConcern); + Status parseCreateOrUpdateRoleCommands(const BSONObj& cmdObj, + const StringData& cmdName, + const std::string& dbname, + CreateOrUpdateRoleArgs* parsedArgs); /** * Takes a command object describing an invocation of the "grantPrivilegesToRole" or diff --git a/src/mongo/db/auth/user_management_commands_parser_test.cpp b/src/mongo/db/auth/user_management_commands_parser_test.cpp deleted file mode 100644 index 84f516aef2c..00000000000 --- a/src/mongo/db/auth/user_management_commands_parser_test.cpp +++ /dev/null @@ -1,785 +0,0 @@ -/* 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/auth/user_management_commands_parser.h" -#include "mongo/db/jsobj.h" -#include "mongo/db/server_options.h" -#include "mongo/unittest/unittest.h" - -namespace mongo { - - // Crutches to make the test compile - ServerGlobalParams serverGlobalParams; - 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, WriteConcernParsing) { - BSONObj writeConcern; - BSONObj parsedUserObj; - // Test no write concern provided - ASSERT_OK(auth::parseAndValidateCreateUserCommand(BSON("createUser" << "spencer" << - "pwd" << "abc" << - "roles" << BSONArray()), - "test", - authzManager.get(), - &parsedUserObj, - &writeConcern)); - ASSERT(writeConcern.isEmpty()); - - ASSERT_OK(auth::parseAndValidateCreateUserCommand( - BSON("createUser" << "spencer" << - "pwd" << "abc" << - "roles" << BSONArray() << - "writeConcern" << BSON("w" << "majority" << "j" << true)), - "test", - authzManager.get(), - &parsedUserObj, - &writeConcern)); - - ASSERT_EQUALS("majority", writeConcern["w"].str()); - ASSERT_EQUALS(true, writeConcern["j"].Bool()); - } - - TEST_F(UserManagementCommandsParserTest, CreateUserCommandParsing) { - BSONArray emptyArray = BSONArrayBuilder().arr(); - BSONObj parsedUserObj; - BSONObj writeConcern; - - // Must have password - ASSERT_NOT_OK(auth::parseAndValidateCreateUserCommand(BSON("createUser" << "spencer" << - "roles" << emptyArray), - "test", - authzManager.get(), - &parsedUserObj, - &writeConcern)); - - // Must have roles array - ASSERT_NOT_OK(auth::parseAndValidateCreateUserCommand(BSON("createUser" << "spencer" << - "pwd" << "password"), - "test", - authzManager.get(), - &parsedUserObj, - &writeConcern)); - - // Cannot create users in the local db - ASSERT_NOT_OK(auth::parseAndValidateCreateUserCommand(BSON("createUser" << "spencer" << - "pwd" << "password" << - "roles" << emptyArray), - "local", - authzManager.get(), - &parsedUserObj, - &writeConcern)); - - // Cannot have extra fields - ASSERT_NOT_OK(auth::parseAndValidateCreateUserCommand(BSON("createUser" << "spencer" << - "pwd" << "password" << - "roles" << emptyArray << - "anotherField" << "garbage"), - "test", - authzManager.get(), - &parsedUserObj, - &writeConcern)); - - // Role must exist (string role) - ASSERT_NOT_OK(auth::parseAndValidateCreateUserCommand( - BSON("createUser" << "spencer" << - "pwd" << "password" << - "roles" << BSON_ARRAY("fakeRole")), - "test", - authzManager.get(), - &parsedUserObj, - &writeConcern)); - - // 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, - &writeConcern)); - - // 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, - &writeConcern)); - - // 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, - &writeConcern)); - - // 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, - &writeConcern)); - - // 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, - &writeConcern)); - - // 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, - &writeConcern)); - - // Empty roles array OK - ASSERT_OK(auth::parseAndValidateCreateUserCommand(BSON("createUser" << "spencer" << - "pwd" << "password" << - "roles" << emptyArray), - "test", - authzManager.get(), - &parsedUserObj, - &writeConcern)); - - - // Missing password OK if source is $external - ASSERT_OK(auth::parseAndValidateCreateUserCommand(BSON("createUser" << "spencer" << - "roles" << emptyArray), - "$external", - authzManager.get(), - &parsedUserObj, - &writeConcern)); - - // String role names OK - ASSERT_OK(auth::parseAndValidateCreateUserCommand( - BSON("createUser" << "spencer" << - "pwd" << "password" << - "roles" << BSON_ARRAY("read" << "dbAdmin")), - "test", - authzManager.get(), - &parsedUserObj, - &writeConcern)); - - 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, - &writeConcern)); - - 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; - BSONObj writeConcern; - 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, - &writeConcern)); - - // Role must exist (string role) - ASSERT_NOT_OK(auth::parseAndValidateUpdateUserCommand( - BSON("updateUser" << "spencer" << - "pwd" << "password" << - "roles" << BSON_ARRAY("fakeRole")), - "test", - authzManager.get(), - &parsedUpdateObj, - &parsedUserName, - &writeConcern)); - - // 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, - &writeConcern)); - - // 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, - &writeConcern)); - - // 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, - &writeConcern)); - - // 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, - &writeConcern)); - - // 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, - &writeConcern)); - - // 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, - &writeConcern)); - - // Empty roles array OK - ASSERT_OK(auth::parseAndValidateUpdateUserCommand(BSON("updateUser" << "spencer" << - "pwd" << "password" << - "roles" << emptyArray), - "test", - authzManager.get(), - &parsedUpdateObj, - &parsedUserName, - &writeConcern)); - - // String role names OK - ASSERT_OK(auth::parseAndValidateUpdateUserCommand( - BSON("updateUser" << "spencer" << - "pwd" << "password" << - "roles" << BSON_ARRAY("read" << "dbAdmin")), - "test", - authzManager.get(), - &parsedUpdateObj, - &parsedUserName, - &writeConcern)); - - 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, - &writeConcern)); - - 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()); - } - - TEST_F(UserManagementCommandsParserTest, UserRoleManipulationCommandsParsing) { - UserName userName; - std::vector<RoleName> roles; - BSONObj writeConcern; - - // Command name must match - ASSERT_NOT_OK(auth::parseUserRoleManipulationCommand( - BSON("grantRolesToUser" << "spencer" << - "roles" << BSON_ARRAY("read")), - "revokeRolesFromUser", - "test", - authzManager.get(), - &userName, - &roles, - &writeConcern)); - - // Roles array can't be empty - ASSERT_NOT_OK(auth::parseUserRoleManipulationCommand( - BSON("grantRolesToUser" << "spencer" << - "roles" << BSONArray()), - "grantRolesToUser", - "test", - authzManager.get(), - &userName, - &roles, - &writeConcern)); - - // Roles must exist - ASSERT_NOT_OK(auth::parseUserRoleManipulationCommand( - BSON("grantRolesToUser" << "spencer" << - "roles" << BSON_ARRAY("fakeRole")), - "grantRolesToUser", - "test", - authzManager.get(), - &userName, - &roles, - &writeConcern)); - - ASSERT_OK(auth::parseUserRoleManipulationCommand( - BSON("grantRolesToUser" << "spencer" << - "roles" << BSON_ARRAY("readWrite" << BSON("name" << "dbAdmin" << - "source" << "test2")) << - "writeConcern" << BSON("w" << 1)), - "grantRolesToUser", - "test", - authzManager.get(), - &userName, - &roles, - &writeConcern)); - - ASSERT_EQUALS(UserName("spencer", "test"), userName); - ASSERT_EQUALS(1, writeConcern["w"].numberInt()); - ASSERT_EQUALS(2U, roles.size()); - ASSERT_EQUALS(RoleName("readWrite", "test"), roles[0]); - ASSERT_EQUALS(RoleName("dbAdmin", "test2"), roles[1]); - } - - TEST_F(UserManagementCommandsParserTest, CreateRoleCommandParsing) { - BSONObj parsedRoleObj; - BSONObj parsedWriteConcern; - - - // Must have roles array - ASSERT_NOT_OK(auth::parseAndValidateCreateRoleCommand(BSON("createRole" << "myRole" << - "privileges" << BSONArray()), - "test", - authzManager.get(), - &parsedRoleObj, - &parsedWriteConcern)); - - // Must have privileges array - ASSERT_NOT_OK(auth::parseAndValidateCreateRoleCommand(BSON("createRole" << "myRole" << - "roles" << BSONArray()), - "test", - authzManager.get(), - &parsedRoleObj, - &parsedWriteConcern)); - - // Cannot create roles in the local db - ASSERT_NOT_OK(auth::parseAndValidateCreateRoleCommand(BSON("createRole" << "myRole" << - "privileges" << BSONArray() << - "roles" << BSONArray()), - "local", - authzManager.get(), - &parsedRoleObj, - &parsedWriteConcern)); - - // Cannot have extra fields - ASSERT_NOT_OK(auth::parseAndValidateCreateRoleCommand(BSON("createRole" << "myRole" << - "privileges" << BSONArray() << - "roles" << BSONArray() << - "anotherField" << true), - "test", - authzManager.get(), - &parsedRoleObj, - &parsedWriteConcern)); - - // Role must exist (string role) - ASSERT_NOT_OK(auth::parseAndValidateCreateRoleCommand( - BSON("createRole" << "myRole" << - "privileges" << BSONArray() << - "roles" << BSON_ARRAY("fakeRole")), - "test", - authzManager.get(), - &parsedRoleObj, - &parsedWriteConcern)); - - // Role must exist (object role) - ASSERT_NOT_OK(auth::parseAndValidateCreateRoleCommand( - BSON("createRole" << "myRole" << - "privileges" << BSONArray() << - "roles" << BSON_ARRAY("name" << "fakeRole" << "source" << "test")), - "test", - authzManager.get(), - &parsedRoleObj, - &parsedWriteConcern)); - - // Role must have name - ASSERT_NOT_OK(auth::parseAndValidateCreateRoleCommand( - BSON("createRole" << "myRole" << - "privileges" << BSONArray() << - "roles" << BSON_ARRAY("source" << "test")), - "test", - authzManager.get(), - &parsedRoleObj, - &parsedWriteConcern)); - - // Role must have source - ASSERT_NOT_OK(auth::parseAndValidateCreateRoleCommand( - BSON("createRole" << "myRole" << - "privileges" << BSONArray() << - "roles" << BSON_ARRAY("name" << "readWrite")), - "test", - authzManager.get(), - &parsedRoleObj, - &parsedWriteConcern)); - - // Role must not have have canDelegate or hasRole - ASSERT_NOT_OK(auth::parseAndValidateCreateRoleCommand( - BSON("createRole" << "myRole" << - "privileges" << BSONArray() << - "roles" << BSON_ARRAY("name" << "readWrite" << - "source" << "test" << - "hasRole" << true << - "canDelegate" << false)), - "test", - authzManager.get(), - &parsedRoleObj, - &parsedWriteConcern)); - - // Privilege must have resource - ASSERT_NOT_OK(auth::parseAndValidateCreateRoleCommand( - BSON("createRole" << "myRole" << - "privileges" << BSON_ARRAY(BSON("actions" << BSON_ARRAY("find"))) << - "roles" << BSONArray()), - "test", - authzManager.get(), - &parsedRoleObj, - &parsedWriteConcern)); - - // Privilege must have actions - ASSERT_NOT_OK(auth::parseAndValidateCreateRoleCommand( - BSON("createRole" << "myRole" << - "privileges" << BSON_ARRAY(BSON("resource" << BSON("cluster" << true))) << - "roles" << BSONArray()), - "test", - authzManager.get(), - &parsedRoleObj, - &parsedWriteConcern)); - - // Action must exist - ASSERT_NOT_OK(auth::parseAndValidateCreateRoleCommand( - BSON("createRole" << "myRole" << - "privileges" << BSON_ARRAY(BSON("resource" << BSON("cluster" << true) << - "actions" << BSON_ARRAY("fakeAction"))) << - "roles" << BSONArray()), - "test", - authzManager.get(), - &parsedRoleObj, - &parsedWriteConcern)); - - // Resource can't be on cluster AND normal resources - ASSERT_NOT_OK(auth::parseAndValidateCreateRoleCommand( - BSON("createRole" << "myRole" << - "privileges" << BSON_ARRAY(BSON("resource" << BSON("cluster" << true << - "db" << "" << - "collection" << "") << - "actions" << BSONArray())) << - "roles" << BSONArray()), - "test", - authzManager.get(), - &parsedRoleObj, - &parsedWriteConcern)); - - // Resource can't have db without collection - ASSERT_NOT_OK(auth::parseAndValidateCreateRoleCommand( - BSON("createRole" << "myRole" << - "privileges" << BSON_ARRAY(BSON("resource" << BSON("db" << "" ) << - "actions" << BSONArray())) << - "roles" << BSONArray()), - "test", - authzManager.get(), - &parsedRoleObj, - &parsedWriteConcern)); - - // Resource can't have collection without db - ASSERT_NOT_OK(auth::parseAndValidateCreateRoleCommand( - BSON("createRole" << "myRole" << - "privileges" << BSON_ARRAY(BSON("resource" << BSON("collection" << "" ) << - "actions" << BSONArray())) << - "roles" << BSONArray()), - "test", - authzManager.get(), - &parsedRoleObj, - &parsedWriteConcern)); - - // Empty roles and privileges arrays OK - ASSERT_OK(auth::parseAndValidateCreateRoleCommand( - BSON("createRole" << "myRole" << - "privileges" << BSONArray() << - "roles" << BSONArray()), - "test", - authzManager.get(), - &parsedRoleObj, - &parsedWriteConcern)); - - ASSERT_EQUALS("myRole", parsedRoleObj["name"].String()); - ASSERT_EQUALS("test", parsedRoleObj["source"].String()); - ASSERT_EQUALS(0U, parsedRoleObj["privileges"].Array().size()); - ASSERT_EQUALS(0U, parsedRoleObj["roles"].Array().size()); - - // Empty db and collection for privilege is OK. - ASSERT_OK(auth::parseAndValidateCreateRoleCommand( - BSON("createRole" << "myRole" << - "privileges" << BSON_ARRAY(BSON("resource" << BSON("db" << "" << - "collection" << "" ) << - "actions" << BSON_ARRAY("find"))) << - "roles" << BSONArray()), - "test", - authzManager.get(), - &parsedRoleObj, - &parsedWriteConcern)); - - std::vector<BSONElement> privileges = parsedRoleObj["privileges"].Array(); - ASSERT_EQUALS(1U, privileges.size()); - ASSERT_EQUALS("", privileges[0].Obj()["resource"].Obj()["db"].String()); - ASSERT_EQUALS("", privileges[0].Obj()["resource"].Obj()["collection"].String()); - ASSERT_EQUALS(1U, privileges[0].Obj()["actions"].Array().size()); - ASSERT_EQUALS("find", privileges[0].Obj()["actions"].Array()[0].String()); - - // Explicit collection name resource OK - ASSERT_OK(auth::parseAndValidateCreateRoleCommand( - BSON("createRole" << "myRole" << - "privileges" << BSON_ARRAY(BSON("resource" << BSON("db" << "test" << - "collection" << "foo" ) << - "actions" << BSON_ARRAY("find"))) << - "roles" << BSONArray()), - "test", - authzManager.get(), - &parsedRoleObj, - &parsedWriteConcern)); - - privileges = parsedRoleObj["privileges"].Array(); - ASSERT_EQUALS(1U, privileges.size()); - ASSERT_EQUALS("test", privileges[0].Obj()["resource"].Obj()["db"].String()); - ASSERT_EQUALS("foo", privileges[0].Obj()["resource"].Obj()["collection"].String()); - - // Cluster resource OK - ASSERT_OK(auth::parseAndValidateCreateRoleCommand( - BSON("createRole" << "myRole" << - "privileges" << BSON_ARRAY(BSON("resource" << BSON("cluster" << true) << - "actions" << BSON_ARRAY("find"))) << - "roles" << BSONArray()), - "test", - authzManager.get(), - &parsedRoleObj, - &parsedWriteConcern)); - - privileges = parsedRoleObj["privileges"].Array(); - ASSERT_EQUALS(1U, privileges.size()); - ASSERT_EQUALS(true, privileges[0].Obj()["resource"].Obj()["cluster"].Bool()); - - // String role names OK - ASSERT_OK(auth::parseAndValidateCreateRoleCommand( - BSON("createRole" << "myRole" << - "privileges" << BSONArray() << - "roles" << BSON_ARRAY("read" << "dbAdmin")), - "test", - authzManager.get(), - &parsedRoleObj, - &parsedWriteConcern)); - - std::vector<BSONElement> rolesArray = parsedRoleObj["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("dbAdmin", rolesArray[1].Obj()["name"].String()); - ASSERT_EQUALS("test", rolesArray[1].Obj()["source"].String()); - - // Object roles OK - ASSERT_OK(auth::parseAndValidateCreateRoleCommand( - BSON("createRole" << "myRole" << - "privileges" << BSONArray() << - "roles" << BSON_ARRAY(BSON("name" << "read" << "source" << "test") << - BSON("name" << "dbAdmin" << "source" << "test"))), - "test", - authzManager.get(), - &parsedRoleObj, - &parsedWriteConcern)); - - rolesArray = parsedRoleObj["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("dbAdmin", rolesArray[1].Obj()["name"].String()); - ASSERT_EQUALS("test", rolesArray[1].Obj()["source"].String()); - } - -} // namespace -} // namespace mongo diff --git a/src/mongo/db/commands/user_management_commands.cpp b/src/mongo/db/commands/user_management_commands.cpp index 6a6d2b322e7..d778e705ac1 100644 --- a/src/mongo/db/commands/user_management_commands.cpp +++ b/src/mongo/db/commands/user_management_commands.cpp @@ -44,6 +44,7 @@ #include "mongo/db/auth/authorization_manager_global.h" #include "mongo/db/auth/privilege.h" #include "mongo/db/auth/user.h" +#include "mongo/db/auth/user_document_parser.h" #include "mongo/db/auth/user_management_commands_parser.h" #include "mongo/db/commands.h" #include "mongo/db/jsobj.h" @@ -52,6 +53,8 @@ namespace mongo { + using mongoutils::str::stream; + static void addStatus(const Status& status, BSONObjBuilder& builder) { builder.append("ok", status.isOK() ? 1.0: 0.0); if (!status.isOK()) @@ -71,7 +74,7 @@ namespace mongo { } } - static BSONArray rolesToBSONArray(const User::RoleDataMap& roles) { + static BSONArray roleDataMapToBSONArray(const User::RoleDataMap& roles) { BSONArrayBuilder arrBuilder; for (User::RoleDataMap::const_iterator it = roles.begin(); it != roles.end(); ++it) { const User::RoleData& role = it->second; @@ -83,6 +86,72 @@ namespace mongo { return arrBuilder.arr(); } + // Should only be called inside the AuthzUpdateLock + static Status getBSONForRoleVectorIfRolesExist(const std::vector<RoleName>& roles, + AuthorizationManager* authzManager, + BSONArray* result) { + BSONArrayBuilder rolesArrayBuilder; + for (std::vector<RoleName>::const_iterator it = roles.begin(); it != roles.end(); ++it) { + const RoleName& role = *it; + if (!authzManager->roleExists(role)) { + return Status(ErrorCodes::RoleNotFound, + mongoutils::str::stream() << role.getFullName() << + " does not name an existing role"); + } + rolesArrayBuilder.append(BSON("name" << role.getRole() << "source" << role.getDB())); + } + *result = rolesArrayBuilder.arr(); + return Status::OK(); + } + + // Should only be called inside the AuthzUpdateLock + static Status roleDataVectorToBSONArrayIfRolesExist(const std::vector<User::RoleData>& roles, + AuthorizationManager* authzManager, + BSONArray* result) { + BSONArrayBuilder rolesArrayBuilder; + for (std::vector<User::RoleData>::const_iterator it = roles.begin(); + it != roles.end(); ++it) { + const User::RoleData& role = *it; + if (!authzManager->roleExists(role.name)) { + return Status(ErrorCodes::RoleNotFound, + mongoutils::str::stream() << it->name.getFullName() << + " does not name an existing role"); + } + if (!role.hasRole && !role.canDelegate) { + return Status(ErrorCodes::BadValue, "At least one of \"hasRole\" and " + "\"canDelegate\" must be true for every role object"); + } + rolesArrayBuilder.append(BSON("name" << role.name.getRole() << + "source" << role.name.getDB() << + "hasRole" << role.hasRole << + "canDelegate" << role.canDelegate)); + } + *result = rolesArrayBuilder.arr(); + return Status::OK(); + } + + static Status privilegeVectorToBSONArray(const PrivilegeVector& privileges, BSONArray* result) { + BSONArrayBuilder arrBuilder; + for (PrivilegeVector::const_iterator it = privileges.begin(); + it != privileges.end(); ++it) { + const Privilege& privilege = *it; + + ParsedPrivilege parsedPrivilege; + std::string errmsg; + if (!ParsedPrivilege::privilegeToParsedPrivilege(privilege, + &parsedPrivilege, + &errmsg)) { + return Status(ErrorCodes::FailedToParse, errmsg); + } + if (!parsedPrivilege.isValid(&errmsg)) { + return Status(ErrorCodes::FailedToParse, errmsg); + } + arrBuilder.append(parsedPrivilege.toBSON()); + } + *result = arrBuilder.arr(); + return Status::OK(); + } + static Status getCurrentUserRoles(AuthorizationManager* authzManager, const UserName& userName, User::RoleDataMap* roles) { @@ -132,6 +201,52 @@ namespace mongo { string& errmsg, BSONObjBuilder& result, bool fromRepl) { + auth::CreateOrUpdateUserArgs args; + Status status = auth::parseCreateOrUpdateUserCommands(cmdObj, + "createUser", + dbname, + &args); + if (!status.isOK()) { + addStatus(status, result); + return false; + } + + if (args.userName.getDB() == "local") { + addStatus(Status(ErrorCodes::BadValue, "Cannot create users in the local database"), + result); + return false; + } + + if (!args.hasHashedPassword && args.userName.getDB() != "$external") { + addStatus(Status(ErrorCodes::BadValue, + "Must provide a 'pwd' field for all user documents, except those" + " with '$external' as the user's source"), + result); + return false; + } + + if (!args.hasRoles) { + addStatus(Status(ErrorCodes::BadValue, + "\"createUser\" command requires a \"roles\" array"), + result); + return false; + } + + BSONObjBuilder userObjBuilder; + userObjBuilder.append("_id", + stream() << args.userName.getDB() << "." << + args.userName.getUser()); + userObjBuilder.append(AuthorizationManager::USER_NAME_FIELD_NAME, + args.userName.getUser()); + userObjBuilder.append(AuthorizationManager::USER_SOURCE_FIELD_NAME, + args.userName.getDB()); + if (args.hasHashedPassword) { + userObjBuilder.append("credentials", BSON("MONGODB-CR" << args.hashedPassword)); + } + if (args.hasCustomData) { + userObjBuilder.append("customData", args.customData); + } + AuthorizationManager* authzManager = getGlobalAuthorizationManager(); AuthzDocumentsUpdateGuard updateGuard(authzManager); if (!updateGuard.tryLock("Create user")) { @@ -140,19 +255,25 @@ namespace mongo { return false; } - BSONObj userObj; - BSONObj writeConcern; - Status status = auth::parseAndValidateCreateUserCommand(cmdObj, - dbname, - authzManager, - &userObj, - &writeConcern); + // Role existence has to be checked after acquiring the update lock + BSONArray rolesArray; + status = roleDataVectorToBSONArrayIfRolesExist(args.roles, authzManager, &rolesArray); if (!status.isOK()) { addStatus(status, result); return false; } + userObjBuilder.append("roles", rolesArray); - status = authzManager->insertPrivilegeDocument(dbname, userObj, writeConcern); + BSONObj userObj = userObjBuilder.obj(); + V2UserDocumentParser parser; + status = parser.checkValidUserDocument(userObj); + if (!status.isOK()) { + addStatus(status, result); + return false; + } + status = authzManager->insertPrivilegeDocument(dbname, + userObj, + args.writeConcern); if (!status.isOK()) { addStatus(status, result); return false; @@ -202,6 +323,31 @@ namespace mongo { string& errmsg, BSONObjBuilder& result, bool fromRepl) { + auth::CreateOrUpdateUserArgs args; + Status status = auth::parseCreateOrUpdateUserCommands(cmdObj, + "updateUser", + dbname, + &args); + if (!status.isOK()) { + addStatus(status, result); + return false; + } + + if (!args.hasHashedPassword && !args.hasCustomData && !args.hasRoles) { + addStatus(Status(ErrorCodes::BadValue, + "Must specify at least one field to update in updateUser"), + result); + return false; + } + + BSONObjBuilder updateSetBuilder; + if (args.hasHashedPassword) { + updateSetBuilder.append("credentials.MONGODB-CR", args.hashedPassword); + } + if (args.hasCustomData) { + updateSetBuilder.append("customData", args.customData); + } + AuthorizationManager* authzManager = getGlobalAuthorizationManager(); AuthzDocumentsUpdateGuard updateGuard(authzManager); if (!updateGuard.tryLock("Update user")) { @@ -210,23 +356,24 @@ namespace mongo { return false; } - BSONObj updateObj; - UserName userName; - BSONObj writeConcern; - Status status = auth::parseAndValidateUpdateUserCommand(cmdObj, - dbname, - authzManager, - &updateObj, - &userName, - &writeConcern); - if (!status.isOK()) { - addStatus(status, result); - return false; + // Role existence has to be checked after acquiring the update lock + if (args.hasRoles) { + BSONArray rolesArray; + status = roleDataVectorToBSONArrayIfRolesExist(args.roles, + authzManager, + &rolesArray); + if (!status.isOK()) { + addStatus(status, result); + return false; + } + updateSetBuilder.append("roles", rolesArray); } - status = authzManager->updatePrivilegeDocument(userName, updateObj, writeConcern); + status = authzManager->updatePrivilegeDocument(args.userName, + BSON("$set" << updateSetBuilder.done()), + args.writeConcern); // Must invalidate even on bad status - what if the write succeeded but the GLE failed? - authzManager->invalidateUserByName(userName); + authzManager->invalidateUserByName(args.userName); if (!status.isOK()) { addStatus(status, result); return false; @@ -438,21 +585,22 @@ namespace mongo { return false; } - UserName userName; + std::string userNameString; std::vector<RoleName> roles; BSONObj writeConcern; - Status status = auth::parseUserRoleManipulationCommand(cmdObj, - "grantRolesToUser", - dbname, - authzManager, - &userName, - &roles, - &writeConcern); + Status status = auth::parseRolePossessionManipulationCommands(cmdObj, + "grantRolesToUser", + "roles", + dbname, + &userNameString, + &roles, + &writeConcern); if (!status.isOK()) { addStatus(status, result); return false; } + UserName userName(userNameString, dbname); User::RoleDataMap userRoles; status = getCurrentUserRoles(authzManager, userName, &userRoles); if (!status.isOK()) { @@ -462,6 +610,13 @@ namespace mongo { for (vector<RoleName>::iterator it = roles.begin(); it != roles.end(); ++it) { RoleName& roleName = *it; + if (!authzManager->roleExists(roleName)) { + addStatus(Status(ErrorCodes::RoleNotFound, + mongoutils::str::stream() << roleName.getFullName() << + " does not name an existing role"), + result); + return false; + } User::RoleData& role = userRoles[roleName]; if (role.name.empty()) { role.name = roleName; @@ -469,7 +624,7 @@ namespace mongo { role.hasRole = true; } - BSONArray newRolesBSONArray = rolesToBSONArray(userRoles); + BSONArray newRolesBSONArray = roleDataMapToBSONArray(userRoles); status = authzManager->updatePrivilegeDocument( userName, BSON("$set" << BSON("roles" << newRolesBSONArray)), writeConcern); // Must invalidate even on bad status - what if the write succeeded but the GLE failed? @@ -528,21 +683,22 @@ namespace mongo { return false; } - UserName userName; + std::string userNameString; std::vector<RoleName> roles; BSONObj writeConcern; - Status status = auth::parseUserRoleManipulationCommand(cmdObj, - "revokeRolesFromUser", - dbname, - authzManager, - &userName, - &roles, - &writeConcern); + Status status = auth::parseRolePossessionManipulationCommands(cmdObj, + "revokeRolesFromUser", + "roles", + dbname, + &userNameString, + &roles, + &writeConcern); if (!status.isOK()) { addStatus(status, result); return false; } + UserName userName(userNameString, dbname); User::RoleDataMap userRoles; status = getCurrentUserRoles(authzManager, userName, &userRoles); if (!status.isOK()) { @@ -552,6 +708,13 @@ namespace mongo { for (vector<RoleName>::iterator it = roles.begin(); it != roles.end(); ++it) { RoleName& roleName = *it; + if (!authzManager->roleExists(roleName)) { + addStatus(Status(ErrorCodes::RoleNotFound, + mongoutils::str::stream() << roleName.getFullName() << + " does not name an existing role"), + result); + return false; + } User::RoleDataMap::iterator roleDataIt = userRoles.find(roleName); if (roleDataIt == userRoles.end()) { continue; // User already doesn't have the role, nothing to do @@ -567,7 +730,7 @@ namespace mongo { } } - BSONArray newRolesBSONArray = rolesToBSONArray(userRoles); + BSONArray newRolesBSONArray = roleDataMapToBSONArray(userRoles); status = authzManager->updatePrivilegeDocument( userName, BSON("$set" << BSON("roles" << newRolesBSONArray)), writeConcern); // Must invalidate even on bad status - what if the write succeeded but the GLE failed? @@ -626,21 +789,22 @@ namespace mongo { return false; } - UserName userName; + std::string userNameString; std::vector<RoleName> roles; BSONObj writeConcern; - Status status = auth::parseUserRoleManipulationCommand(cmdObj, - "grantDelegateRolesToUser", - dbname, - authzManager, - &userName, - &roles, - &writeConcern); + Status status = auth::parseRolePossessionManipulationCommands(cmdObj, + "grantDelegateRolesToUser", + "roles", + dbname, + &userNameString, + &roles, + &writeConcern); if (!status.isOK()) { addStatus(status, result); return false; } + UserName userName(userNameString, dbname); User::RoleDataMap userRoles; status = getCurrentUserRoles(authzManager, userName, &userRoles); if (!status.isOK()) { @@ -650,6 +814,13 @@ namespace mongo { for (vector<RoleName>::iterator it = roles.begin(); it != roles.end(); ++it) { RoleName& roleName = *it; + if (!authzManager->roleExists(roleName)) { + addStatus(Status(ErrorCodes::RoleNotFound, + mongoutils::str::stream() << roleName.getFullName() << + " does not name an existing role"), + result); + return false; + } User::RoleData& role = userRoles[roleName]; if (role.name.empty()) { role.name = roleName; @@ -657,7 +828,7 @@ namespace mongo { role.canDelegate = true; } - BSONArray newRolesBSONArray = rolesToBSONArray(userRoles); + BSONArray newRolesBSONArray = roleDataMapToBSONArray(userRoles); status = authzManager->updatePrivilegeDocument( userName, BSON("$set" << BSON("roles" << newRolesBSONArray)), writeConcern); // Must invalidate even on bad status - what if the write succeeded but the GLE failed? @@ -716,21 +887,23 @@ namespace mongo { return false; } - UserName userName; + std::string userNameString; std::vector<RoleName> roles; BSONObj writeConcern; - Status status = auth::parseUserRoleManipulationCommand(cmdObj, - "revokeDelegateRolesFromUser", - dbname, - authzManager, - &userName, - &roles, - &writeConcern); + Status status = + auth::parseRolePossessionManipulationCommands(cmdObj, + "revokeDelegateRolesFromUser", + "roles", + dbname, + &userNameString, + &roles, + &writeConcern); if (!status.isOK()) { addStatus(status, result); return false; } + UserName userName(userNameString, dbname); User::RoleDataMap userRoles; status = getCurrentUserRoles(authzManager, userName, &userRoles); if (!status.isOK()) { @@ -740,6 +913,13 @@ namespace mongo { for (vector<RoleName>::iterator it = roles.begin(); it != roles.end(); ++it) { RoleName& roleName = *it; + if (!authzManager->roleExists(roleName)) { + addStatus(Status(ErrorCodes::RoleNotFound, + mongoutils::str::stream() << roleName.getFullName() << + " does not name an existing role"), + result); + return false; + } User::RoleDataMap::iterator roleDataIt = userRoles.find(roleName); if (roleDataIt == userRoles.end()) { continue; // User already doesn't have the role, nothing to do @@ -755,7 +935,7 @@ namespace mongo { } } - BSONArray newRolesBSONArray = rolesToBSONArray(userRoles); + BSONArray newRolesBSONArray = roleDataMapToBSONArray(userRoles); status = authzManager->updatePrivilegeDocument( userName, BSON("$set" << BSON("roles" << newRolesBSONArray)), writeConcern); // Must invalidate even on bad status - what if the write succeeded but the GLE failed? @@ -877,6 +1057,53 @@ namespace mongo { string& errmsg, BSONObjBuilder& result, bool fromRepl) { + auth::CreateOrUpdateRoleArgs args; + Status status = auth::parseCreateOrUpdateRoleCommands(cmdObj, + "createRole", + dbname, + &args); + if (!status.isOK()) { + addStatus(status, result); + return false; + } + + if (args.roleName.getDB() == "local") { + addStatus(Status(ErrorCodes::BadValue, "Cannot create roles in the local database"), + result); + return false; + } + + if (!args.hasRoles) { + addStatus(Status(ErrorCodes::BadValue, + "\"createRole\" command requires a \"roles\" array"), + result); + return false; + } + + if (!args.hasPrivileges) { + addStatus(Status(ErrorCodes::BadValue, + "\"createRole\" command requires a \"privileges\" array"), + result); + return false; + } + + BSONObjBuilder roleObjBuilder; + + roleObjBuilder.append("_id", stream() << args.roleName.getDB() << "." << + args.roleName.getRole()); + roleObjBuilder.append(AuthorizationManager::ROLE_NAME_FIELD_NAME, + args.roleName.getRole()); + roleObjBuilder.append(AuthorizationManager::ROLE_SOURCE_FIELD_NAME, + args.roleName.getDB()); + + BSONArray privileges; + status = privilegeVectorToBSONArray(args.privileges, &privileges); + if (!status.isOK()) { + addStatus(status, result); + return false; + } + roleObjBuilder.append("privileges", privileges); + AuthorizationManager* authzManager = getGlobalAuthorizationManager(); AuthzDocumentsUpdateGuard updateGuard(authzManager); if (!updateGuard.tryLock("Create role")) { @@ -885,19 +1112,16 @@ namespace mongo { return false; } - BSONObj roleObj; - BSONObj writeConcern; - Status status = auth::parseAndValidateCreateRoleCommand(cmdObj, - dbname, - authzManager, - &roleObj, - &writeConcern); + // Role existence has to be checked after acquiring the update lock + BSONArray roles; + status = getBSONForRoleVectorIfRolesExist(args.roles, authzManager, &roles); if (!status.isOK()) { addStatus(status, result); return false; } + roleObjBuilder.append("roles", roles); - status = authzManager->insertRoleDocument(roleObj, writeConcern); + status = authzManager->insertRoleDocument(roleObjBuilder.done(), args.writeConcern); if (!status.isOK()) { addStatus(status, result); return false; @@ -1022,7 +1246,7 @@ namespace mongo { return true; } - } cmdGrantPrivilegeToRole; + } cmdGrantPrivilegesToRole; class CmdRolesInfo: public Command { public: |