diff options
author | Daniel Alabi <alabidan@gmail.com> | 2015-05-11 09:25:12 -0400 |
---|---|---|
committer | Daniel Alabi <alabidan@gmail.com> | 2015-05-13 16:46:17 -0400 |
commit | fbae7c1bf914a594ce3f4e4338e2231bba872582 (patch) | |
tree | 1f18e2b0a677e36e6c546eeae0ffc8f0df5fea06 | |
parent | 4be360e1d25b7c15e88a19b410493f7df68f3cbc (diff) | |
download | mongo-fbae7c1bf914a594ce3f4e4338e2231bba872582.tar.gz |
SERVER-18328 Create mongos version of user management commands that uses catalog manager
-rw-r--r-- | src/mongo/db/SConscript | 3 | ||||
-rw-r--r-- | src/mongo/db/commands/user_management_commands.cpp | 748 | ||||
-rw-r--r-- | src/mongo/db/commands/user_management_commands.h | 199 | ||||
-rw-r--r-- | src/mongo/db/commands/user_management_commands_common.cpp | 749 | ||||
-rw-r--r-- | src/mongo/s/catalog/catalog_manager.h | 21 | ||||
-rw-r--r-- | src/mongo/s/catalog/legacy/catalog_manager_legacy.cpp | 103 | ||||
-rw-r--r-- | src/mongo/s/catalog/legacy/catalog_manager_legacy.h | 12 | ||||
-rw-r--r-- | src/mongo/s/commands/SConscript | 1 | ||||
-rw-r--r-- | src/mongo/s/commands/cluster_user_management_commands.cpp | 791 |
9 files changed, 1958 insertions, 669 deletions
diff --git a/src/mongo/db/SConscript b/src/mongo/db/SConscript index a074bbbf81b..92cbb92e5fe 100644 --- a/src/mongo/db/SConscript +++ b/src/mongo/db/SConscript @@ -427,7 +427,7 @@ coredbEnv.Library( "commands/rename_collection_common.cpp", "commands/server_status.cpp", "commands/parameters.cpp", - "commands/user_management_commands.cpp", + "commands/user_management_commands_common.cpp", "commands/write_commands/write_commands_common.cpp", "pipeline/pipeline.cpp", "dbcommands_generic.cpp", @@ -547,6 +547,7 @@ serverOnlyFiles = [ "commands/test_commands.cpp", "commands/top_command.cpp", "commands/touch.cpp", + "commands/user_management_commands.cpp", "commands/validate.cpp", "commands/write_commands/batch_executor.cpp", "commands/write_commands/write_commands.cpp", diff --git a/src/mongo/db/commands/user_management_commands.cpp b/src/mongo/db/commands/user_management_commands.cpp index d0813279a0f..fbac3ef177b 100644 --- a/src/mongo/db/commands/user_management_commands.cpp +++ b/src/mongo/db/commands/user_management_commands.cpp @@ -30,6 +30,8 @@ #include "mongo/platform/basic.h" +#include "mongo/db/commands/user_management_commands.h" + #include <string> #include <vector> @@ -72,250 +74,6 @@ namespace mongo { using std::stringstream; using std::vector; - static void redactPasswordData(mutablebson::Element parent) { - namespace mmb = mutablebson; - const StringData pwdFieldName("pwd", StringData::LiteralTag()); - for (mmb::Element pwdElement = mmb::findFirstChildNamed(parent, pwdFieldName); - pwdElement.ok(); - pwdElement = mmb::findElementNamed(pwdElement.rightSibling(), pwdFieldName)) { - - pwdElement.setValueString("xxx"); - } - } - - static BSONArray roleSetToBSONArray(const unordered_set<RoleName>& roles) { - BSONArrayBuilder rolesArrayBuilder; - for (unordered_set<RoleName>::const_iterator it = roles.begin(); it != roles.end(); ++it) { - const RoleName& role = *it; - rolesArrayBuilder.append( - BSON(AuthorizationManager::ROLE_NAME_FIELD_NAME << role.getRole() << - AuthorizationManager::ROLE_DB_FIELD_NAME << role.getDB())); - } - return rolesArrayBuilder.arr(); - } - - static BSONArray rolesVectorToBSONArray(const std::vector<RoleName>& roles) { - BSONArrayBuilder rolesArrayBuilder; - for (std::vector<RoleName>::const_iterator it = roles.begin(); it != roles.end(); ++it) { - const RoleName& role = *it; - rolesArrayBuilder.append( - BSON(AuthorizationManager::ROLE_NAME_FIELD_NAME << role.getRole() << - AuthorizationManager::ROLE_DB_FIELD_NAME << role.getDB())); - } - return rolesArrayBuilder.arr(); - } - - 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(OperationContext* txn, - AuthorizationManager* authzManager, - const UserName& userName, - unordered_set<RoleName>* roles) { - User* user; - authzManager->invalidateUserByName(userName); // Need to make sure cache entry is up to date - Status status = authzManager->acquireUser(txn, userName, &user); - if (!status.isOK()) { - return status; - } - RoleNameIterator rolesIt = user->getRoles(); - while (rolesIt.more()) { - roles->insert(rolesIt.next()); - } - authzManager->releaseUser(user); - return Status::OK(); - } - - static Status checkAuthorizedToGrantRoles(AuthorizationSession* authzSession, - const std::vector<RoleName>& roles) { - for (size_t i = 0; i < roles.size(); ++i) { - if (!authzSession->isAuthorizedToGrantRole(roles[i])) { - return Status(ErrorCodes::Unauthorized, - str::stream() << "Not authorized to grant role: " << - roles[i].getFullName()); - } - } - return Status::OK(); - } - - static Status checkAuthorizedToRevokeRoles(AuthorizationSession* authzSession, - const std::vector<RoleName>& roles) { - for (size_t i = 0; i < roles.size(); ++i) { - if (!authzSession->isAuthorizedToRevokeRole(roles[i])) { - return Status(ErrorCodes::Unauthorized, - str::stream() << "Not authorized to revoke role: " << - roles[i].getFullName()); - } - } - return Status::OK(); - } - - static Status checkAuthorizedToGrantPrivileges(AuthorizationSession* authzSession, - const PrivilegeVector& privileges) { - for (PrivilegeVector::const_iterator it = privileges.begin(); - it != privileges.end(); ++it) { - Status status = authzSession->checkAuthorizedToGrantPrivilege(*it); - if (!status.isOK()) { - return status; - } - } - - return Status::OK(); - } - - static Status checkAuthorizedToRevokePrivileges(AuthorizationSession* authzSession, - const PrivilegeVector& privileges) { - for (PrivilegeVector::const_iterator it = privileges.begin(); - it != privileges.end(); ++it) { - Status status = authzSession->checkAuthorizedToRevokePrivilege(*it); - if (!status.isOK()) { - return status; - } - } - - return Status::OK(); - } - - /* - * Checks that every role in "rolesToAdd" exists, that adding each of those roles to "role" - * will not result in a cycle to the role graph, and that every role being added comes from the - * same database as the role it is being added to (or that the role being added to is from the - * "admin" database. - */ - static Status checkOkayToGrantRolesToRole(const RoleName& role, - const std::vector<RoleName> rolesToAdd, - AuthorizationManager* authzManager) { - for (vector<RoleName>::const_iterator it = rolesToAdd.begin(); - it != rolesToAdd.end(); ++it) { - const RoleName& roleToAdd = *it; - if (roleToAdd == role) { - return Status(ErrorCodes::InvalidRoleModification, - mongoutils::str::stream() << "Cannot grant role " << - role.getFullName() << " to itself."); - } - - if (role.getDB() != "admin" && roleToAdd.getDB() != role.getDB()) { - return Status(ErrorCodes::InvalidRoleModification, - str::stream() << "Roles on the \'" << role.getDB() << - "\' database cannot be granted roles from other databases"); - } - - BSONObj roleToAddDoc; - Status status = authzManager->getRoleDescription(roleToAdd, false, &roleToAddDoc); - if (status == ErrorCodes::RoleNotFound) { - return Status(ErrorCodes::RoleNotFound, - "Cannot grant nonexistent role " + roleToAdd.toString()); - } - if (!status.isOK()) { - return status; - } - std::vector<RoleName> indirectRoles; - status = auth::parseRoleNamesFromBSONArray( - BSONArray(roleToAddDoc["inheritedRoles"].Obj()), - role.getDB(), - &indirectRoles); - if (!status.isOK()) { - return status; - } - - if (sequenceContains(indirectRoles, role)) { - return Status(ErrorCodes::InvalidRoleModification, - mongoutils::str::stream() << "Granting " << - roleToAdd.getFullName() << " to " << role.getFullName() - << " would introduce a cycle in the role graph."); - } - } - return Status::OK(); - } - - /** - * Checks that every privilege being granted targets just the database the role is from, or that - * the role is from the "admin" db. - */ - static Status checkOkayToGrantPrivilegesToRole(const RoleName& role, - const PrivilegeVector& privileges) { - - if (role.getDB() == "admin") { - return Status::OK(); - } - - for (PrivilegeVector::const_iterator it = privileges.begin(); - it != privileges.end(); ++it) { - const ResourcePattern& resource = (*it).getResourcePattern(); - if ((resource.isDatabasePattern() || resource.isExactNamespacePattern()) && - (resource.databaseToMatch() == role.getDB())) { - continue; - } - - return Status(ErrorCodes::InvalidRoleModification, - str::stream() << "Roles on the \'" << role.getDB() << - "\' database cannot be granted privileges that target other " - "databases or the cluster"); - } - - return Status::OK(); - } - - static Status requireAuthSchemaVersion26Final(OperationContext* txn, - AuthorizationManager* authzManager) { - int foundSchemaVersion; - Status status = authzManager->getAuthorizationVersion(txn, &foundSchemaVersion); - if (!status.isOK()) { - return status; - } - - if (foundSchemaVersion < AuthorizationManager::schemaVersion26Final) { - return Status( - ErrorCodes::AuthSchemaIncompatible, - str::stream() << "User and role management commands require auth data to have " - "at least schema version " << AuthorizationManager::schemaVersion26Final << - " but found " << foundSchemaVersion); - } - return authzManager->writeAuthSchemaVersionIfNeeded(txn, foundSchemaVersion); - } - - static Status requireAuthSchemaVersion26UpgradeOrFinal(OperationContext* txn, - AuthorizationManager* authzManager) { - int foundSchemaVersion; - Status status = authzManager->getAuthorizationVersion(txn, &foundSchemaVersion); - if (!status.isOK()) { - return status; - } - - if (foundSchemaVersion < AuthorizationManager::schemaVersion26Upgrade) { - return Status( - ErrorCodes::AuthSchemaIncompatible, - str::stream() << "The usersInfo and rolesInfo commands require auth data to " - "have at least schema version " << - AuthorizationManager::schemaVersion26Upgrade << - " but found " << foundSchemaVersion); - } - return Status::OK(); - } - - static void appendBSONObjToBSONArrayBuilder(BSONArrayBuilder* array, const BSONObj& obj) { - array->append(obj); - } - class CmdCreateUser : public Command { public: @@ -325,7 +83,7 @@ namespace mongo { return false; } - virtual bool isWriteCommandForConfigServer() const { return false; } + virtual bool isWriteCommandForConfigServer() const { return true; } virtual void help(stringstream& ss) const { ss << "Adds a user to the system" << endl; @@ -334,28 +92,11 @@ namespace mongo { virtual Status checkAuthForCommand(ClientBasic* client, const std::string& dbname, const BSONObj& cmdObj) { - AuthorizationSession* authzSession = AuthorizationSession::get(client); - auth::CreateOrUpdateUserArgs args; - Status status = auth::parseCreateOrUpdateUserCommands(cmdObj, - "createUser", - dbname, - &args); - if (!status.isOK()) { - return status; - } - - if (!authzSession->isAuthorizedForActionsOnResource( - ResourcePattern::forDatabaseName(args.userName.getDB()), - ActionType::createUser)) { - return Status(ErrorCodes::Unauthorized, - str::stream() << "Not authorized to create users on db: " << - args.userName.getDB()); - } - - return checkAuthorizedToGrantRoles(authzSession, args.roles); + return auth::checkAuthForCreateUserCommand(client, dbname, cmdObj); } - bool run(OperationContext* txn, const string& dbname, + bool run(OperationContext* txn, + const string& dbname, BSONObj& cmdObj, int options, string& errmsg, @@ -450,7 +191,7 @@ namespace mongo { if (args.hasCustomData) { userObjBuilder.append("customData", args.customData); } - userObjBuilder.append("roles", rolesVectorToBSONArray(args.roles)); + userObjBuilder.append("roles", auth::rolesVectorToBSONArray(args.roles)); BSONObj userObj = userObjBuilder.obj(); V2UserDocumentParser parser; @@ -466,7 +207,7 @@ namespace mongo { Status(ErrorCodes::LockBusy, "Could not lock auth data update lock.")); } - status = requireAuthSchemaVersion26Final(txn, authzManager); + status = auth::requireAuthSchemaVersion26Final(txn, authzManager); if (!status.isOK()) { return appendCommandStatus(result, status); } @@ -493,7 +234,7 @@ namespace mongo { } virtual void redactForLogging(mutablebson::Document* cmdObj) { - redactPasswordData(cmdObj->root()); + auth::redactPasswordData(cmdObj->root()); } } cmdCreateUser; @@ -507,7 +248,7 @@ namespace mongo { return false; } - virtual bool isWriteCommandForConfigServer() const { return false; } + virtual bool isWriteCommandForConfigServer() const { return true; } virtual void help(stringstream& ss) const { ss << "Used to update a user, for example to change its password" << endl; @@ -516,51 +257,7 @@ namespace mongo { virtual Status checkAuthForCommand(ClientBasic* client, const std::string& dbname, const BSONObj& cmdObj) { - AuthorizationSession* authzSession = AuthorizationSession::get(client); - auth::CreateOrUpdateUserArgs args; - Status status = auth::parseCreateOrUpdateUserCommands(cmdObj, - "updateUser", - dbname, - &args); - if (!status.isOK()) { - return status; - } - - if (args.hasHashedPassword) { - if (!authzSession->isAuthorizedToChangeOwnPasswordAsUser(args.userName) && - !authzSession->isAuthorizedForActionsOnResource( - ResourcePattern::forDatabaseName(args.userName.getDB()), - ActionType::changePassword)) { - return Status(ErrorCodes::Unauthorized, - str::stream() << "Not authorized to change password of user: " << - args.userName.getFullName()); - } - } - - if (args.hasCustomData) { - if (!authzSession->isAuthorizedToChangeOwnCustomDataAsUser(args.userName) && - !authzSession->isAuthorizedForActionsOnResource( - ResourcePattern::forDatabaseName(args.userName.getDB()), - ActionType::changeCustomData)) { - return Status(ErrorCodes::Unauthorized, - str::stream() << "Not authorized to change customData of user: " - << args.userName.getFullName()); - } - } - - if (args.hasRoles) { - // You don't know what roles you might be revoking, so require the ability to - // revoke any role in the system. - if (!authzSession->isAuthorizedForActionsOnResource( - ResourcePattern::forAnyNormalResource(), ActionType::revokeRole)) { - return Status(ErrorCodes::Unauthorized, - "In order to use updateUser to set roles array, must be " - "authorized to revoke any role in the system"); - } - - return checkAuthorizedToGrantRoles(authzSession, args.roles); - } - return Status::OK(); + return auth::checkAuthForUpdateUserCommand(client, dbname, cmdObj); } bool run(OperationContext* txn, const string& dbname, @@ -619,7 +316,7 @@ namespace mongo { updateSetBuilder.append("customData", args.customData); } if (args.hasRoles) { - updateSetBuilder.append("roles", rolesVectorToBSONArray(args.roles)); + updateSetBuilder.append("roles", auth::rolesVectorToBSONArray(args.roles)); } AuthorizationManager* authzManager = getGlobalAuthorizationManager(); @@ -630,7 +327,7 @@ namespace mongo { Status(ErrorCodes::LockBusy, "Could not lock auth data update lock.")); } - status = requireAuthSchemaVersion26Final(txn, authzManager); + status = auth::requireAuthSchemaVersion26Final(txn, authzManager); if (!status.isOK()) { return appendCommandStatus(result, status); } @@ -663,7 +360,7 @@ namespace mongo { } virtual void redactForLogging(mutablebson::Document* cmdObj) { - redactPasswordData(cmdObj->root()); + auth::redactPasswordData(cmdObj->root()); } } cmdUpdateUser; @@ -677,7 +374,7 @@ namespace mongo { return false; } - virtual bool isWriteCommandForConfigServer() const { return false; } + virtual bool isWriteCommandForConfigServer() const { return true; } virtual void help(stringstream& ss) const { ss << "Drops a single user." << endl; @@ -686,24 +383,7 @@ namespace mongo { virtual Status checkAuthForCommand(ClientBasic* client, const std::string& dbname, const BSONObj& cmdObj) { - AuthorizationSession* authzSession = AuthorizationSession::get(client); - UserName userName; - BSONObj unusedWriteConcern; - Status status = auth::parseAndValidateDropUserCommand(cmdObj, - dbname, - &userName, - &unusedWriteConcern); - if (!status.isOK()) { - return status; - } - - if (!authzSession->isAuthorizedForActionsOnResource( - ResourcePattern::forDatabaseName(userName.getDB()), ActionType::dropUser)) { - return Status(ErrorCodes::Unauthorized, - str::stream() << "Not authorized to drop users from the " << - userName.getDB() << " database"); - } - return Status::OK(); + return auth::checkAuthForDropUserCommand(client, dbname, cmdObj); } bool run(OperationContext* txn, const string& dbname, @@ -719,7 +399,7 @@ namespace mongo { Status(ErrorCodes::LockBusy, "Could not lock auth data update lock.")); } - Status status = requireAuthSchemaVersion26Final(txn, authzManager); + Status status = auth::requireAuthSchemaVersion26Final(txn, authzManager); if (!status.isOK()) { return appendCommandStatus(result, status); } @@ -773,7 +453,7 @@ namespace mongo { return false; } - virtual bool isWriteCommandForConfigServer() const { return false; } + virtual bool isWriteCommandForConfigServer() const { return true; } virtual void help(stringstream& ss) const { ss << "Drops all users for a single database." << endl; @@ -782,14 +462,7 @@ namespace mongo { virtual Status checkAuthForCommand(ClientBasic* client, const std::string& dbname, const BSONObj& cmdObj) { - AuthorizationSession* authzSession = AuthorizationSession::get(client); - if (!authzSession->isAuthorizedForActionsOnResource( - ResourcePattern::forDatabaseName(dbname), ActionType::dropUser)) { - return Status(ErrorCodes::Unauthorized, - str::stream() << "Not authorized to drop users from the " << - dbname << " database"); - } - return Status::OK(); + return auth::checkAuthForDropAllUsersFromDatabaseCommand(client, dbname); } bool run(OperationContext* txn, const string& dbname, @@ -805,7 +478,7 @@ namespace mongo { Status(ErrorCodes::LockBusy, "Could not lock auth data update lock.")); } - Status status = requireAuthSchemaVersion26Final(txn, authzManager); + Status status = auth::requireAuthSchemaVersion26Final(txn, authzManager); if (!status.isOK()) { return appendCommandStatus(result, status); } @@ -848,7 +521,7 @@ namespace mongo { return false; } - virtual bool isWriteCommandForConfigServer() const { return false; } + virtual bool isWriteCommandForConfigServer() const { return true; } virtual void help(stringstream& ss) const { ss << "Grants roles to a user." << endl; @@ -857,21 +530,7 @@ namespace mongo { virtual Status checkAuthForCommand(ClientBasic* client, const std::string& dbname, const BSONObj& cmdObj) { - AuthorizationSession* authzSession = AuthorizationSession::get(client); - std::vector<RoleName> roles; - std::string unusedUserNameString; - BSONObj unusedWriteConcern; - Status status = auth::parseRolePossessionManipulationCommands(cmdObj, - "grantRolesToUser", - dbname, - &unusedUserNameString, - &roles, - &unusedWriteConcern); - if (!status.isOK()) { - return status; - } - - return checkAuthorizedToGrantRoles(authzSession, roles); + return auth::checkAuthForGrantRolesToUserCommand(client, dbname, cmdObj); } bool run(OperationContext* txn, const string& dbname, @@ -887,7 +546,7 @@ namespace mongo { Status(ErrorCodes::LockBusy, "Could not lock auth data update lock.")); } - Status status = requireAuthSchemaVersion26Final(txn, authzManager); + Status status = auth::requireAuthSchemaVersion26Final(txn, authzManager); if (!status.isOK()) { return appendCommandStatus(result, status); } @@ -907,7 +566,7 @@ namespace mongo { UserName userName(userNameString, dbname); unordered_set<RoleName> userRoles; - status = getCurrentUserRoles(txn, authzManager, userName, &userRoles); + status = auth::getCurrentUserRoles(txn, authzManager, userName, &userRoles); if (!status.isOK()) { return appendCommandStatus(result, status); } @@ -926,7 +585,7 @@ namespace mongo { audit::logGrantRolesToUser(ClientBasic::getCurrent(), userName, roles); - BSONArray newRolesBSONArray = roleSetToBSONArray(userRoles); + BSONArray newRolesBSONArray = auth::roleSetToBSONArray(userRoles); status = authzManager->updatePrivilegeDocument( txn, userName, BSON("$set" << BSON("roles" << newRolesBSONArray)), writeConcern); // Must invalidate even on bad status - what if the write succeeded but the GLE failed? @@ -945,7 +604,7 @@ namespace mongo { return false; } - virtual bool isWriteCommandForConfigServer() const { return false; } + virtual bool isWriteCommandForConfigServer() const { return true; } virtual void help(stringstream& ss) const { ss << "Revokes roles from a user." << endl; @@ -954,21 +613,7 @@ namespace mongo { virtual Status checkAuthForCommand(ClientBasic* client, const std::string& dbname, const BSONObj& cmdObj) { - AuthorizationSession* authzSession = AuthorizationSession::get(client); - std::vector<RoleName> roles; - std::string unusedUserNameString; - BSONObj unusedWriteConcern; - Status status = auth::parseRolePossessionManipulationCommands(cmdObj, - "revokeRolesFromUser", - dbname, - &unusedUserNameString, - &roles, - &unusedWriteConcern); - if (!status.isOK()) { - return status; - } - - return checkAuthorizedToRevokeRoles(authzSession, roles); + return auth::checkAuthForRevokeRolesFromUserCommand(client, dbname, cmdObj); } bool run(OperationContext* txn, const string& dbname, @@ -984,7 +629,7 @@ namespace mongo { Status(ErrorCodes::LockBusy, "Could not lock auth data update lock.")); } - Status status = requireAuthSchemaVersion26Final(txn, authzManager); + Status status = auth::requireAuthSchemaVersion26Final(txn, authzManager); if (!status.isOK()) { return appendCommandStatus(result, status); } @@ -1004,7 +649,7 @@ namespace mongo { UserName userName(userNameString, dbname); unordered_set<RoleName> userRoles; - status = getCurrentUserRoles(txn, authzManager, userName, &userRoles); + status = auth::getCurrentUserRoles(txn, authzManager, userName, &userRoles); if (!status.isOK()) { return appendCommandStatus(result, status); } @@ -1023,7 +668,7 @@ namespace mongo { audit::logRevokeRolesFromUser(ClientBasic::getCurrent(), userName, roles); - BSONArray newRolesBSONArray = roleSetToBSONArray(userRoles); + BSONArray newRolesBSONArray = auth::roleSetToBSONArray(userRoles); status = authzManager->updatePrivilegeDocument( txn, userName, BSON("$set" << BSON("roles" << newRolesBSONArray)), writeConcern); // Must invalidate even on bad status - what if the write succeeded but the GLE failed? @@ -1055,35 +700,7 @@ namespace mongo { virtual Status checkAuthForCommand(ClientBasic* client, const std::string& dbname, const BSONObj& cmdObj) { - AuthorizationSession* authzSession = AuthorizationSession::get(client); - auth::UsersInfoArgs args; - Status status = auth::parseUsersInfoCommand(cmdObj, dbname, &args); - if (!status.isOK()) { - return status; - } - - if (args.allForDB) { - if (!authzSession->isAuthorizedForActionsOnResource( - ResourcePattern::forDatabaseName(dbname), ActionType::viewUser)) { - return Status(ErrorCodes::Unauthorized, - str::stream() << "Not authorized to view users from the " << - dbname << " database"); - } - } else { - for (size_t i = 0; i < args.userNames.size(); ++i) { - if (authzSession->lookupUser(args.userNames[i])) { - continue; // Can always view users you are logged in as - } - if (!authzSession->isAuthorizedForActionsOnResource( - ResourcePattern::forDatabaseName(args.userNames[i].getDB()), - ActionType::viewUser)) { - return Status(ErrorCodes::Unauthorized, - str::stream() << "Not authorized to view users from the " << - dbname << " database"); - } - } - } - return Status::OK(); + return auth::checkAuthForUsersInfoCommand(client, dbname, cmdObj); } bool run(OperationContext* txn, const string& dbname, @@ -1098,8 +715,9 @@ namespace mongo { return appendCommandStatus(result, status); } - status = requireAuthSchemaVersion26UpgradeOrFinal(txn, - getGlobalAuthorizationManager()); + status = + auth::requireAuthSchemaVersion26UpgradeOrFinal(txn, + getGlobalAuthorizationManager()); if (!status.isOK()) { return appendCommandStatus(result, status); } @@ -1161,7 +779,7 @@ namespace mongo { projection.append("credentials", 0); } const stdx::function<void(const BSONObj&)> function = stdx::bind( - appendBSONObjToBSONArrayBuilder, + auth::appendBSONObjToBSONArrayBuilder, &usersArrayBuilder, stdx::placeholders::_1); authzManager->queryAuthzDocument(txn, @@ -1185,7 +803,7 @@ namespace mongo { return false; } - virtual bool isWriteCommandForConfigServer() const { return false; } + virtual bool isWriteCommandForConfigServer() const { return true; } virtual void help(stringstream& ss) const { ss << "Adds a role to the system" << endl; @@ -1194,30 +812,7 @@ namespace mongo { virtual Status checkAuthForCommand(ClientBasic* client, const std::string& dbname, const BSONObj& cmdObj) { - AuthorizationSession* authzSession = AuthorizationSession::get(client); - auth::CreateOrUpdateRoleArgs args; - Status status = auth::parseCreateOrUpdateRoleCommands(cmdObj, - "createRole", - dbname, - &args); - if (!status.isOK()) { - return status; - } - - if (!authzSession->isAuthorizedForActionsOnResource( - ResourcePattern::forDatabaseName(args.roleName.getDB()), - ActionType::createRole)) { - return Status(ErrorCodes::Unauthorized, - str::stream() << "Not authorized to create roles on db: " << - args.roleName.getDB()); - } - - status = checkAuthorizedToGrantRoles(authzSession, args.roles); - if (!status.isOK()) { - return status; - } - - return checkAuthorizedToGrantPrivileges(authzSession, args.privileges); + return auth::checkAuthForCreateRoleCommand(client, dbname, cmdObj); } bool run(OperationContext* txn, const string& dbname, @@ -1277,13 +872,13 @@ namespace mongo { args.roleName.getDB()); BSONArray privileges; - status = privilegeVectorToBSONArray(args.privileges, &privileges); + status = auth::privilegeVectorToBSONArray(args.privileges, &privileges); if (!status.isOK()) { return appendCommandStatus(result, status); } roleObjBuilder.append("privileges", privileges); - roleObjBuilder.append("roles", rolesVectorToBSONArray(args.roles)); + roleObjBuilder.append("roles", auth::rolesVectorToBSONArray(args.roles)); AuthorizationManager* authzManager = getGlobalAuthorizationManager(); AuthzDocumentsUpdateGuard updateGuard(authzManager); @@ -1293,18 +888,18 @@ namespace mongo { Status(ErrorCodes::LockBusy, "Could not lock auth data update lock.")); } - status = requireAuthSchemaVersion26Final(txn, authzManager); + status = auth::requireAuthSchemaVersion26Final(txn, authzManager); if (!status.isOK()) { return appendCommandStatus(result, status); } // Role existence has to be checked after acquiring the update lock - status = checkOkayToGrantRolesToRole(args.roleName, args.roles, authzManager); + status = auth::checkOkayToGrantRolesToRole(args.roleName, args.roles, authzManager); if (!status.isOK()) { return appendCommandStatus(result, status); } - status = checkOkayToGrantPrivilegesToRole(args.roleName, args.privileges); + status = auth::checkOkayToGrantPrivilegesToRole(args.roleName, args.privileges); if (!status.isOK()) { return appendCommandStatus(result, status); } @@ -1329,7 +924,7 @@ namespace mongo { return false; } - virtual bool isWriteCommandForConfigServer() const { return false; } + virtual bool isWriteCommandForConfigServer() const { return true; } virtual void help(stringstream& ss) const { ss << "Used to update a role" << endl; @@ -1338,31 +933,7 @@ namespace mongo { virtual Status checkAuthForCommand(ClientBasic* client, const std::string& dbname, const BSONObj& cmdObj) { - AuthorizationSession* authzSession = AuthorizationSession::get(client); - auth::CreateOrUpdateRoleArgs args; - Status status = auth::parseCreateOrUpdateRoleCommands(cmdObj, - "updateRole", - dbname, - &args); - if (!status.isOK()) { - return status; - } - - // You don't know what roles or privileges you might be revoking, so require the ability - // to revoke any role (or privilege) in the system. - if (!authzSession->isAuthorizedForActionsOnResource( - ResourcePattern::forAnyNormalResource(), ActionType::revokeRole)) { - return Status(ErrorCodes::Unauthorized, - "updateRole command required the ability to revoke any role in the " - "system"); - } - - status = checkAuthorizedToGrantRoles(authzSession, args.roles); - if (!status.isOK()) { - return status; - } - - return checkAuthorizedToGrantPrivileges(authzSession, args.privileges); + return auth::checkAuthForUpdateRoleCommand(client, dbname, cmdObj); } bool run(OperationContext* txn, const string& dbname, @@ -1390,7 +961,7 @@ namespace mongo { if (args.hasPrivileges) { BSONArray privileges; - status = privilegeVectorToBSONArray(args.privileges, &privileges); + status = auth::privilegeVectorToBSONArray(args.privileges, &privileges); if (!status.isOK()) { return appendCommandStatus(result, status); } @@ -1398,7 +969,7 @@ namespace mongo { } if (args.hasRoles) { - updateSetBuilder.append("roles", rolesVectorToBSONArray(args.roles)); + updateSetBuilder.append("roles", auth::rolesVectorToBSONArray(args.roles)); } AuthorizationManager* authzManager = getGlobalAuthorizationManager(); @@ -1409,7 +980,7 @@ namespace mongo { Status(ErrorCodes::LockBusy, "Could not lock auth data update lock.")); } - status = requireAuthSchemaVersion26Final(txn, authzManager); + status = auth::requireAuthSchemaVersion26Final(txn, authzManager); if (!status.isOK()) { return appendCommandStatus(result, status); } @@ -1422,14 +993,14 @@ namespace mongo { } if (args.hasRoles) { - status = checkOkayToGrantRolesToRole(args.roleName, args.roles, authzManager); + status = auth::checkOkayToGrantRolesToRole(args.roleName, args.roles, authzManager); if (!status.isOK()) { return appendCommandStatus(result, status); } } if (args.hasPrivileges) { - status = checkOkayToGrantPrivilegesToRole(args.roleName, args.privileges); + status = auth::checkOkayToGrantPrivilegesToRole(args.roleName, args.privileges); if (!status.isOK()) { return appendCommandStatus(result, status); } @@ -1459,7 +1030,7 @@ namespace mongo { return false; } - virtual bool isWriteCommandForConfigServer() const { return false; } + virtual bool isWriteCommandForConfigServer() const { return true; } virtual void help(stringstream& ss) const { ss << "Grants privileges to a role" << endl; @@ -1468,22 +1039,7 @@ namespace mongo { virtual Status checkAuthForCommand(ClientBasic* client, const std::string& dbname, const BSONObj& cmdObj) { - AuthorizationSession* authzSession = AuthorizationSession::get(client); - PrivilegeVector privileges; - RoleName unusedRoleName; - BSONObj unusedWriteConcern; - Status status = auth::parseAndValidateRolePrivilegeManipulationCommands( - cmdObj, - "grantPrivilegesToRole", - dbname, - &unusedRoleName, - &privileges, - &unusedWriteConcern); - if (!status.isOK()) { - return status; - } - - return checkAuthorizedToGrantPrivileges(authzSession, privileges); + return auth::checkAuthForGrantPrivilegesToRoleCommand(client, dbname, cmdObj); } bool run(OperationContext* txn, const string& dbname, @@ -1499,7 +1055,7 @@ namespace mongo { Status(ErrorCodes::LockBusy, "Could not lock auth data update lock.")); } - Status status = requireAuthSchemaVersion26Final(txn, authzManager); + Status status = auth::requireAuthSchemaVersion26Final(txn, authzManager); if (!status.isOK()) { return appendCommandStatus(result, status); } @@ -1526,7 +1082,7 @@ namespace mongo { " is a built-in role and cannot be modified.")); } - status = checkOkayToGrantPrivilegesToRole(roleName, privilegesToAdd); + status = auth::checkOkayToGrantPrivilegesToRole(roleName, privilegesToAdd); if (!status.isOK()) { return appendCommandStatus(result, status); } @@ -1595,7 +1151,7 @@ namespace mongo { return false; } - virtual bool isWriteCommandForConfigServer() const { return false; } + virtual bool isWriteCommandForConfigServer() const { return true; } virtual void help(stringstream& ss) const { ss << "Revokes privileges from a role" << endl; @@ -1604,22 +1160,7 @@ namespace mongo { virtual Status checkAuthForCommand(ClientBasic* client, const std::string& dbname, const BSONObj& cmdObj) { - AuthorizationSession* authzSession = AuthorizationSession::get(client); - PrivilegeVector privileges; - RoleName unusedRoleName; - BSONObj unusedWriteConcern; - Status status = auth::parseAndValidateRolePrivilegeManipulationCommands( - cmdObj, - "revokePrivilegesFromRole", - dbname, - &unusedRoleName, - &privileges, - &unusedWriteConcern); - if (!status.isOK()) { - return status; - } - - return checkAuthorizedToRevokePrivileges(authzSession, privileges); + return auth::checkAuthForRevokePrivilegesFromRoleCommand(client, dbname, cmdObj); } bool run(OperationContext* txn, const string& dbname, @@ -1635,7 +1176,7 @@ namespace mongo { Status(ErrorCodes::LockBusy, "Could not lock auth data update lock.")); } - Status status = requireAuthSchemaVersion26Final(txn, authzManager); + Status status = auth::requireAuthSchemaVersion26Final(txn, authzManager); if (!status.isOK()) { return appendCommandStatus(result, status); } @@ -1733,7 +1274,7 @@ namespace mongo { return false; } - virtual bool isWriteCommandForConfigServer() const { return false; } + virtual bool isWriteCommandForConfigServer() const { return true; } virtual void help(stringstream& ss) const { ss << "Grants roles to another role." << endl; @@ -1742,21 +1283,7 @@ namespace mongo { virtual Status checkAuthForCommand(ClientBasic* client, const std::string& dbname, const BSONObj& cmdObj) { - AuthorizationSession* authzSession = AuthorizationSession::get(client); - std::vector<RoleName> roles; - std::string unusedUserNameString; - BSONObj unusedWriteConcern; - Status status = auth::parseRolePossessionManipulationCommands(cmdObj, - "grantRolesToRole", - dbname, - &unusedUserNameString, - &roles, - &unusedWriteConcern); - if (!status.isOK()) { - return status; - } - - return checkAuthorizedToGrantRoles(authzSession, roles); + return auth::checkAuthForGrantRolesToRoleCommand(client, dbname, cmdObj); } bool run(OperationContext* txn, const string& dbname, @@ -1795,7 +1322,7 @@ namespace mongo { Status(ErrorCodes::LockBusy, "Could not lock auth data update lock.")); } - status = requireAuthSchemaVersion26Final(txn, authzManager); + status = auth::requireAuthSchemaVersion26Final(txn, authzManager); if (!status.isOK()) { return appendCommandStatus(result, status); } @@ -1808,7 +1335,7 @@ namespace mongo { } // Check for cycles - status = checkOkayToGrantRolesToRole(roleName, rolesToAdd, authzManager); + status = auth::checkOkayToGrantRolesToRole(roleName, rolesToAdd, authzManager); if (!status.isOK()) { return appendCommandStatus(result, status); } @@ -1834,7 +1361,7 @@ namespace mongo { status = authzManager->updateRoleDocument( txn, roleName, - BSON("$set" << BSON("roles" << rolesVectorToBSONArray(directRoles))), + BSON("$set" << BSON("roles" << auth::rolesVectorToBSONArray(directRoles))), writeConcern); // Must invalidate even on bad status - what if the write succeeded but the GLE failed? authzManager->invalidateUserCache(); @@ -1852,7 +1379,7 @@ namespace mongo { return false; } - virtual bool isWriteCommandForConfigServer() const { return false; } + virtual bool isWriteCommandForConfigServer() const { return true; } virtual void help(stringstream& ss) const { ss << "Revokes roles from another role." << endl; @@ -1861,21 +1388,7 @@ namespace mongo { virtual Status checkAuthForCommand(ClientBasic* client, const std::string& dbname, const BSONObj& cmdObj) { - AuthorizationSession* authzSession = AuthorizationSession::get(client); - std::vector<RoleName> roles; - std::string unusedUserNameString; - BSONObj unusedWriteConcern; - Status status = auth::parseRolePossessionManipulationCommands(cmdObj, - "revokeRolesFromRole", - dbname, - &unusedUserNameString, - &roles, - &unusedWriteConcern); - if (!status.isOK()) { - return status; - } - - return checkAuthorizedToRevokeRoles(authzSession, roles); + return auth::checkAuthForRevokeRolesFromRoleCommand(client, dbname, cmdObj); } bool run(OperationContext* txn, const string& dbname, @@ -1891,7 +1404,7 @@ namespace mongo { Status(ErrorCodes::LockBusy, "Could not lock auth data update lock.")); } - Status status = requireAuthSchemaVersion26Final(txn, authzManager); + Status status = auth::requireAuthSchemaVersion26Final(txn, authzManager); if (!status.isOK()) { return appendCommandStatus(result, status); } @@ -1947,7 +1460,7 @@ namespace mongo { status = authzManager->updateRoleDocument( txn, roleName, - BSON("$set" << BSON("roles" << rolesVectorToBSONArray(roles))), + BSON("$set" << BSON("roles" << auth::rolesVectorToBSONArray(roles))), writeConcern); // Must invalidate even on bad status - what if the write succeeded but the GLE failed? authzManager->invalidateUserCache(); @@ -1965,7 +1478,7 @@ namespace mongo { return false; } - virtual bool isWriteCommandForConfigServer() const { return false; } + virtual bool isWriteCommandForConfigServer() const { return true; } virtual void help(stringstream& ss) const { ss << "Drops a single role. Before deleting the role completely it must remove it " @@ -1977,24 +1490,7 @@ namespace mongo { virtual Status checkAuthForCommand(ClientBasic* client, const std::string& dbname, const BSONObj& cmdObj) { - AuthorizationSession* authzSession = AuthorizationSession::get(client); - RoleName roleName; - BSONObj unusedWriteConcern; - Status status = auth::parseDropRoleCommand(cmdObj, - dbname, - &roleName, - &unusedWriteConcern); - if (!status.isOK()) { - return status; - } - - if (!authzSession->isAuthorizedForActionsOnResource( - ResourcePattern::forDatabaseName(roleName.getDB()), ActionType::dropRole)) { - return Status(ErrorCodes::Unauthorized, - str::stream() << "Not authorized to drop roles from the " << - roleName.getDB() << " database"); - } - return Status::OK(); + return auth::checkAuthForDropRoleCommand(client, dbname, cmdObj); } bool run(OperationContext* txn, const string& dbname, @@ -2010,7 +1506,7 @@ namespace mongo { Status(ErrorCodes::LockBusy, "Could not lock auth data update lock.")); } - Status status = requireAuthSchemaVersion26Final(txn, authzManager); + Status status = auth::requireAuthSchemaVersion26Final(txn, authzManager); if (!status.isOK()) { return appendCommandStatus(result, status); } @@ -2144,7 +1640,7 @@ namespace mongo { return false; } - virtual bool isWriteCommandForConfigServer() const { return false; } + virtual bool isWriteCommandForConfigServer() const { return true; } virtual void help(stringstream& ss) const { ss << "Drops all roles from the given database. Before deleting the roles completely " @@ -2157,14 +1653,7 @@ namespace mongo { virtual Status checkAuthForCommand(ClientBasic* client, const std::string& dbname, const BSONObj& cmdObj) { - AuthorizationSession* authzSession = AuthorizationSession::get(client); - if (!authzSession->isAuthorizedForActionsOnResource( - ResourcePattern::forDatabaseName(dbname), ActionType::dropRole)) { - return Status(ErrorCodes::Unauthorized, - str::stream() << "Not authorized to drop roles from the " << - dbname << " database"); - } - return Status::OK(); + return auth::checkAuthForDropAllRolesFromDatabaseCommand(client, dbname); } bool run(OperationContext* txn, const string& dbname, @@ -2188,7 +1677,7 @@ namespace mongo { Status(ErrorCodes::LockBusy, "Could not lock auth data update lock.")); } - status = requireAuthSchemaVersion26Final(txn, authzManager); + status = auth::requireAuthSchemaVersion26Final(txn, authzManager); if (!status.isOK()) { return appendCommandStatus(result, status); } @@ -2291,37 +1780,7 @@ namespace mongo { virtual Status checkAuthForCommand(ClientBasic* client, const std::string& dbname, const BSONObj& cmdObj) { - AuthorizationSession* authzSession = AuthorizationSession::get(client); - auth::RolesInfoArgs args; - Status status = auth::parseRolesInfoCommand(cmdObj, dbname, &args); - if (!status.isOK()) { - return status; - } - - if (args.allForDB) { - if (!authzSession->isAuthorizedForActionsOnResource( - ResourcePattern::forDatabaseName(dbname), ActionType::viewRole)) { - return Status(ErrorCodes::Unauthorized, - str::stream() << "Not authorized to view roles from the " << - dbname << " database"); - } - } else { - for (size_t i = 0; i < args.roleNames.size(); ++i) { - if (authzSession->isAuthenticatedAsUserWithRole(args.roleNames[i])) { - continue; // Can always see roles that you are a member of - } - - if (!authzSession->isAuthorizedForActionsOnResource( - ResourcePattern::forDatabaseName(args.roleNames[i].getDB()), - ActionType::viewRole)) { - return Status(ErrorCodes::Unauthorized, - str::stream() << "Not authorized to view roles from the " << - args.roleNames[i].getDB() << " database"); - } - } - } - - return Status::OK(); + return auth::checkAuthForRolesInfoCommand(client, dbname, cmdObj); } bool run(OperationContext* txn, const string& dbname, @@ -2336,8 +1795,9 @@ namespace mongo { return appendCommandStatus(result, status); } - status = requireAuthSchemaVersion26UpgradeOrFinal(txn, - getGlobalAuthorizationManager()); + status = + auth::requireAuthSchemaVersion26UpgradeOrFinal(txn, + getGlobalAuthorizationManager()); if (!status.isOK()) { return appendCommandStatus(result, status); } @@ -2396,12 +1856,7 @@ namespace mongo { virtual Status checkAuthForCommand(ClientBasic* client, const std::string& dbname, const BSONObj& cmdObj) { - AuthorizationSession* authzSession = AuthorizationSession::get(client); - if (!authzSession->isAuthorizedForActionsOnResource( - ResourcePattern::forClusterResource(), ActionType::invalidateUserCache)) { - return Status(ErrorCodes::Unauthorized, "Not authorized to invalidate user cache"); - } - return Status::OK(); + return auth::checkAuthForInvalidateUserCacheCommand(client); } bool run(OperationContext* txn, const string& dbname, @@ -2439,12 +1894,7 @@ namespace mongo { virtual Status checkAuthForCommand(ClientBasic* client, const std::string& dbname, const BSONObj& cmdObj) { - AuthorizationSession* authzSession = AuthorizationSession::get(client); - if (!authzSession->isAuthorizedForActionsOnResource( - ResourcePattern::forClusterResource(), ActionType::internal)) { - return Status(ErrorCodes::Unauthorized, "Not authorized to get cache generation"); - } - return Status::OK(); + return auth::checkAuthForGetUserCacheGenerationCommand(client); } bool run(OperationContext* txn, @@ -2480,7 +1930,7 @@ namespace mongo { return false; } - virtual bool isWriteCommandForConfigServer() const { return false; } + virtual bool isWriteCommandForConfigServer() const { return true; } virtual bool adminOnly() const { return true; @@ -2493,45 +1943,7 @@ namespace mongo { virtual Status checkAuthForCommand(ClientBasic* client, const std::string& dbname, const BSONObj& cmdObj) { - auth::MergeAuthzCollectionsArgs args; - Status status = auth::parseMergeAuthzCollectionsCommand(cmdObj, &args); - if (!status.isOK()) { - return status; - } - - AuthorizationSession* authzSession = AuthorizationSession::get(client); - ActionSet actions; - actions.addAction(ActionType::createUser); - actions.addAction(ActionType::createRole); - actions.addAction(ActionType::grantRole); - actions.addAction(ActionType::revokeRole); - if (args.drop) { - actions.addAction(ActionType::dropUser); - actions.addAction(ActionType::dropRole); - } - if (!authzSession->isAuthorizedForActionsOnResource( - ResourcePattern::forAnyNormalResource(), actions)) { - return Status(ErrorCodes::Unauthorized, - "Not authorized to update user/role data using _mergeAuthzCollections" - " command"); - } - if (!args.usersCollName.empty() && - !authzSession->isAuthorizedForActionsOnResource( - ResourcePattern::forExactNamespace(NamespaceString(args.usersCollName)), - ActionType::find)) { - return Status(ErrorCodes::Unauthorized, - mongoutils::str::stream() << "Not authorized to read " << - args.usersCollName); - } - if (!args.rolesCollName.empty() && - !authzSession->isAuthorizedForActionsOnResource( - ResourcePattern::forExactNamespace(NamespaceString(args.rolesCollName)), - ActionType::find)) { - return Status(ErrorCodes::Unauthorized, - mongoutils::str::stream() << "Not authorized to read " << - args.rolesCollName); - } - return Status::OK(); + return auth::checkAuthForMergeAuthzCollectionsCommand(client, cmdObj); } static UserName extractUserNameFromBSON(const BSONObj& userObj) { @@ -2916,7 +2328,7 @@ namespace mongo { Status(ErrorCodes::LockBusy, "Could not lock auth data update lock.")); } - status = requireAuthSchemaVersion26Final(txn, authzManager); + status = auth::requireAuthSchemaVersion26Final(txn, authzManager); if (!status.isOK()) { return appendCommandStatus(result, status); } diff --git a/src/mongo/db/commands/user_management_commands.h b/src/mongo/db/commands/user_management_commands.h new file mode 100644 index 00000000000..5f21c719117 --- /dev/null +++ b/src/mongo/db/commands/user_management_commands.h @@ -0,0 +1,199 @@ +/** + * Copyright (C) 2015 MongoDB Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the GNU Affero General Public License in all respects for + * all of the code used other than as permitted herein. If you modify file(s) + * with this exception, you may extend this exception to your version of the + * file(s), but you are not obligated to do so. If you do not wish to do so, + * delete this exception statement from your version. If you delete this + * exception statement from all source files in the program, then also delete + * it in the license file. + */ + +#pragma once + +#include <string> +#include <vector> + +#include "mongo/bson/mutable/element.h" +#include "mongo/db/auth/privilege.h" +#include "mongo/db/auth/role_name.h" +#include "mongo/db/auth/user_name.h" +#include "mongo/platform/unordered_set.h" + +namespace mongo { + + class AuthorizationManager; + class AuthorizationSession; + struct BSONArray; + class BSONObj; + class ClientBasic; + class OperationContext; + +namespace auth { + + /** + * Looks for a field name "pwd" in the given BSONObj and if found replaces its contents with the + * string "xxx" so that password data on the command object used in executing a user management + * command isn't exposed in the logs. + */ + void redactPasswordData(mutablebson::Element parent); + + BSONArray roleSetToBSONArray(const unordered_set<RoleName>& roles); + + BSONArray rolesVectorToBSONArray(const std::vector<RoleName>& roles); + + Status privilegeVectorToBSONArray(const PrivilegeVector& privileges, BSONArray* result); + + /** + * Used to get all current roles of the user identified by 'userName'. + */ + Status getCurrentUserRoles(OperationContext* txn, + AuthorizationManager* authzManager, + const UserName& userName, + unordered_set<RoleName>* roles); + + /** + * Checks that every role in "rolesToAdd" exists, that adding each of those roles to "role" + * will not result in a cycle to the role graph, and that every role being added comes from the + * same database as the role it is being added to (or that the role being added to is from the + * "admin" database. + */ + Status checkOkayToGrantRolesToRole(const RoleName& role, + const std::vector<RoleName> rolesToAdd, + AuthorizationManager* authzManager); + + /** + * Checks that every privilege being granted targets just the database the role is from, or that + * the role is from the "admin" db. + */ + Status checkOkayToGrantPrivilegesToRole(const RoleName& role, + const PrivilegeVector& privileges); + + /** + * Returns Status::OK() if the current Auth schema version is at least the auth schema version + * for the MongoDB 2.6 and 3.0 MongoDB-CR/SCRAM mixed auth mode. + * Returns an error otherwise. + */ + Status requireAuthSchemaVersion26Final(OperationContext* txn, + AuthorizationManager* authzManager); + + /** + * Returns Status::OK() if the current Auth schema version is at least the auth schema version + * for MongoDB 2.6 during the upgrade process. + * Returns an error otherwise. + */ + Status requireAuthSchemaVersion26UpgradeOrFinal(OperationContext* txn, + AuthorizationManager* authzManager); + + void appendBSONObjToBSONArrayBuilder(BSONArrayBuilder* array, const BSONObj& obj); + + // + // checkAuthorizedTo* methods + // + + Status checkAuthorizedToGrantRoles(AuthorizationSession* authzSession, + const std::vector<RoleName>& roles); + + Status checkAuthorizedToGrantPrivileges(AuthorizationSession* authzSession, + const PrivilegeVector& privileges); + + Status checkAuthorizedToRevokeRoles(AuthorizationSession* authzSession, + const std::vector<RoleName>& roles); + + Status checkAuthorizedToRevokePrivileges(AuthorizationSession* authzSession, + const PrivilegeVector& privileges); + + // + // checkAuthFor*Command methods + // + + Status checkAuthForCreateUserCommand(ClientBasic* client, + const std::string& dbname, + const BSONObj& cmdObj); + + Status checkAuthForUpdateUserCommand(ClientBasic* client, + const std::string& dbname, + const BSONObj& cmdObj); + + Status checkAuthForGrantRolesToUserCommand(ClientBasic* client, + const std::string& dbname, + const BSONObj& cmdObj); + + Status checkAuthForCreateRoleCommand(ClientBasic* client, + const std::string& dbname, + const BSONObj& cmdObj); + + Status checkAuthForUpdateRoleCommand(ClientBasic* client, + const std::string& dbname, + const BSONObj& cmdObj); + + Status checkAuthForGrantRolesToRoleCommand(ClientBasic* client, + const std::string& dbname, + const BSONObj& cmdObj); + + Status checkAuthForGrantPrivilegesToRoleCommand(ClientBasic* client, + const std::string& dbname, + const BSONObj& cmdObj); + + Status checkAuthForDropAllUsersFromDatabaseCommand(ClientBasic* client, + const std::string& dbname); + + Status checkAuthForRevokeRolesFromUserCommand(ClientBasic* client, + const std::string& dbname, + const BSONObj& cmdObj); + + Status checkAuthForRevokeRolesFromRoleCommand(ClientBasic* client, + const std::string& dbname, + const BSONObj& cmdObj); + + Status checkAuthForDropUserCommand(ClientBasic* client, + const std::string& dbname, + const BSONObj& cmdObj); + + Status checkAuthForDropRoleCommand(ClientBasic* client, + const std::string& dbname, + const BSONObj& cmdObj); + + + Status checkAuthForUsersInfoCommand(ClientBasic* client, + const std::string& dbname, + const BSONObj& cmdObj); + + Status checkAuthForRevokePrivilegesFromRoleCommand(ClientBasic* client, + const std::string& dbname, + const BSONObj& cmdObj); + + Status checkAuthForDropAllRolesFromDatabaseCommand(ClientBasic* client, + const std::string& dbname); + + Status checkAuthForRolesInfoCommand(ClientBasic* client, + const std::string& dbname, + const BSONObj& cmdObj); + + Status checkAuthForInvalidateUserCacheCommand(ClientBasic* client); + + Status checkAuthForGetUserCacheGenerationCommand(ClientBasic* client); + + Status checkAuthForMergeAuthzCollectionsCommand(ClientBasic* client, + const BSONObj& cmdObj); + + +} // namespace auth +} // namespace mongo diff --git a/src/mongo/db/commands/user_management_commands_common.cpp b/src/mongo/db/commands/user_management_commands_common.cpp new file mode 100644 index 00000000000..cfe634f142a --- /dev/null +++ b/src/mongo/db/commands/user_management_commands_common.cpp @@ -0,0 +1,749 @@ +/** + * Copyright (C) 2015 MongoDB Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the GNU Affero General Public License in all respects for + * all of the code used other than as permitted herein. If you modify file(s) + * with this exception, you may extend this exception to your version of the + * file(s), but you are not obligated to do so. If you do not wish to do so, + * delete this exception statement from your version. If you delete this + * exception statement from all source files in the program, then also delete + * it in the license file. + */ + +#define MONGO_LOG_DEFAULT_COMPONENT ::mongo::logger::LogComponent::kAccessControl + +#include "mongo/platform/basic.h" + +#include "mongo/db/commands/user_management_commands.h" + +#include <string> +#include <vector> + +#include "mongo/base/status.h" +#include "mongo/bson/mutable/algorithm.h" +#include "mongo/config.h" +#include "mongo/db/auth/action_set.h" +#include "mongo/db/auth/action_type.h" +#include "mongo/db/auth/authorization_manager.h" +#include "mongo/db/auth/authorization_session.h" +#include "mongo/db/auth/resource_pattern.h" +#include "mongo/db/auth/user.h" +#include "mongo/db/auth/user_management_commands_parser.h" +#include "mongo/db/jsobj.h" +#include "mongo/util/log.h" +#include "mongo/util/mongoutils/str.h" +#include "mongo/util/sequence_util.h" + +namespace mongo { +namespace auth { + + void redactPasswordData(mutablebson::Element parent) { + namespace mmb = mutablebson; + const StringData pwdFieldName("pwd", StringData::LiteralTag()); + for (mmb::Element pwdElement = mmb::findFirstChildNamed(parent, pwdFieldName); + pwdElement.ok(); + pwdElement = mmb::findElementNamed(pwdElement.rightSibling(), pwdFieldName)) { + + pwdElement.setValueString("xxx"); + } + } + + BSONArray roleSetToBSONArray(const unordered_set<RoleName>& roles) { + BSONArrayBuilder rolesArrayBuilder; + for (unordered_set<RoleName>::const_iterator it = roles.begin(); it != roles.end(); ++it) { + const RoleName& role = *it; + rolesArrayBuilder.append( + BSON(AuthorizationManager::ROLE_NAME_FIELD_NAME << role.getRole() << + AuthorizationManager::ROLE_DB_FIELD_NAME << role.getDB())); + } + return rolesArrayBuilder.arr(); + } + + BSONArray rolesVectorToBSONArray(const std::vector<RoleName>& roles) { + BSONArrayBuilder rolesArrayBuilder; + for (std::vector<RoleName>::const_iterator it = roles.begin(); it != roles.end(); ++it) { + const RoleName& role = *it; + rolesArrayBuilder.append( + BSON(AuthorizationManager::ROLE_NAME_FIELD_NAME << role.getRole() << + AuthorizationManager::ROLE_DB_FIELD_NAME << role.getDB())); + } + return rolesArrayBuilder.arr(); + } + + 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(); + } + + Status getCurrentUserRoles(OperationContext* txn, + AuthorizationManager* authzManager, + const UserName& userName, + unordered_set<RoleName>* roles) { + User* user; + authzManager->invalidateUserByName(userName); // Need to make sure cache entry is up to date + Status status = authzManager->acquireUser(txn, userName, &user); + if (!status.isOK()) { + return status; + } + RoleNameIterator rolesIt = user->getRoles(); + while (rolesIt.more()) { + roles->insert(rolesIt.next()); + } + authzManager->releaseUser(user); + return Status::OK(); + } + + Status checkOkayToGrantRolesToRole(const RoleName& role, + const std::vector<RoleName> rolesToAdd, + AuthorizationManager* authzManager) { + for (std::vector<RoleName>::const_iterator it = rolesToAdd.begin(); + it != rolesToAdd.end(); ++it) { + const RoleName& roleToAdd = *it; + if (roleToAdd == role) { + return Status(ErrorCodes::InvalidRoleModification, + mongoutils::str::stream() << "Cannot grant role " << + role.getFullName() << " to itself."); + } + + if (role.getDB() != "admin" && roleToAdd.getDB() != role.getDB()) { + return Status(ErrorCodes::InvalidRoleModification, + str::stream() << "Roles on the \'" << role.getDB() << + "\' database cannot be granted roles from other databases"); + } + + BSONObj roleToAddDoc; + Status status = authzManager->getRoleDescription(roleToAdd, false, &roleToAddDoc); + if (status == ErrorCodes::RoleNotFound) { + return Status(ErrorCodes::RoleNotFound, + "Cannot grant nonexistent role " + roleToAdd.toString()); + } + if (!status.isOK()) { + return status; + } + std::vector<RoleName> indirectRoles; + status = auth::parseRoleNamesFromBSONArray( + BSONArray(roleToAddDoc["inheritedRoles"].Obj()), + role.getDB(), + &indirectRoles); + if (!status.isOK()) { + return status; + } + + if (sequenceContains(indirectRoles, role)) { + return Status(ErrorCodes::InvalidRoleModification, + mongoutils::str::stream() << "Granting " << + roleToAdd.getFullName() << " to " << role.getFullName() + << " would introduce a cycle in the role graph."); + } + } + return Status::OK(); + } + + Status checkOkayToGrantPrivilegesToRole(const RoleName& role, + const PrivilegeVector& privileges) { + if (role.getDB() == "admin") { + return Status::OK(); + } + + for (PrivilegeVector::const_iterator it = privileges.begin(); + it != privileges.end(); ++it) { + const ResourcePattern& resource = (*it).getResourcePattern(); + if ((resource.isDatabasePattern() || resource.isExactNamespacePattern()) && + (resource.databaseToMatch() == role.getDB())) { + continue; + } + + return Status(ErrorCodes::InvalidRoleModification, + str::stream() << "Roles on the \'" << role.getDB() << + "\' database cannot be granted privileges that target other " + "databases or the cluster"); + } + + return Status::OK(); + } + + Status requireAuthSchemaVersion26Final(OperationContext* txn, + AuthorizationManager* authzManager) { + int foundSchemaVersion; + Status status = authzManager->getAuthorizationVersion(txn, &foundSchemaVersion); + if (!status.isOK()) { + return status; + } + + if (foundSchemaVersion < AuthorizationManager::schemaVersion26Final) { + return Status( + ErrorCodes::AuthSchemaIncompatible, + str::stream() << "User and role management commands require auth data to have " + "at least schema version " << AuthorizationManager::schemaVersion26Final << + " but found " << foundSchemaVersion); + } + return authzManager->writeAuthSchemaVersionIfNeeded(txn, foundSchemaVersion); + } + + Status requireAuthSchemaVersion26UpgradeOrFinal(OperationContext* txn, + AuthorizationManager* authzManager) { + int foundSchemaVersion; + Status status = authzManager->getAuthorizationVersion(txn, &foundSchemaVersion); + if (!status.isOK()) { + return status; + } + + if (foundSchemaVersion < AuthorizationManager::schemaVersion26Upgrade) { + return Status( + ErrorCodes::AuthSchemaIncompatible, + str::stream() << "The usersInfo and rolesInfo commands require auth data to " + "have at least schema version " << + AuthorizationManager::schemaVersion26Upgrade << + " but found " << foundSchemaVersion); + } + return Status::OK(); + } + + void appendBSONObjToBSONArrayBuilder(BSONArrayBuilder* array, const BSONObj& obj) { + array->append(obj); + } + + Status checkAuthorizedToGrantRoles(AuthorizationSession* authzSession, + const std::vector<RoleName>& roles) { + for (size_t i = 0; i < roles.size(); ++i) { + if (!authzSession->isAuthorizedToGrantRole(roles[i])) { + return Status(ErrorCodes::Unauthorized, + str::stream() << "Not authorized to grant role: " + << roles[i].getFullName()); + } + } + + return Status::OK(); + } + + Status checkAuthorizedToGrantPrivileges(AuthorizationSession* authzSession, + const PrivilegeVector& privileges) { + for (PrivilegeVector::const_iterator it = privileges.begin(); + it != privileges.end(); ++it) { + Status status = authzSession->checkAuthorizedToGrantPrivilege(*it); + if (!status.isOK()) { + return status; + } + } + + return Status::OK(); + } + + Status checkAuthorizedToRevokeRoles(AuthorizationSession* authzSession, + const std::vector<RoleName>& roles) { + for (size_t i = 0; i < roles.size(); ++i) { + if (!authzSession->isAuthorizedToRevokeRole(roles[i])) { + return Status(ErrorCodes::Unauthorized, + str::stream() << "Not authorized to revoke role: " << + roles[i].getFullName()); + } + } + return Status::OK(); + } + + Status checkAuthorizedToRevokePrivileges(AuthorizationSession* authzSession, + const PrivilegeVector& privileges) { + for (PrivilegeVector::const_iterator it = privileges.begin(); + it != privileges.end(); ++it) { + Status status = authzSession->checkAuthorizedToRevokePrivilege(*it); + if (!status.isOK()) { + return status; + } + } + + return Status::OK(); + } + + Status checkAuthForCreateUserCommand(ClientBasic* client, + const std::string& dbname, + const BSONObj& cmdObj) { + AuthorizationSession* authzSession = AuthorizationSession::get(client); + auth::CreateOrUpdateUserArgs args; + Status status = auth::parseCreateOrUpdateUserCommands(cmdObj, + "createUser", + dbname, + &args); + if (!status.isOK()) { + return status; + } + + if (!authzSession->isAuthorizedForActionsOnResource( + ResourcePattern::forDatabaseName(args.userName.getDB()), + ActionType::createUser)) { + return Status(ErrorCodes::Unauthorized, + str::stream() << "Not authorized to create users on db: " + << args.userName.getDB()); + } + + return checkAuthorizedToGrantRoles(authzSession, args.roles); + } + + Status checkAuthForUpdateUserCommand(ClientBasic* client, + const std::string& dbname, + const BSONObj& cmdObj) { + AuthorizationSession* authzSession = AuthorizationSession::get(client); + auth::CreateOrUpdateUserArgs args; + Status status = auth::parseCreateOrUpdateUserCommands(cmdObj, + "updateUser", + dbname, + &args); + if (!status.isOK()) { + return status; + } + + if (args.hasHashedPassword) { + if (!authzSession->isAuthorizedToChangeOwnPasswordAsUser(args.userName) && + !authzSession->isAuthorizedForActionsOnResource( + ResourcePattern::forDatabaseName(args.userName.getDB()), + ActionType::changePassword)) { + return Status(ErrorCodes::Unauthorized, + str::stream() << "Not authorized to change password of user: " + << args.userName.getFullName()); + } + } + + if (args.hasCustomData) { + if (!authzSession->isAuthorizedToChangeOwnCustomDataAsUser(args.userName) && + !authzSession->isAuthorizedForActionsOnResource( + ResourcePattern::forDatabaseName(args.userName.getDB()), + ActionType::changeCustomData)) { + return Status(ErrorCodes::Unauthorized, + str::stream() << "Not authorized to change customData of user: " + << args.userName.getFullName()); + } + } + + if (args.hasRoles) { + // You don't know what roles you might be revoking, so require the ability to + // revoke any role in the system. + if (!authzSession->isAuthorizedForActionsOnResource( + ResourcePattern::forAnyNormalResource(), ActionType::revokeRole)) { + return Status(ErrorCodes::Unauthorized, + "In order to use updateUser to set roles array, must be " + "authorized to revoke any role in the system"); + } + + return checkAuthorizedToGrantRoles(authzSession, args.roles); + } + + return Status::OK(); + } + + Status checkAuthForGrantRolesToUserCommand(ClientBasic* client, + const std::string& dbname, + const BSONObj& cmdObj) { + AuthorizationSession* authzSession = AuthorizationSession::get(client); + std::vector<RoleName> roles; + std::string unusedUserNameString; + BSONObj unusedWriteConcern; + Status status = auth::parseRolePossessionManipulationCommands(cmdObj, + "grantRolesToUser", + dbname, + &unusedUserNameString, + &roles, + &unusedWriteConcern); + if (!status.isOK()) { + return status; + } + + return checkAuthorizedToGrantRoles(authzSession, roles); + } + + Status checkAuthForCreateRoleCommand(ClientBasic* client, + const std::string& dbname, + const BSONObj& cmdObj) { + AuthorizationSession* authzSession = AuthorizationSession::get(client); + auth::CreateOrUpdateRoleArgs args; + Status status = auth::parseCreateOrUpdateRoleCommands(cmdObj, + "createRole", + dbname, + &args); + if (!status.isOK()) { + return status; + } + + if (!authzSession->isAuthorizedForActionsOnResource( + ResourcePattern::forDatabaseName(args.roleName.getDB()), + ActionType::createRole)) { + return Status(ErrorCodes::Unauthorized, + str::stream() << "Not authorized to create roles on db: " + << args.roleName.getDB()); + } + + status = checkAuthorizedToGrantRoles(authzSession, args.roles); + if (!status.isOK()) { + return status; + } + + return checkAuthorizedToGrantPrivileges(authzSession, args.privileges); + } + + Status checkAuthForUpdateRoleCommand(ClientBasic* client, + const std::string& dbname, + const BSONObj& cmdObj) { + AuthorizationSession* authzSession = AuthorizationSession::get(client); + auth::CreateOrUpdateRoleArgs args; + Status status = auth::parseCreateOrUpdateRoleCommands(cmdObj, + "updateRole", + dbname, + &args); + if (!status.isOK()) { + return status; + } + + // You don't know what roles or privileges you might be revoking, so require the ability + // to revoke any role (or privilege) in the system. + if (!authzSession->isAuthorizedForActionsOnResource( + ResourcePattern::forAnyNormalResource(), ActionType::revokeRole)) { + return Status(ErrorCodes::Unauthorized, + "updateRole command required the ability to revoke any role in the " + "system"); + } + + status = checkAuthorizedToGrantRoles(authzSession, args.roles); + if (!status.isOK()) { + return status; + } + + return checkAuthorizedToGrantPrivileges(authzSession, args.privileges); + } + + Status checkAuthForGrantRolesToRoleCommand(ClientBasic* client, + const std::string& dbname, + const BSONObj& cmdObj) { + AuthorizationSession* authzSession = AuthorizationSession::get(client); + std::vector<RoleName> roles; + std::string unusedUserNameString; + BSONObj unusedWriteConcern; + Status status = auth::parseRolePossessionManipulationCommands(cmdObj, + "grantRolesToRole", + dbname, + &unusedUserNameString, + &roles, + &unusedWriteConcern); + if (!status.isOK()) { + return status; + } + + return checkAuthorizedToGrantRoles(authzSession, roles); + } + + Status checkAuthForGrantPrivilegesToRoleCommand(ClientBasic* client, + const std::string& dbname, + const BSONObj& cmdObj) { + AuthorizationSession* authzSession = AuthorizationSession::get(client); + PrivilegeVector privileges; + RoleName unusedRoleName; + BSONObj unusedWriteConcern; + Status status = + auth::parseAndValidateRolePrivilegeManipulationCommands(cmdObj, + "grantPrivilegesToRole", + dbname, + &unusedRoleName, + &privileges, + &unusedWriteConcern); + if (!status.isOK()) { + return status; + } + + return checkAuthorizedToGrantPrivileges(authzSession, privileges); + } + + Status checkAuthForDropUserCommand(ClientBasic* client, + const std::string& dbname, + const BSONObj& cmdObj) { + AuthorizationSession* authzSession = AuthorizationSession::get(client); + UserName userName; + BSONObj unusedWriteConcern; + Status status = auth::parseAndValidateDropUserCommand(cmdObj, + dbname, + &userName, + &unusedWriteConcern); + if (!status.isOK()) { + return status; + } + + if (!authzSession->isAuthorizedForActionsOnResource( + ResourcePattern::forDatabaseName(userName.getDB()), ActionType::dropUser)) { + return Status(ErrorCodes::Unauthorized, + str::stream() << "Not authorized to drop users from the " + << userName.getDB() << " database"); + } + return Status::OK(); + } + + Status checkAuthForDropRoleCommand(ClientBasic* client, + const std::string& dbname, + const BSONObj& cmdObj) { + AuthorizationSession* authzSession = AuthorizationSession::get(client); + RoleName roleName; + BSONObj unusedWriteConcern; + Status status = auth::parseDropRoleCommand(cmdObj, + dbname, + &roleName, + &unusedWriteConcern); + if (!status.isOK()) { + return status; + } + + if (!authzSession->isAuthorizedForActionsOnResource( + ResourcePattern::forDatabaseName(roleName.getDB()), ActionType::dropRole)) { + return Status(ErrorCodes::Unauthorized, + str::stream() << "Not authorized to drop roles from the " + << roleName.getDB() << " database"); + } + return Status::OK(); + } + + Status checkAuthForDropAllUsersFromDatabaseCommand(ClientBasic* client, + const std::string& dbname) { + AuthorizationSession* authzSession = AuthorizationSession::get(client); + if (!authzSession->isAuthorizedForActionsOnResource( + ResourcePattern::forDatabaseName(dbname), ActionType::dropUser)) { + return Status(ErrorCodes::Unauthorized, + str::stream() << "Not authorized to drop users from the " + << dbname << " database"); + } + return Status::OK(); + } + + Status checkAuthForRevokeRolesFromUserCommand(ClientBasic* client, + const std::string& dbname, + const BSONObj& cmdObj) { + AuthorizationSession* authzSession = AuthorizationSession::get(client); + std::vector<RoleName> roles; + std::string unusedUserNameString; + BSONObj unusedWriteConcern; + Status status = auth::parseRolePossessionManipulationCommands(cmdObj, + "revokeRolesFromUser", + dbname, + &unusedUserNameString, + &roles, + &unusedWriteConcern); + if (!status.isOK()) { + return status; + } + + return checkAuthorizedToRevokeRoles(authzSession, roles); + } + + Status checkAuthForRevokeRolesFromRoleCommand(ClientBasic* client, + const std::string& dbname, + const BSONObj& cmdObj) { + AuthorizationSession* authzSession = AuthorizationSession::get(client); + std::vector<RoleName> roles; + std::string unusedUserNameString; + BSONObj unusedWriteConcern; + Status status = auth::parseRolePossessionManipulationCommands(cmdObj, + "revokeRolesFromRole", + dbname, + &unusedUserNameString, + &roles, + &unusedWriteConcern); + if (!status.isOK()) { + return status; + } + + return checkAuthorizedToRevokeRoles(authzSession, roles); + } + + Status checkAuthForUsersInfoCommand(ClientBasic* client, + const std::string& dbname, + const BSONObj& cmdObj) { + AuthorizationSession* authzSession = AuthorizationSession::get(client); + auth::UsersInfoArgs args; + Status status = auth::parseUsersInfoCommand(cmdObj, dbname, &args); + if (!status.isOK()) { + return status; + } + + if (args.allForDB) { + if (!authzSession->isAuthorizedForActionsOnResource( + ResourcePattern::forDatabaseName(dbname), ActionType::viewUser)) { + return Status(ErrorCodes::Unauthorized, + str::stream() << "Not authorized to view users from the " + << dbname << " database"); + } + } else { + for (size_t i = 0; i < args.userNames.size(); ++i) { + if (authzSession->lookupUser(args.userNames[i])) { + continue; // Can always view users you are logged in as + } + if (!authzSession->isAuthorizedForActionsOnResource( + ResourcePattern::forDatabaseName(args.userNames[i].getDB()), + ActionType::viewUser)) { + return Status(ErrorCodes::Unauthorized, + str::stream() << "Not authorized to view users from the " + << dbname << " database"); + } + } + } + return Status::OK(); + } + + Status checkAuthForRevokePrivilegesFromRoleCommand(ClientBasic* client, + const std::string& dbname, + const BSONObj& cmdObj) { + AuthorizationSession* authzSession = AuthorizationSession::get(client); + PrivilegeVector privileges; + RoleName unusedRoleName; + BSONObj unusedWriteConcern; + Status status = + auth::parseAndValidateRolePrivilegeManipulationCommands(cmdObj, + "revokePrivilegesFromRole", + dbname, + &unusedRoleName, + &privileges, + &unusedWriteConcern); + if (!status.isOK()) { + return status; + } + + return checkAuthorizedToRevokePrivileges(authzSession, privileges); + } + + Status checkAuthForDropAllRolesFromDatabaseCommand(ClientBasic* client, + const std::string& dbname) { + AuthorizationSession* authzSession = AuthorizationSession::get(client); + if (!authzSession->isAuthorizedForActionsOnResource( + ResourcePattern::forDatabaseName(dbname), ActionType::dropRole)) { + return Status(ErrorCodes::Unauthorized, + str::stream() << "Not authorized to drop roles from the " + << dbname << " database"); + } + return Status::OK(); + } + + Status checkAuthForRolesInfoCommand(ClientBasic* client, + const std::string& dbname, + const BSONObj& cmdObj) { + AuthorizationSession* authzSession = AuthorizationSession::get(client); + auth::RolesInfoArgs args; + Status status = auth::parseRolesInfoCommand(cmdObj, dbname, &args); + if (!status.isOK()) { + return status; + } + + if (args.allForDB) { + if (!authzSession->isAuthorizedForActionsOnResource( + ResourcePattern::forDatabaseName(dbname), ActionType::viewRole)) { + return Status(ErrorCodes::Unauthorized, + str::stream() << "Not authorized to view roles from the " + << dbname << " database"); + } + } else { + for (size_t i = 0; i < args.roleNames.size(); ++i) { + if (authzSession->isAuthenticatedAsUserWithRole(args.roleNames[i])) { + continue; // Can always see roles that you are a member of + } + + if (!authzSession->isAuthorizedForActionsOnResource( + ResourcePattern::forDatabaseName(args.roleNames[i].getDB()), + ActionType::viewRole)) { + return Status(ErrorCodes::Unauthorized, + str::stream() << "Not authorized to view roles from the " + << args.roleNames[i].getDB() << " database"); + } + } + } + + return Status::OK(); + } + + Status checkAuthForInvalidateUserCacheCommand(ClientBasic* client) { + AuthorizationSession* authzSession = AuthorizationSession::get(client); + if (!authzSession->isAuthorizedForActionsOnResource( + ResourcePattern::forClusterResource(), ActionType::invalidateUserCache)) { + return Status(ErrorCodes::Unauthorized, "Not authorized to invalidate user cache"); + } + return Status::OK(); + } + + Status checkAuthForGetUserCacheGenerationCommand(ClientBasic* client) { + AuthorizationSession* authzSession = AuthorizationSession::get(client); + if (!authzSession->isAuthorizedForActionsOnResource( + ResourcePattern::forClusterResource(), ActionType::internal)) { + return Status(ErrorCodes::Unauthorized, "Not authorized to get cache generation"); + } + return Status::OK(); + } + + Status checkAuthForMergeAuthzCollectionsCommand(ClientBasic* client, + const BSONObj& cmdObj) { + auth::MergeAuthzCollectionsArgs args; + Status status = auth::parseMergeAuthzCollectionsCommand(cmdObj, &args); + if (!status.isOK()) { + return status; + } + + AuthorizationSession* authzSession = AuthorizationSession::get(client); + ActionSet actions; + actions.addAction(ActionType::createUser); + actions.addAction(ActionType::createRole); + actions.addAction(ActionType::grantRole); + actions.addAction(ActionType::revokeRole); + if (args.drop) { + actions.addAction(ActionType::dropUser); + actions.addAction(ActionType::dropRole); + } + if (!authzSession->isAuthorizedForActionsOnResource( + ResourcePattern::forAnyNormalResource(), actions)) { + return Status(ErrorCodes::Unauthorized, + "Not authorized to update user/role data using _mergeAuthzCollections" + " command"); + } + if (!args.usersCollName.empty() && + !authzSession->isAuthorizedForActionsOnResource( + ResourcePattern::forExactNamespace(NamespaceString(args.usersCollName)), + ActionType::find)) { + return Status(ErrorCodes::Unauthorized, + str::stream() << "Not authorized to read " + << args.usersCollName); + } + if (!args.rolesCollName.empty() && + !authzSession->isAuthorizedForActionsOnResource( + ResourcePattern::forExactNamespace(NamespaceString(args.rolesCollName)), + ActionType::find)) { + return Status(ErrorCodes::Unauthorized, + str::stream() << "Not authorized to read " + << args.rolesCollName); + } + return Status::OK(); + } + + +} // namespace auth +} // namespace mongo diff --git a/src/mongo/s/catalog/catalog_manager.h b/src/mongo/s/catalog/catalog_manager.h index 8d9598a120d..bc5e1608dc9 100644 --- a/src/mongo/s/catalog/catalog_manager.h +++ b/src/mongo/s/catalog/catalog_manager.h @@ -41,6 +41,7 @@ namespace mongo { class BatchedCommandResponse; struct BSONArray; class BSONObj; + class BSONObjBuilder; class ChunkType; class CollectionType; class ConnectionString; @@ -251,6 +252,26 @@ namespace mongo { virtual bool doShardsExist() = 0; /** + * Runs a user management command on the config servers. + * @param commandName: name of command + * @param dbname: database for which the user management command is invoked + * @param cmdObj: command obj + * @param result: contains data returned from config servers + * Returns true on success. + */ + virtual bool runUserManagementWriteCommand(const std::string& commandName, + const std::string& dbname, + const BSONObj& cmdObj, + BSONObjBuilder* result) = 0; + + /** + * Runs a read-only user management command on a single config server. + */ + virtual bool runUserManagementReadCommand(const std::string& dbname, + const BSONObj& cmdObj, + BSONObjBuilder* result) = 0; + + /** * Applies oplog entries to the config servers. * Used by mergeChunk, splitChunk, and moveChunk commands. * diff --git a/src/mongo/s/catalog/legacy/catalog_manager_legacy.cpp b/src/mongo/s/catalog/legacy/catalog_manager_legacy.cpp index f4fb009e9df..88c1c57c21f 100644 --- a/src/mongo/s/catalog/legacy/catalog_manager_legacy.cpp +++ b/src/mongo/s/catalog/legacy/catalog_manager_legacy.cpp @@ -43,9 +43,11 @@ #include "mongo/client/replica_set_monitor.h" #include "mongo/db/audit.h" #include "mongo/db/client.h" +#include "mongo/db/commands.h" #include "mongo/db/operation_context.h" #include "mongo/db/server_options.h" #include "mongo/platform/atomic_word.h" +#include "mongo/s/bson_serializable.h" #include "mongo/s/catalog/legacy/cluster_client_internal.h" #include "mongo/s/catalog/legacy/config_coordinator.h" #include "mongo/s/catalog/type_actionlog.h" @@ -1213,6 +1215,107 @@ namespace { return _getShardCount() > 0; } + bool CatalogManagerLegacy::runUserManagementWriteCommand(const string& commandName, + const string& dbname, + const BSONObj& cmdObj, + BSONObjBuilder* result) { + DBClientMultiCommand dispatcher; + RawBSONSerializable requestCmdSerial(cmdObj); + for (const ConnectionString& configServer : _configServers) { + dispatcher.addCommand(configServer, dbname, requestCmdSerial); + } + + auto scopedDistLock = getDistLockManager()->lock("authorizationData", + commandName, + stdx::chrono::milliseconds(5000)); + if (!scopedDistLock.isOK()) { + return Command::appendCommandStatus(*result, scopedDistLock.getStatus()); + } + + dispatcher.sendAll(); + + BSONObj responseObj; + + Status prevStatus{Status::OK()}; + Status currStatus{Status::OK()}; + + BSONObjBuilder responses; + unsigned failedCount = 0; + bool sameError = true; + while (dispatcher.numPending() > 0) { + ConnectionString configServer; + RawBSONSerializable responseCmdSerial; + + Status dispatchStatus = dispatcher.recvAny(&configServer, + &responseCmdSerial); + + if (!dispatchStatus.isOK()) { + return Command::appendCommandStatus(*result, dispatchStatus); + } + + responseObj = responseCmdSerial.toBSON(); + responses.append(configServer.toString(), responseObj); + + currStatus = Command::getStatusFromCommandResult(responseObj); + if (!currStatus.isOK()) { + // same error <=> adjacent error statuses are the same + if (failedCount > 0 && prevStatus != currStatus) { + sameError = false; + } + failedCount++; + prevStatus = currStatus; + } + } + + if (failedCount == 0) { + result->appendElements(responseObj); + return true; + } + + // if the command succeeds on at least one config server and fails on at least one, + // manual intervention is required + if (failedCount < _configServers.size()) { + Status status(ErrorCodes::ManualInterventionRequired, + str::stream() << "Config write was not consistent - " + << "user management command failed on at least one " + << "config server but passed on at least one other. " + << "Manual intervention may be required. " + << "Config responses: " << responses.obj().toString()); + return Command::appendCommandStatus(*result, status); + } + + if (sameError) { + result->appendElements(responseObj); + return false; + } + + Status status(ErrorCodes::ManualInterventionRequired, + str::stream() << "Config write was not consistent - " + << "user management command produced inconsistent results. " + << "Manual intervention may be required. " + << "Config responses: " << responses.obj().toString()); + return Command::appendCommandStatus(*result, status); + } + + bool CatalogManagerLegacy::runUserManagementReadCommand(const string& dbname, + const BSONObj& cmdObj, + BSONObjBuilder* result) { + try { + // let SyncClusterConnection handle connecting to the first config server + // that is reachable and returns data + ScopedDbConnection conn(_configServerConnectionString, 30); + + BSONObj cmdResult; + const bool ok = conn->runCommand(dbname, cmdObj, cmdResult); + result->appendElements(cmdResult); + conn.done(); + return ok; + } + catch (const DBException& ex) { + return Command::appendCommandStatus(*result, ex.toStatus()); + } + } + Status CatalogManagerLegacy::applyChunkOpsDeprecated(const BSONArray& updateOps, const BSONArray& preCondition) { BSONObj cmd = BSON("applyOps" << updateOps << diff --git a/src/mongo/s/catalog/legacy/catalog_manager_legacy.h b/src/mongo/s/catalog/legacy/catalog_manager_legacy.h index ac9ee02ab94..9be92345517 100644 --- a/src/mongo/s/catalog/legacy/catalog_manager_legacy.h +++ b/src/mongo/s/catalog/legacy/catalog_manager_legacy.h @@ -108,6 +108,18 @@ namespace mongo { virtual bool doShardsExist(); + /** + * Grabs a distributed lock and runs the command on all config servers. + */ + virtual bool runUserManagementWriteCommand(const std::string& commandName, + const std::string& dbname, + const BSONObj& cmdObj, + BSONObjBuilder* result); + + virtual bool runUserManagementReadCommand(const std::string& dbname, + const BSONObj& cmdObj, + BSONObjBuilder* result); + virtual Status applyChunkOpsDeprecated(const BSONArray& updateOps, const BSONArray& preCondition); diff --git a/src/mongo/s/commands/SConscript b/src/mongo/s/commands/SConscript index 59ea19f71c1..a32160a676a 100644 --- a/src/mongo/s/commands/SConscript +++ b/src/mongo/s/commands/SConscript @@ -38,6 +38,7 @@ env.Library( 'cluster_shard_collection_cmd.cpp', 'cluster_shutdown_cmd.cpp', 'cluster_split_collection_cmd.cpp', + 'cluster_user_management_commands.cpp', 'cluster_whats_my_uri_cmd.cpp', 'cluster_write_cmd.cpp', 'commands_public.cpp', diff --git a/src/mongo/s/commands/cluster_user_management_commands.cpp b/src/mongo/s/commands/cluster_user_management_commands.cpp new file mode 100644 index 00000000000..2c781ac0e91 --- /dev/null +++ b/src/mongo/s/commands/cluster_user_management_commands.cpp @@ -0,0 +1,791 @@ +/** + * Copyright (C) 2015 MongoDB Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the GNU Affero General Public License in all respects for + * all of the code used other than as permitted herein. If you modify file(s) + * with this exception, you may extend this exception to your version of the + * file(s), but you are not obligated to do so. If you do not wish to do so, + * delete this exception statement from your version. If you delete this + * exception statement from all source files in the program, then also delete + * it in the license file. + */ + +#define MONGO_LOG_DEFAULT_COMPONENT ::mongo::logger::LogComponent::kAccessControl + +#include "mongo/platform/basic.h" + +#include "mongo/db/commands/user_management_commands.h" + +#include "mongo/base/status.h" +#include "mongo/bson/mutable/document.h" +#include "mongo/db/auth/authorization_manager.h" +#include "mongo/db/auth/authorization_manager_global.h" +#include "mongo/db/auth/user_management_commands_parser.h" +#include "mongo/client/dbclientinterface.h" +#include "mongo/config.h" +#include "mongo/db/commands.h" +#include "mongo/db/jsobj.h" +#include "mongo/s/catalog/catalog_manager.h" +#include "mongo/s/grid.h" + +namespace mongo { + + using std::string; + using std::stringstream; + using std::vector; + + class CmdCreateUser : public Command { + public: + + CmdCreateUser() : Command("createUser") {} + + virtual bool slaveOk() const { return false; } + + virtual bool isWriteCommandForConfigServer() const { return false; } + + virtual void help(stringstream& ss) const { + ss << "Adds a user to the system"; + } + + virtual Status checkAuthForCommand(ClientBasic* client, + const std::string& dbname, + const BSONObj& cmdObj) { + return auth::checkAuthForCreateUserCommand(client, dbname, cmdObj); + } + + bool run(OperationContext* txn, + const string& dbname, + BSONObj& cmdObj, + int options, + string& errmsg, + BSONObjBuilder& result) { + return grid.catalogManager()->runUserManagementWriteCommand(this->name, + dbname, + cmdObj, + &result); + } + + virtual void redactForLogging(mutablebson::Document* cmdObj) { + auth::redactPasswordData(cmdObj->root()); + } + + } cmdCreateUser; + + class CmdUpdateUser : public Command { + public: + + CmdUpdateUser() : Command("updateUser") {} + + virtual bool slaveOk() const { return false; } + + virtual bool isWriteCommandForConfigServer() const { return false; } + + virtual void help(stringstream& ss) const { + ss << "Used to update a user, for example to change its password"; + } + + virtual Status checkAuthForCommand(ClientBasic* client, + const std::string& dbname, + const BSONObj& cmdObj) { + return auth::checkAuthForUpdateUserCommand(client, dbname, cmdObj); + } + + bool run(OperationContext* txn, + const string& dbname, + BSONObj& cmdObj, + int options, + string& errmsg, + BSONObjBuilder& result) { + auth::CreateOrUpdateUserArgs args; + Status status = auth::parseCreateOrUpdateUserCommands(cmdObj, + this->name, + dbname, + &args); + if (!status.isOK()) { + return appendCommandStatus(result, status); + } + const bool ok = grid.catalogManager()->runUserManagementWriteCommand(this->name, + dbname, + cmdObj, + &result); + + AuthorizationManager* authzManager = getGlobalAuthorizationManager(); + invariant(authzManager); + authzManager->invalidateUserByName(args.userName); + + return ok; + } + + virtual void redactForLogging(mutablebson::Document* cmdObj) { + auth::redactPasswordData(cmdObj->root()); + } + + } cmdUpdateUser; + + class CmdDropUser : public Command { + public: + + CmdDropUser() : Command("dropUser") {} + + virtual bool slaveOk() const { return false; } + + virtual bool isWriteCommandForConfigServer() const { return false; } + + virtual void help(stringstream& ss) const { + ss << "Drops a single user."; + } + + virtual Status checkAuthForCommand(ClientBasic* client, + const std::string& dbname, + const BSONObj& cmdObj) { + return auth::checkAuthForDropUserCommand(client, dbname, cmdObj); + } + + bool run(OperationContext* txn, + const string& dbname, + BSONObj& cmdObj, + int options, + string& errmsg, + BSONObjBuilder& result) { + UserName userName; + BSONObj unusedWriteConcern; + Status status = auth::parseAndValidateDropUserCommand(cmdObj, + dbname, + &userName, + &unusedWriteConcern); + if (!status.isOK()) { + return appendCommandStatus(result, status); + } + const bool ok = grid.catalogManager()->runUserManagementWriteCommand(this->name, + dbname, + cmdObj, + &result); + + AuthorizationManager* authzManager = getGlobalAuthorizationManager(); + invariant(authzManager); + authzManager->invalidateUserByName(userName); + + return ok; + } + + } cmdDropUser; + + class CmdDropAllUsersFromDatabase : public Command { + public: + + CmdDropAllUsersFromDatabase() : Command("dropAllUsersFromDatabase") {} + + virtual bool slaveOk() const { return false; } + + virtual bool isWriteCommandForConfigServer() const { return false; } + + virtual void help(stringstream& ss) const { + ss << "Drops all users for a single database."; + } + + virtual Status checkAuthForCommand(ClientBasic* client, + const std::string& dbname, + const BSONObj& cmdObj) { + return auth::checkAuthForDropAllUsersFromDatabaseCommand(client, dbname); + } + + bool run(OperationContext* txn, + const string& dbname, + BSONObj& cmdObj, + int options, + string& errmsg, + BSONObjBuilder& result) { + const bool ok = grid.catalogManager()->runUserManagementWriteCommand(this->name, + dbname, + cmdObj, + &result); + + AuthorizationManager* authzManager = getGlobalAuthorizationManager(); + invariant(authzManager); + authzManager->invalidateUsersFromDB(dbname); + + return ok; + } + + } cmdDropAllUsersFromDatabase; + + class CmdGrantRolesToUser: public Command { + public: + + CmdGrantRolesToUser() : Command("grantRolesToUser") {} + + virtual bool slaveOk() const { return false; } + + virtual bool isWriteCommandForConfigServer() const { return false; } + + virtual void help(stringstream& ss) const { + ss << "Grants roles to a user."; + } + + virtual Status checkAuthForCommand(ClientBasic* client, + const std::string& dbname, + const BSONObj& cmdObj) { + return auth::checkAuthForGrantRolesToUserCommand(client, dbname, cmdObj); + } + + bool run(OperationContext* txn, + const string& dbname, + BSONObj& cmdObj, + int options, + string& errmsg, + BSONObjBuilder& result) { + string userNameString; + vector<RoleName> roles; + BSONObj unusedWriteConcern; + Status status = auth::parseRolePossessionManipulationCommands(cmdObj, + this->name, + dbname, + &userNameString, + &roles, + &unusedWriteConcern); + if (!status.isOK()) { + return appendCommandStatus(result, status); + } + const bool ok = grid.catalogManager()->runUserManagementWriteCommand(this->name, + dbname, + cmdObj, + &result); + + AuthorizationManager* authzManager = getGlobalAuthorizationManager(); + invariant(authzManager); + authzManager->invalidateUserByName(UserName(userNameString, dbname)); + + return ok; + } + + } cmdGrantRolesToUser; + + class CmdRevokeRolesFromUser: public Command { + public: + + CmdRevokeRolesFromUser() : Command("revokeRolesFromUser") {} + + virtual bool slaveOk() const { return false; } + + virtual bool isWriteCommandForConfigServer() const { return false; } + + virtual void help(stringstream& ss) const { + ss << "Revokes roles from a user."; + } + + virtual Status checkAuthForCommand(ClientBasic* client, + const std::string& dbname, + const BSONObj& cmdObj) { + return auth::checkAuthForRevokeRolesFromUserCommand(client, dbname, cmdObj); + } + + bool run(OperationContext* txn, + const string& dbname, + BSONObj& cmdObj, + int options, + string& errmsg, + BSONObjBuilder& result) { + string userNameString; + vector<RoleName> unusedRoles; + BSONObj unusedWriteConcern; + Status status = auth::parseRolePossessionManipulationCommands(cmdObj, + this->name, + dbname, + &userNameString, + &unusedRoles, + &unusedWriteConcern); + if (!status.isOK()) { + return appendCommandStatus(result, status); + } + const bool ok = grid.catalogManager()->runUserManagementWriteCommand(this->name, + dbname, + cmdObj, + &result); + + AuthorizationManager* authzManager = getGlobalAuthorizationManager(); + invariant(authzManager); + authzManager->invalidateUserByName(UserName(userNameString, dbname)); + + return ok; + } + + } cmdRevokeRolesFromUser; + + class CmdUsersInfo: public Command { + public: + + virtual bool slaveOk() const { return false; } + + virtual bool slaveOverrideOk() const { return true; } + + virtual bool isWriteCommandForConfigServer() const { return false; } + + CmdUsersInfo() : Command("usersInfo") {} + + virtual void help(stringstream& ss) const { + ss << "Returns information about users."; + } + + virtual Status checkAuthForCommand(ClientBasic* client, + const std::string& dbname, + const BSONObj& cmdObj) { + return auth::checkAuthForUsersInfoCommand(client, dbname, cmdObj); + } + + bool run(OperationContext* txn, + const string& dbname, + BSONObj& cmdObj, + int options, + string& errmsg, + BSONObjBuilder& result) { + return grid.catalogManager()->runUserManagementReadCommand(dbname, + cmdObj, + &result); + } + + } cmdUsersInfo; + + class CmdCreateRole: public Command { + public: + + CmdCreateRole() : Command("createRole") {} + + virtual bool slaveOk() const { return false; } + + virtual bool isWriteCommandForConfigServer() const { return false; } + + virtual void help(stringstream& ss) const { + ss << "Adds a role to the system"; + } + + virtual Status checkAuthForCommand(ClientBasic* client, + const std::string& dbname, + const BSONObj& cmdObj) { + return auth::checkAuthForCreateRoleCommand(client, dbname, cmdObj); + } + + bool run(OperationContext* txn, + const string& dbname, + BSONObj& cmdObj, + int options, + string& errmsg, + BSONObjBuilder& result) { + return grid.catalogManager()->runUserManagementWriteCommand(this->name, + dbname, + cmdObj, + &result); + } + + } cmdCreateRole; + + class CmdUpdateRole: public Command { + public: + + CmdUpdateRole() : Command("updateRole") {} + + virtual bool slaveOk() const { return false; } + + virtual bool isWriteCommandForConfigServer() const { return false; } + + virtual void help(stringstream& ss) const { + ss << "Used to update a role"; + } + + virtual Status checkAuthForCommand(ClientBasic* client, + const std::string& dbname, + const BSONObj& cmdObj) { + return auth::checkAuthForUpdateRoleCommand(client, dbname, cmdObj); + } + + bool run(OperationContext* txn, + const string& dbname, + BSONObj& cmdObj, + int options, + string& errmsg, + BSONObjBuilder& result) { + const bool ok = grid.catalogManager()->runUserManagementWriteCommand(this->name, + dbname, + cmdObj, + &result); + + AuthorizationManager* authzManager = getGlobalAuthorizationManager(); + invariant(authzManager); + authzManager->invalidateUserCache(); + + return ok; + } + + } cmdUpdateRole; + + class CmdGrantPrivilegesToRole: public Command { + public: + + CmdGrantPrivilegesToRole() : Command("grantPrivilegesToRole") {} + + virtual bool slaveOk() const { return false; } + + virtual bool isWriteCommandForConfigServer() const { return false; } + + virtual void help(stringstream& ss) const { + ss << "Grants privileges to a role"; + } + + virtual Status checkAuthForCommand(ClientBasic* client, + const std::string& dbname, + const BSONObj& cmdObj) { + return auth::checkAuthForGrantPrivilegesToRoleCommand(client, dbname, cmdObj); + } + + bool run(OperationContext* txn, + const string& dbname, + BSONObj& cmdObj, + int options, + string& errmsg, + BSONObjBuilder& result) { + const bool ok = grid.catalogManager()->runUserManagementWriteCommand(this->name, + dbname, + cmdObj, + &result); + + AuthorizationManager* authzManager = getGlobalAuthorizationManager(); + invariant(authzManager); + authzManager->invalidateUserCache(); + + return ok; + } + + } cmdGrantPrivilegesToRole; + + class CmdRevokePrivilegesFromRole: public Command { + public: + + CmdRevokePrivilegesFromRole() : Command("revokePrivilegesFromRole") {} + + virtual bool slaveOk() const { return false; } + + virtual bool isWriteCommandForConfigServer() const { return false; } + + virtual void help(stringstream& ss) const { + ss << "Revokes privileges from a role"; + } + + virtual Status checkAuthForCommand(ClientBasic* client, + const std::string& dbname, + const BSONObj& cmdObj) { + return auth::checkAuthForRevokePrivilegesFromRoleCommand(client, dbname, cmdObj); + } + + bool run(OperationContext* txn, + const string& dbname, + BSONObj& cmdObj, + int options, + string& errmsg, + BSONObjBuilder& result) { + const bool ok = grid.catalogManager()->runUserManagementWriteCommand(this->name, + dbname, + cmdObj, + &result); + + AuthorizationManager* authzManager = getGlobalAuthorizationManager(); + invariant(authzManager); + authzManager->invalidateUserCache(); + + return ok; + } + + } cmdRevokePrivilegesFromRole; + + class CmdGrantRolesToRole: public Command { + public: + + CmdGrantRolesToRole() : Command("grantRolesToRole") {} + + virtual bool slaveOk() const { return false; } + + virtual bool isWriteCommandForConfigServer() const { return false; } + + virtual void help(stringstream& ss) const { + ss << "Grants roles to another role."; + } + + virtual Status checkAuthForCommand(ClientBasic* client, + const std::string& dbname, + const BSONObj& cmdObj) { + return auth::checkAuthForGrantRolesToRoleCommand(client, dbname, cmdObj); + } + + bool run(OperationContext* txn, + const string& dbname, + BSONObj& cmdObj, + int options, + string& errmsg, + BSONObjBuilder& result) { + const bool ok = grid.catalogManager()->runUserManagementWriteCommand(this->name, + dbname, + cmdObj, + &result); + + AuthorizationManager* authzManager = getGlobalAuthorizationManager(); + invariant(authzManager); + authzManager->invalidateUserCache(); + + return ok; + } + + } cmdGrantRolesToRole; + + class CmdRevokeRolesFromRole: public Command { + public: + + CmdRevokeRolesFromRole() : Command("revokeRolesFromRole") {} + + virtual bool slaveOk() const { return false; } + + virtual bool isWriteCommandForConfigServer() const { return false; } + + virtual void help(stringstream& ss) const { + ss << "Revokes roles from another role."; + } + + virtual Status checkAuthForCommand(ClientBasic* client, + const std::string& dbname, + const BSONObj& cmdObj) { + return auth::checkAuthForRevokeRolesFromRoleCommand(client, dbname, cmdObj); + } + + bool run(OperationContext* txn, + const string& dbname, + BSONObj& cmdObj, + int options, + string& errmsg, + BSONObjBuilder& result) { + const bool ok = grid.catalogManager()->runUserManagementWriteCommand(this->name, + dbname, + cmdObj, + &result); + + AuthorizationManager* authzManager = getGlobalAuthorizationManager(); + invariant(authzManager); + authzManager->invalidateUserCache(); + + return ok; + } + + } cmdRevokeRolesFromRole; + + class CmdDropRole: public Command { + public: + + CmdDropRole() : Command("dropRole") {} + + virtual bool slaveOk() const { return false; } + + virtual bool isWriteCommandForConfigServer() const { return false; } + + virtual void help(stringstream& ss) const { + ss << "Drops a single role. Before deleting the role completely it must remove it " + "from any users or roles that reference it. If any errors occur in the middle " + "of that process it's possible to be left in a state where the role has been " + "removed from some user/roles but otherwise still exists."; + } + + virtual Status checkAuthForCommand(ClientBasic* client, + const std::string& dbname, + const BSONObj& cmdObj) { + return auth::checkAuthForDropRoleCommand(client, dbname, cmdObj); + } + + bool run(OperationContext* txn, + const string& dbname, + BSONObj& cmdObj, + int options, + string& errmsg, + BSONObjBuilder& result) { + const bool ok = grid.catalogManager()->runUserManagementWriteCommand(this->name, + dbname, + cmdObj, + &result); + + AuthorizationManager* authzManager = getGlobalAuthorizationManager(); + invariant(authzManager); + authzManager->invalidateUserCache(); + + return ok; + } + + } cmdDropRole; + + class CmdDropAllRolesFromDatabase: public Command { + public: + + CmdDropAllRolesFromDatabase() : Command("dropAllRolesFromDatabase") {} + + virtual bool slaveOk() const { return false; } + + virtual bool isWriteCommandForConfigServer() const { return false; } + + virtual void help(stringstream& ss) const { + ss << "Drops all roles from the given database. Before deleting the roles completely " + "it must remove them from any users or other roles that reference them. If any " + "errors occur in the middle of that process it's possible to be left in a state " + "where the roles have been removed from some user/roles but otherwise still " + "exist."; + } + + virtual Status checkAuthForCommand(ClientBasic* client, + const std::string& dbname, + const BSONObj& cmdObj) { + return auth::checkAuthForDropAllRolesFromDatabaseCommand(client, dbname); + } + + bool run(OperationContext* txn, + const string& dbname, + BSONObj& cmdObj, + int options, + string& errmsg, + BSONObjBuilder& result) { + const bool ok = grid.catalogManager()->runUserManagementWriteCommand(this->name, + dbname, + cmdObj, + &result); + + AuthorizationManager* authzManager = getGlobalAuthorizationManager(); + invariant(authzManager); + authzManager->invalidateUserCache(); + + return ok; + } + + } cmdDropAllRolesFromDatabase; + + class CmdRolesInfo: public Command { + public: + + CmdRolesInfo() : Command("rolesInfo") {} + + virtual bool slaveOk() const { return false; } + + virtual bool slaveOverrideOk() const { return true; } + + virtual bool isWriteCommandForConfigServer() const { return false; } + + virtual void help(stringstream& ss) const { + ss << "Returns information about roles."; + } + + virtual Status checkAuthForCommand(ClientBasic* client, + const std::string& dbname, + const BSONObj& cmdObj) { + return auth::checkAuthForRolesInfoCommand(client, dbname, cmdObj); + } + + bool run(OperationContext* txn, + const string& dbname, + BSONObj& cmdObj, + int options, + string& errmsg, + BSONObjBuilder& result) { + return grid.catalogManager()->runUserManagementReadCommand(dbname, + cmdObj, + &result); + } + + } cmdRolesInfo; + + class CmdInvalidateUserCache: public Command { + public: + + CmdInvalidateUserCache() : Command("invalidateUserCache") {} + + virtual bool slaveOk() const { return true; } + + virtual bool adminOnly() const { return true; } + + virtual bool isWriteCommandForConfigServer() const { return false; } + + virtual void help(stringstream& ss) const { + ss << "Invalidates the in-memory cache of user information"; + } + + virtual Status checkAuthForCommand(ClientBasic* client, + const std::string& dbname, + const BSONObj& cmdObj) { + return auth::checkAuthForInvalidateUserCacheCommand(client); + } + + bool run(OperationContext* txn, + const string& dbname, + BSONObj& cmdObj, + int options, + string& errmsg, + BSONObjBuilder& result) { + AuthorizationManager* authzManager = getGlobalAuthorizationManager(); + invariant(authzManager); + authzManager->invalidateUserCache(); + return true; + } + + } cmdInvalidateUserCache; + + /** + * This command is used only by mongorestore to handle restoring users/roles. We do this so + * that mongorestore doesn't do direct inserts into the admin.system.users and + * admin.system.roles, which would bypass the authzUpdateLock and allow multiple concurrent + * modifications to users/roles. What mongorestore now does instead is it inserts all user/role + * definitions it wants to restore into temporary collections, then this command moves those + * user/role definitions into their proper place in admin.system.users and admin.system.roles. + * It either adds the users/roles to the existing ones or replaces the existing ones, depending + * on whether the "drop" argument is true or false. + */ + class CmdMergeAuthzCollections : public Command { + public: + + CmdMergeAuthzCollections() : Command("_mergeAuthzCollections") {} + + virtual bool slaveOk() const { return false; } + + virtual bool isWriteCommandForConfigServer() const { return false; } + + virtual bool adminOnly() const { return true; } + + virtual void help(stringstream& ss) const { + ss << "Internal command used by mongorestore for updating user/role data"; + } + + virtual Status checkAuthForCommand(ClientBasic* client, + const std::string& dbname, + const BSONObj& cmdObj) { + return auth::checkAuthForMergeAuthzCollectionsCommand(client, cmdObj); + } + + bool run(OperationContext* txn, + const string& dbname, + BSONObj& cmdObj, + int options, + string& errmsg, + BSONObjBuilder& result) { + return grid.catalogManager()->runUserManagementWriteCommand(this->name, + dbname, + cmdObj, + &result); + } + + } cmdMergeAuthzCollections; + +} // namespace mongo |