/** * Copyright (C) 2013 10gen Inc. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . * * 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 #include #include "mongo/base/status.h" #include "mongo/bson/mutable/algorithm.h" #include "mongo/bson/mutable/document.h" #include "mongo/bson/mutable/element.h" #include "mongo/bson/util/bson_extract.h" #include "mongo/client/dbclientinterface.h" #include "mongo/crypto/mechanism_scram.h" #include "mongo/db/audit.h" #include "mongo/db/auth/action_set.h" #include "mongo/db/auth/action_type.h" #include "mongo/db/auth/authz_documents_update_guard.h" #include "mongo/db/auth/authorization_manager.h" #include "mongo/db/auth/authorization_manager_global.h" #include "mongo/db/auth/authorization_session.h" #include "mongo/db/auth/privilege.h" #include "mongo/db/auth/resource_pattern.h" #include "mongo/db/auth/user.h" #include "mongo/db/auth/user_document_parser.h" #include "mongo/db/auth/user_management_commands_parser.h" #include "mongo/db/commands.h" #include "mongo/db/jsobj.h" #include "mongo/platform/unordered_set.h" #include "mongo/stdx/functional.h" #include "mongo/util/log.h" #include "mongo/util/mongoutils/str.h" #include "mongo/util/net/ssl_manager.h" #include "mongo/util/sequence_util.h" namespace mongo { namespace str = mongoutils::str; 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& roles) { BSONArrayBuilder rolesArrayBuilder; for (unordered_set::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& roles) { BSONArrayBuilder rolesArrayBuilder; for (std::vector::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* 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& 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& 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 rolesToAdd, AuthorizationManager* authzManager) { for (vector::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 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: 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" << endl; } virtual Status checkAuthForCommand(ClientBasic* client, const std::string& dbname, const BSONObj& cmdObj) { AuthorizationSession* authzSession = client->getAuthorizationSession(); 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); } bool run(OperationContext* txn, const string& dbname, BSONObj& cmdObj, int options, string& errmsg, BSONObjBuilder& result, bool fromRepl) { auth::CreateOrUpdateUserArgs args; Status status = auth::parseCreateOrUpdateUserCommands(cmdObj, "createUser", dbname, &args); if (!status.isOK()) { return appendCommandStatus(result, status); } if (args.userName.getDB() == "local") { return appendCommandStatus( result, Status(ErrorCodes::BadValue, "Cannot create users in the local database")); } if (!args.hasHashedPassword && args.userName.getDB() != "$external") { return appendCommandStatus( result, Status(ErrorCodes::BadValue, "Must provide a 'pwd' field for all user documents, except those" " with '$external' as the user's source db")); } if ((args.hasHashedPassword) && args.userName.getDB() == "$external") { return appendCommandStatus( result, Status(ErrorCodes::BadValue, "Cannot set the password for users defined on the '$external' " "database")); } if (!args.hasRoles) { return appendCommandStatus( result, Status(ErrorCodes::BadValue, "\"createUser\" command requires a \"roles\" array")); } #ifdef MONGO_SSL if (args.userName.getDB() == "$external" && getSSLManager() && getSSLManager()->getSSLConfiguration() .serverSubjectName == args.userName.getUser()) { return appendCommandStatus( result, Status(ErrorCodes::BadValue, "Cannot create an x.509 user with the same " "subjectname as the server")); } #endif BSONObjBuilder userObjBuilder; userObjBuilder.append("_id", str::stream() << args.userName.getDB() << "." << args.userName.getUser()); userObjBuilder.append(AuthorizationManager::USER_NAME_FIELD_NAME, args.userName.getUser()); userObjBuilder.append(AuthorizationManager::USER_DB_FIELD_NAME, args.userName.getDB()); if (!args.hasHashedPassword) { // Must be an external user userObjBuilder.append("credentials", BSON("external" << true)); } else if (args.mechanism == "SCRAM-SHA-1" || args.mechanism == "MONGODB-CR" || args.mechanism == "CRAM-MD5" || args.mechanism.empty()) { // At the moment we are ignoring the mechanism parameter and create // both SCRAM-SHA-1 and MONGODB-CR credentials for all new users. BSONObjBuilder credentialsBuilder(userObjBuilder.subobjStart("credentials")); AuthorizationManager* authzManager = getGlobalAuthorizationManager(); int authzVersion; Status status = authzManager->getAuthorizationVersion(txn, &authzVersion); if (!status.isOK()) { return appendCommandStatus(result, status); } // Add SCRAM credentials for appropriate authSchemaVersions. if (authzVersion > AuthorizationManager::schemaVersion26Final) { BSONObj scramCred = scram::generateCredentials(args.hashedPassword); credentialsBuilder.append("SCRAM-SHA-1", scramCred); } else { // Otherwise default to MONGODB-CR. credentialsBuilder.append("MONGODB-CR", args.hashedPassword); } credentialsBuilder.done(); } else { return appendCommandStatus( result, Status(ErrorCodes::BadValue, "Unsupported password authentication mechanism " + args.mechanism)); } if (args.hasCustomData) { userObjBuilder.append("customData", args.customData); } userObjBuilder.append("roles", rolesVectorToBSONArray(args.roles)); BSONObj userObj = userObjBuilder.obj(); V2UserDocumentParser parser; status = parser.checkValidUserDocument(userObj); if (!status.isOK()) { return appendCommandStatus(result, status); } AuthorizationManager* authzManager = getGlobalAuthorizationManager(); AuthzDocumentsUpdateGuard updateGuard(authzManager); if (!updateGuard.tryLock("Create user")) { return appendCommandStatus( result, Status(ErrorCodes::LockBusy, "Could not lock auth data update lock.")); } status = requireAuthSchemaVersion26Final(txn, authzManager); if (!status.isOK()) { return appendCommandStatus(result, status); } // Role existence has to be checked after acquiring the update lock for (size_t i = 0; i < args.roles.size(); ++i) { BSONObj ignored; status = authzManager->getRoleDescription(args.roles[i], false, &ignored); if (!status.isOK()) { return appendCommandStatus(result, status); } } audit::logCreateUser(ClientBasic::getCurrent(), args.userName, args.hasHashedPassword, args.hasCustomData? &args.customData : NULL, args.roles); status = authzManager->insertPrivilegeDocument(txn, dbname, userObj, args.writeConcern); return appendCommandStatus(result, status); } virtual void redactForLogging(mutablebson::Document* cmdObj) { 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" << endl; } virtual Status checkAuthForCommand(ClientBasic* client, const std::string& dbname, const BSONObj& cmdObj) { AuthorizationSession* authzSession = client->getAuthorizationSession(); 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(); } bool run(OperationContext* txn, const string& dbname, BSONObj& cmdObj, int options, string& errmsg, BSONObjBuilder& result, bool fromRepl) { auth::CreateOrUpdateUserArgs args; Status status = auth::parseCreateOrUpdateUserCommands(cmdObj, "updateUser", dbname, &args); if (!status.isOK()) { return appendCommandStatus(result, status); } if (!args.hasHashedPassword && !args.hasCustomData && !args.hasRoles) { return appendCommandStatus( result, Status(ErrorCodes::BadValue, "Must specify at least one field to update in updateUser")); } if (args.hasHashedPassword && args.userName.getDB() == "$external") { return appendCommandStatus( result, Status(ErrorCodes::BadValue, "Cannot set the password for users defined on the '$external' " "database")); } BSONObjBuilder updateSetBuilder; if (args.hasHashedPassword) { BSONObjBuilder credentialsBuilder(updateSetBuilder.subobjStart("credentials")); AuthorizationManager* authzManager = getGlobalAuthorizationManager(); int authzVersion; Status status = authzManager->getAuthorizationVersion(txn, &authzVersion); if (!status.isOK()) { return appendCommandStatus(result, status); } // Add SCRAM credentials for appropriate authSchemaVersions if (authzVersion > AuthorizationManager::schemaVersion26Final) { BSONObj scramCred = scram::generateCredentials(args.hashedPassword); credentialsBuilder.append("SCRAM-SHA-1",scramCred); } else { // Otherwise default to MONGODB-CR credentialsBuilder.append("MONGODB-CR", args.hashedPassword); } credentialsBuilder.done(); } if (args.hasCustomData) { updateSetBuilder.append("customData", args.customData); } if (args.hasRoles) { updateSetBuilder.append("roles", rolesVectorToBSONArray(args.roles)); } AuthorizationManager* authzManager = getGlobalAuthorizationManager(); AuthzDocumentsUpdateGuard updateGuard(authzManager); if (!updateGuard.tryLock("Update user")) { return appendCommandStatus( result, Status(ErrorCodes::LockBusy, "Could not lock auth data update lock.")); } status = requireAuthSchemaVersion26Final(txn, authzManager); if (!status.isOK()) { return appendCommandStatus(result, status); } // Role existence has to be checked after acquiring the update lock if (args.hasRoles) { for (size_t i = 0; i < args.roles.size(); ++i) { BSONObj ignored; status = authzManager->getRoleDescription(args.roles[i], false, &ignored); if (!status.isOK()) { return appendCommandStatus(result, status); } } } audit::logUpdateUser(ClientBasic::getCurrent(), args.userName, args.hasHashedPassword, args.hasCustomData? &args.customData : NULL, args.hasRoles? &args.roles : NULL); status = authzManager->updatePrivilegeDocument(txn, args.userName, BSON("$set" << updateSetBuilder.done()), args.writeConcern); // Must invalidate even on bad status - what if the write succeeded but the GLE failed? authzManager->invalidateUserByName(args.userName); return appendCommandStatus(result, status); } virtual void redactForLogging(mutablebson::Document* cmdObj) { 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." << endl; } virtual Status checkAuthForCommand(ClientBasic* client, const std::string& dbname, const BSONObj& cmdObj) { AuthorizationSession* authzSession = client->getAuthorizationSession(); 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(); } bool run(OperationContext* txn, const string& dbname, BSONObj& cmdObj, int options, string& errmsg, BSONObjBuilder& result, bool fromRepl) { AuthorizationManager* authzManager = getGlobalAuthorizationManager(); AuthzDocumentsUpdateGuard updateGuard(authzManager); if (!updateGuard.tryLock("Drop user")) { return appendCommandStatus( result, Status(ErrorCodes::LockBusy, "Could not lock auth data update lock.")); } Status status = requireAuthSchemaVersion26Final(txn, authzManager); if (!status.isOK()) { return appendCommandStatus(result, status); } UserName userName; BSONObj writeConcern; status = auth::parseAndValidateDropUserCommand(cmdObj, dbname, &userName, &writeConcern); if (!status.isOK()) { return appendCommandStatus(result, status); } int nMatched; audit::logDropUser(ClientBasic::getCurrent(), userName); status = authzManager->removePrivilegeDocuments( txn, BSON(AuthorizationManager::USER_NAME_FIELD_NAME << userName.getUser() << AuthorizationManager::USER_DB_FIELD_NAME << userName.getDB()), writeConcern, &nMatched); // Must invalidate even on bad status - what if the write succeeded but the GLE failed? authzManager->invalidateUserByName(userName); if (!status.isOK()) { return appendCommandStatus(result, status); } if (nMatched == 0) { return appendCommandStatus( result, Status(ErrorCodes::UserNotFound, str::stream() << "User '" << userName.getFullName() << "' not found")); } return true; } } 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." << endl; } virtual Status checkAuthForCommand(ClientBasic* client, const std::string& dbname, const BSONObj& cmdObj) { AuthorizationSession* authzSession = client->getAuthorizationSession(); 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(); } bool run(OperationContext* txn, const string& dbname, BSONObj& cmdObj, int options, string& errmsg, BSONObjBuilder& result, bool fromRepl) { AuthorizationManager* authzManager = getGlobalAuthorizationManager(); AuthzDocumentsUpdateGuard updateGuard(authzManager); if (!updateGuard.tryLock("Drop all users from database")) { return appendCommandStatus( result, Status(ErrorCodes::LockBusy, "Could not lock auth data update lock.")); } Status status = requireAuthSchemaVersion26Final(txn, authzManager); if (!status.isOK()) { return appendCommandStatus(result, status); } BSONObj writeConcern; status = auth::parseAndValidateDropAllUsersFromDatabaseCommand(cmdObj, dbname, &writeConcern); if (!status.isOK()) { return appendCommandStatus(result, status); } int numRemoved; audit::logDropAllUsersFromDatabase(ClientBasic::getCurrent(), dbname); status = authzManager->removePrivilegeDocuments( txn, BSON(AuthorizationManager::USER_DB_FIELD_NAME << dbname), writeConcern, &numRemoved); // Must invalidate even on bad status - what if the write succeeded but the GLE failed? authzManager->invalidateUsersFromDB(dbname); if (!status.isOK()) { return appendCommandStatus(result, status); } result.append("n", numRemoved); return true; } } 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." << endl; } virtual Status checkAuthForCommand(ClientBasic* client, const std::string& dbname, const BSONObj& cmdObj) { AuthorizationSession* authzSession = client->getAuthorizationSession(); std::vector 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); } bool run(OperationContext* txn, const string& dbname, BSONObj& cmdObj, int options, string& errmsg, BSONObjBuilder& result, bool fromRepl) { AuthorizationManager* authzManager = getGlobalAuthorizationManager(); AuthzDocumentsUpdateGuard updateGuard(authzManager); if (!updateGuard.tryLock("Grant roles to user")) { return appendCommandStatus( result, Status(ErrorCodes::LockBusy, "Could not lock auth data update lock.")); } Status status = requireAuthSchemaVersion26Final(txn, authzManager); if (!status.isOK()) { return appendCommandStatus(result, status); } std::string userNameString; std::vector roles; BSONObj writeConcern; status = auth::parseRolePossessionManipulationCommands(cmdObj, "grantRolesToUser", dbname, &userNameString, &roles, &writeConcern); if (!status.isOK()) { return appendCommandStatus(result, status); } UserName userName(userNameString, dbname); unordered_set userRoles; status = getCurrentUserRoles(txn, authzManager, userName, &userRoles); if (!status.isOK()) { return appendCommandStatus(result, status); } for (vector::iterator it = roles.begin(); it != roles.end(); ++it) { RoleName& roleName = *it; BSONObj roleDoc; status = authzManager->getRoleDescription(roleName, false, &roleDoc); if (!status.isOK()) { return appendCommandStatus(result, status); } userRoles.insert(roleName); } audit::logGrantRolesToUser(ClientBasic::getCurrent(), userName, roles); BSONArray newRolesBSONArray = 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? authzManager->invalidateUserByName(userName); return appendCommandStatus(result, status); } } 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." << endl; } virtual Status checkAuthForCommand(ClientBasic* client, const std::string& dbname, const BSONObj& cmdObj) { AuthorizationSession* authzSession = client->getAuthorizationSession(); std::vector 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); } bool run(OperationContext* txn, const string& dbname, BSONObj& cmdObj, int options, string& errmsg, BSONObjBuilder& result, bool fromRepl) { AuthorizationManager* authzManager = getGlobalAuthorizationManager(); AuthzDocumentsUpdateGuard updateGuard(authzManager); if (!updateGuard.tryLock("Revoke roles from user")) { return appendCommandStatus( result, Status(ErrorCodes::LockBusy, "Could not lock auth data update lock.")); } Status status = requireAuthSchemaVersion26Final(txn, authzManager); if (!status.isOK()) { return appendCommandStatus(result, status); } std::string userNameString; std::vector roles; BSONObj writeConcern; status = auth::parseRolePossessionManipulationCommands(cmdObj, "revokeRolesFromUser", dbname, &userNameString, &roles, &writeConcern); if (!status.isOK()) { return appendCommandStatus(result, status); } UserName userName(userNameString, dbname); unordered_set userRoles; status = getCurrentUserRoles(txn, authzManager, userName, &userRoles); if (!status.isOK()) { return appendCommandStatus(result, status); } for (vector::iterator it = roles.begin(); it != roles.end(); ++it) { RoleName& roleName = *it; BSONObj roleDoc; status = authzManager->getRoleDescription(roleName, false, &roleDoc); if (!status.isOK()) { return appendCommandStatus(result, status); } userRoles.erase(roleName); } audit::logRevokeRolesFromUser(ClientBasic::getCurrent(), userName, roles); BSONArray newRolesBSONArray = 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? authzManager->invalidateUserByName(userName); return appendCommandStatus(result, status); } } 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." << endl; } virtual Status checkAuthForCommand(ClientBasic* client, const std::string& dbname, const BSONObj& cmdObj) { AuthorizationSession* authzSession = client->getAuthorizationSession(); 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(); } bool run(OperationContext* txn, const string& dbname, BSONObj& cmdObj, int options, string& errmsg, BSONObjBuilder& result, bool fromRepl) { auth::UsersInfoArgs args; Status status = auth::parseUsersInfoCommand(cmdObj, dbname, &args); if (!status.isOK()) { return appendCommandStatus(result, status); } status = requireAuthSchemaVersion26UpgradeOrFinal(txn, getGlobalAuthorizationManager()); if (!status.isOK()) { return appendCommandStatus(result, status); } if (args.allForDB && args.showPrivileges) { return appendCommandStatus( result, Status(ErrorCodes::IllegalOperation, "Can only get privilege details on exact-match usersInfo " "queries.")); } BSONArrayBuilder usersArrayBuilder; if (args.showPrivileges) { // If you want privileges you need to call getUserDescription on each user. for (size_t i = 0; i < args.userNames.size(); ++i) { BSONObj userDetails; status = getGlobalAuthorizationManager()->getUserDescription( txn, args.userNames[i], &userDetails); if (status.code() == ErrorCodes::UserNotFound) { continue; } if (!status.isOK()) { return appendCommandStatus(result, status); } if (!args.showCredentials) { // getUserDescription always includes credentials, need to strip it out BSONObjBuilder userWithoutCredentials(usersArrayBuilder.subobjStart()); for (BSONObjIterator it(userDetails); it.more(); ) { BSONElement e = it.next(); if (e.fieldNameStringData() != "credentials") userWithoutCredentials.append(e); } userWithoutCredentials.doneFast(); } else { usersArrayBuilder.append(userDetails); } } } else { // If you don't need privileges, you can just do a regular query on system.users BSONObjBuilder queryBuilder; if (args.allForDB) { queryBuilder.append(AuthorizationManager::USER_DB_FIELD_NAME, dbname); } else { BSONArrayBuilder usersMatchArray; for (size_t i = 0; i < args.userNames.size(); ++i) { usersMatchArray.append(BSON(AuthorizationManager::USER_NAME_FIELD_NAME << args.userNames[i].getUser() << AuthorizationManager::USER_DB_FIELD_NAME << args.userNames[i].getDB())); } queryBuilder.append("$or", usersMatchArray.arr()); } AuthorizationManager* authzManager = getGlobalAuthorizationManager(); BSONObjBuilder projection; if (!args.showCredentials) { projection.append("credentials", 0); } const stdx::function function = stdx::bind( appendBSONObjToBSONArrayBuilder, &usersArrayBuilder, stdx::placeholders::_1); authzManager->queryAuthzDocument(txn, AuthorizationManager::usersCollectionNamespace, queryBuilder.done(), projection.done(), function); } result.append("users", usersArrayBuilder.arr()); return true; } } 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" << endl; } virtual Status checkAuthForCommand(ClientBasic* client, const std::string& dbname, const BSONObj& cmdObj) { AuthorizationSession* authzSession = client->getAuthorizationSession(); 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); } bool run(OperationContext* txn, const string& dbname, BSONObj& cmdObj, int options, string& errmsg, BSONObjBuilder& result, bool fromRepl) { auth::CreateOrUpdateRoleArgs args; Status status = auth::parseCreateOrUpdateRoleCommands(cmdObj, "createRole", dbname, &args); if (!status.isOK()) { return appendCommandStatus(result, status); } if (args.roleName.getRole().empty()) { return appendCommandStatus( result, Status(ErrorCodes::BadValue, "Role name must be non-empty")); } if (args.roleName.getDB() == "local") { return appendCommandStatus( result, Status(ErrorCodes::BadValue, "Cannot create roles in the local database")); } if (args.roleName.getDB() == "$external") { return appendCommandStatus( result, Status(ErrorCodes::BadValue, "Cannot create roles in the $external database")); } if (!args.hasRoles) { return appendCommandStatus( result, Status(ErrorCodes::BadValue, "\"createRole\" command requires a \"roles\" array")); } if (!args.hasPrivileges) { return appendCommandStatus( result, Status(ErrorCodes::BadValue, "\"createRole\" command requires a \"privileges\" array")); } BSONObjBuilder roleObjBuilder; roleObjBuilder.append("_id", str::stream() << args.roleName.getDB() << "." << args.roleName.getRole()); roleObjBuilder.append(AuthorizationManager::ROLE_NAME_FIELD_NAME, args.roleName.getRole()); roleObjBuilder.append(AuthorizationManager::ROLE_DB_FIELD_NAME, args.roleName.getDB()); BSONArray privileges; status = privilegeVectorToBSONArray(args.privileges, &privileges); if (!status.isOK()) { return appendCommandStatus(result, status); } roleObjBuilder.append("privileges", privileges); roleObjBuilder.append("roles", rolesVectorToBSONArray(args.roles)); AuthorizationManager* authzManager = getGlobalAuthorizationManager(); AuthzDocumentsUpdateGuard updateGuard(authzManager); if (!updateGuard.tryLock("Create role")) { return appendCommandStatus( result, Status(ErrorCodes::LockBusy, "Could not lock auth data update lock.")); } status = 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); if (!status.isOK()) { return appendCommandStatus(result, status); } status = checkOkayToGrantPrivilegesToRole(args.roleName, args.privileges); if (!status.isOK()) { return appendCommandStatus(result, status); } audit::logCreateRole(ClientBasic::getCurrent(), args.roleName, args.roles, args.privileges); status = authzManager->insertRoleDocument(txn, roleObjBuilder.done(), args.writeConcern); return appendCommandStatus(result, status); } } 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" << endl; } virtual Status checkAuthForCommand(ClientBasic* client, const std::string& dbname, const BSONObj& cmdObj) { AuthorizationSession* authzSession = client->getAuthorizationSession(); 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); } bool run(OperationContext* txn, const string& dbname, BSONObj& cmdObj, int options, string& errmsg, BSONObjBuilder& result, bool fromRepl) { auth::CreateOrUpdateRoleArgs args; Status status = auth::parseCreateOrUpdateRoleCommands(cmdObj, "updateRole", dbname, &args); if (!status.isOK()) { return appendCommandStatus(result, status); } if (!args.hasPrivileges && !args.hasRoles) { return appendCommandStatus( result, Status(ErrorCodes::BadValue, "Must specify at least one field to update in updateRole")); } BSONObjBuilder updateSetBuilder; if (args.hasPrivileges) { BSONArray privileges; status = privilegeVectorToBSONArray(args.privileges, &privileges); if (!status.isOK()) { return appendCommandStatus(result, status); } updateSetBuilder.append("privileges", privileges); } if (args.hasRoles) { updateSetBuilder.append("roles", rolesVectorToBSONArray(args.roles)); } AuthorizationManager* authzManager = getGlobalAuthorizationManager(); AuthzDocumentsUpdateGuard updateGuard(authzManager); if (!updateGuard.tryLock("Update role")) { return appendCommandStatus( result, Status(ErrorCodes::LockBusy, "Could not lock auth data update lock.")); } status = requireAuthSchemaVersion26Final(txn, authzManager); if (!status.isOK()) { return appendCommandStatus(result, status); } // Role existence has to be checked after acquiring the update lock BSONObj ignored; status = authzManager->getRoleDescription(args.roleName, false, &ignored); if (!status.isOK()) { return appendCommandStatus(result, status); } if (args.hasRoles) { status = checkOkayToGrantRolesToRole(args.roleName, args.roles, authzManager); if (!status.isOK()) { return appendCommandStatus(result, status); } } if (args.hasPrivileges) { status = checkOkayToGrantPrivilegesToRole(args.roleName, args.privileges); if (!status.isOK()) { return appendCommandStatus(result, status); } } audit::logUpdateRole(ClientBasic::getCurrent(), args.roleName, args.hasRoles? &args.roles : NULL, args.hasPrivileges? &args.privileges : NULL); status = authzManager->updateRoleDocument(txn, args.roleName, BSON("$set" << updateSetBuilder.done()), args.writeConcern); // Must invalidate even on bad status - what if the write succeeded but the GLE failed? authzManager->invalidateUserCache(); return appendCommandStatus(result, status); } } 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" << endl; } virtual Status checkAuthForCommand(ClientBasic* client, const std::string& dbname, const BSONObj& cmdObj) { AuthorizationSession* authzSession = client->getAuthorizationSession(); 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); } bool run(OperationContext* txn, const string& dbname, BSONObj& cmdObj, int options, string& errmsg, BSONObjBuilder& result, bool fromRepl) { AuthorizationManager* authzManager = getGlobalAuthorizationManager(); AuthzDocumentsUpdateGuard updateGuard(authzManager); if (!updateGuard.tryLock("Grant privileges to role")) { return appendCommandStatus( result, Status(ErrorCodes::LockBusy, "Could not lock auth data update lock.")); } Status status = requireAuthSchemaVersion26Final(txn, authzManager); if (!status.isOK()) { return appendCommandStatus(result, status); } RoleName roleName; PrivilegeVector privilegesToAdd; BSONObj writeConcern; status = auth::parseAndValidateRolePrivilegeManipulationCommands( cmdObj, "grantPrivilegesToRole", dbname, &roleName, &privilegesToAdd, &writeConcern); if (!status.isOK()) { return appendCommandStatus(result, status); } if (RoleGraph::isBuiltinRole(roleName)) { return appendCommandStatus( result, Status(ErrorCodes::InvalidRoleModification, str::stream() << roleName.getFullName() << " is a built-in role and cannot be modified.")); } status = checkOkayToGrantPrivilegesToRole(roleName, privilegesToAdd); if (!status.isOK()) { return appendCommandStatus(result, status); } BSONObj roleDoc; status = authzManager->getRoleDescription(roleName, true, &roleDoc); if (!status.isOK()) { return appendCommandStatus(result, status); } PrivilegeVector privileges; status = auth::parseAndValidatePrivilegeArray(BSONArray(roleDoc["privileges"].Obj()), &privileges); if (!status.isOK()) { return appendCommandStatus(result, status); } for (PrivilegeVector::iterator it = privilegesToAdd.begin(); it != privilegesToAdd.end(); ++it) { Privilege::addPrivilegeToPrivilegeVector(&privileges, *it); } // Build up update modifier object to $set privileges. mutablebson::Document updateObj; mutablebson::Element setElement = updateObj.makeElementObject("$set"); status = updateObj.root().pushBack(setElement); if (!status.isOK()) { return appendCommandStatus(result, status); } mutablebson::Element privilegesElement = updateObj.makeElementArray("privileges"); status = setElement.pushBack(privilegesElement); if (!status.isOK()) { return appendCommandStatus(result, status); } status = authzManager->getBSONForPrivileges(privileges, privilegesElement); if (!status.isOK()) { return appendCommandStatus(result, status); } BSONObjBuilder updateBSONBuilder; updateObj.writeTo(&updateBSONBuilder); audit::logGrantPrivilegesToRole(ClientBasic::getCurrent(), roleName, privilegesToAdd); status = authzManager->updateRoleDocument( txn, roleName, updateBSONBuilder.done(), writeConcern); // Must invalidate even on bad status - what if the write succeeded but the GLE failed? authzManager->invalidateUserCache(); return appendCommandStatus(result, status); } } 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" << endl; } virtual Status checkAuthForCommand(ClientBasic* client, const std::string& dbname, const BSONObj& cmdObj) { AuthorizationSession* authzSession = client->getAuthorizationSession(); 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); } bool run(OperationContext* txn, const string& dbname, BSONObj& cmdObj, int options, string& errmsg, BSONObjBuilder& result, bool fromRepl) { AuthorizationManager* authzManager = getGlobalAuthorizationManager(); AuthzDocumentsUpdateGuard updateGuard(authzManager); if (!updateGuard.tryLock("Revoke privileges from role")) { return appendCommandStatus( result, Status(ErrorCodes::LockBusy, "Could not lock auth data update lock.")); } Status status = requireAuthSchemaVersion26Final(txn, authzManager); if (!status.isOK()) { return appendCommandStatus(result, status); } RoleName roleName; PrivilegeVector privilegesToRemove; BSONObj writeConcern; status = auth::parseAndValidateRolePrivilegeManipulationCommands( cmdObj, "revokePrivilegesFromRole", dbname, &roleName, &privilegesToRemove, &writeConcern); if (!status.isOK()) { return appendCommandStatus(result, status); } if (RoleGraph::isBuiltinRole(roleName)) { return appendCommandStatus( result, Status(ErrorCodes::InvalidRoleModification, str::stream() << roleName.getFullName() << " is a built-in role and cannot be modified.")); } BSONObj roleDoc; status = authzManager->getRoleDescription(roleName, true, &roleDoc); if (!status.isOK()) { return appendCommandStatus(result, status); } PrivilegeVector privileges; status = auth::parseAndValidatePrivilegeArray(BSONArray(roleDoc["privileges"].Obj()), &privileges); if (!status.isOK()) { return appendCommandStatus(result, status); } for (PrivilegeVector::iterator itToRm = privilegesToRemove.begin(); itToRm != privilegesToRemove.end(); ++itToRm) { for (PrivilegeVector::iterator curIt = privileges.begin(); curIt != privileges.end(); ++curIt) { if (curIt->getResourcePattern() == itToRm->getResourcePattern()) { curIt->removeActions(itToRm->getActions()); if (curIt->getActions().empty()) { privileges.erase(curIt); } break; } } } // Build up update modifier object to $set privileges. mutablebson::Document updateObj; mutablebson::Element setElement = updateObj.makeElementObject("$set"); status = updateObj.root().pushBack(setElement); if (!status.isOK()) { return appendCommandStatus(result, status); } mutablebson::Element privilegesElement = updateObj.makeElementArray("privileges"); status = setElement.pushBack(privilegesElement); if (!status.isOK()) { return appendCommandStatus(result, status); } status = authzManager->getBSONForPrivileges(privileges, privilegesElement); if (!status.isOK()) { return appendCommandStatus(result, status); } audit::logRevokePrivilegesFromRole(ClientBasic::getCurrent(), roleName, privilegesToRemove); BSONObjBuilder updateBSONBuilder; updateObj.writeTo(&updateBSONBuilder); status = authzManager->updateRoleDocument( txn, roleName, updateBSONBuilder.done(), writeConcern); // Must invalidate even on bad status - what if the write succeeded but the GLE failed? authzManager->invalidateUserCache(); return appendCommandStatus(result, status); } } 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." << endl; } virtual Status checkAuthForCommand(ClientBasic* client, const std::string& dbname, const BSONObj& cmdObj) { AuthorizationSession* authzSession = client->getAuthorizationSession(); std::vector 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); } bool run(OperationContext* txn, const string& dbname, BSONObj& cmdObj, int options, string& errmsg, BSONObjBuilder& result, bool fromRepl) { std::string roleNameString; std::vector rolesToAdd; BSONObj writeConcern; Status status = auth::parseRolePossessionManipulationCommands( cmdObj, "grantRolesToRole", dbname, &roleNameString, &rolesToAdd, &writeConcern); if (!status.isOK()) { return appendCommandStatus(result, status); } RoleName roleName(roleNameString, dbname); if (RoleGraph::isBuiltinRole(roleName)) { return appendCommandStatus( result, Status(ErrorCodes::InvalidRoleModification, str::stream() << roleName.getFullName() << " is a built-in role and cannot be modified.")); } AuthorizationManager* authzManager = getGlobalAuthorizationManager(); AuthzDocumentsUpdateGuard updateGuard(authzManager); if (!updateGuard.tryLock("Grant roles to role")) { return appendCommandStatus( result, Status(ErrorCodes::LockBusy, "Could not lock auth data update lock.")); } status = requireAuthSchemaVersion26Final(txn, authzManager); if (!status.isOK()) { return appendCommandStatus(result, status); } // Role existence has to be checked after acquiring the update lock BSONObj roleDoc; status = authzManager->getRoleDescription(roleName, false, &roleDoc); if (!status.isOK()) { return appendCommandStatus(result, status); } // Check for cycles status = checkOkayToGrantRolesToRole(roleName, rolesToAdd, authzManager); if (!status.isOK()) { return appendCommandStatus(result, status); } // Add new roles to existing roles std::vector directRoles; status = auth::parseRoleNamesFromBSONArray(BSONArray(roleDoc["roles"].Obj()), roleName.getDB(), &directRoles); if (!status.isOK()) { return appendCommandStatus(result, status); } for (vector::iterator it = rolesToAdd.begin(); it != rolesToAdd.end(); ++it) { const RoleName& roleToAdd = *it; if (!sequenceContains(directRoles, roleToAdd)) // Don't double-add role directRoles.push_back(*it); } audit::logGrantRolesToRole(ClientBasic::getCurrent(), roleName, rolesToAdd); status = authzManager->updateRoleDocument( txn, roleName, BSON("$set" << BSON("roles" << rolesVectorToBSONArray(directRoles))), writeConcern); // Must invalidate even on bad status - what if the write succeeded but the GLE failed? authzManager->invalidateUserCache(); return appendCommandStatus(result, status); } } 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." << endl; } virtual Status checkAuthForCommand(ClientBasic* client, const std::string& dbname, const BSONObj& cmdObj) { AuthorizationSession* authzSession = client->getAuthorizationSession(); std::vector 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); } bool run(OperationContext* txn, const string& dbname, BSONObj& cmdObj, int options, string& errmsg, BSONObjBuilder& result, bool fromRepl) { AuthorizationManager* authzManager = getGlobalAuthorizationManager(); AuthzDocumentsUpdateGuard updateGuard(authzManager); if (!updateGuard.tryLock("Revoke roles from role")) { return appendCommandStatus( result, Status(ErrorCodes::LockBusy, "Could not lock auth data update lock.")); } Status status = requireAuthSchemaVersion26Final(txn, authzManager); if (!status.isOK()) { return appendCommandStatus(result, status); } std::string roleNameString; std::vector rolesToRemove; BSONObj writeConcern; status = auth::parseRolePossessionManipulationCommands(cmdObj, "revokeRolesFromRole", dbname, &roleNameString, &rolesToRemove, &writeConcern); if (!status.isOK()) { return appendCommandStatus(result, status); } RoleName roleName(roleNameString, dbname); if (RoleGraph::isBuiltinRole(roleName)) { return appendCommandStatus( result, Status(ErrorCodes::InvalidRoleModification, str::stream() << roleName.getFullName() << " is a built-in role and cannot be modified.")); } BSONObj roleDoc; status = authzManager->getRoleDescription(roleName, false, &roleDoc); if (!status.isOK()) { return appendCommandStatus(result, status); } std::vector roles; status = auth::parseRoleNamesFromBSONArray(BSONArray(roleDoc["roles"].Obj()), roleName.getDB(), &roles); if (!status.isOK()) { return appendCommandStatus(result, status); } for (vector::const_iterator it = rolesToRemove.begin(); it != rolesToRemove.end(); ++it) { vector::iterator itToRm = std::find(roles.begin(), roles.end(), *it); if (itToRm != roles.end()) { roles.erase(itToRm); } } audit::logRevokeRolesFromRole(ClientBasic::getCurrent(), roleName, rolesToRemove); status = authzManager->updateRoleDocument( txn, roleName, BSON("$set" << BSON("roles" << rolesVectorToBSONArray(roles))), writeConcern); // Must invalidate even on bad status - what if the write succeeded but the GLE failed? authzManager->invalidateUserCache(); return appendCommandStatus(result, status); } } 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."<< endl; } virtual Status checkAuthForCommand(ClientBasic* client, const std::string& dbname, const BSONObj& cmdObj) { AuthorizationSession* authzSession = client->getAuthorizationSession(); 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(); } bool run(OperationContext* txn, const string& dbname, BSONObj& cmdObj, int options, string& errmsg, BSONObjBuilder& result, bool fromRepl) { AuthorizationManager* authzManager = getGlobalAuthorizationManager(); AuthzDocumentsUpdateGuard updateGuard(authzManager); if (!updateGuard.tryLock("Drop role")) { return appendCommandStatus( result, Status(ErrorCodes::LockBusy, "Could not lock auth data update lock.")); } Status status = requireAuthSchemaVersion26Final(txn, authzManager); if (!status.isOK()) { return appendCommandStatus(result, status); } RoleName roleName; BSONObj writeConcern; status = auth::parseDropRoleCommand(cmdObj, dbname, &roleName, &writeConcern); if (!status.isOK()) { return appendCommandStatus(result, status); } if (RoleGraph::isBuiltinRole(roleName)) { return appendCommandStatus( result, Status(ErrorCodes::InvalidRoleModification, str::stream() << roleName.getFullName() << " is a built-in role and cannot be modified.")); } BSONObj roleDoc; status = authzManager->getRoleDescription(roleName, false, &roleDoc); if (!status.isOK()) { return appendCommandStatus(result, status); } // Remove this role from all users int nMatched; status = authzManager->updateAuthzDocuments( txn, NamespaceString("admin.system.users"), BSON("roles" << BSON("$elemMatch" << BSON(AuthorizationManager::ROLE_NAME_FIELD_NAME << roleName.getRole() << AuthorizationManager::ROLE_DB_FIELD_NAME << roleName.getDB()))), BSON("$pull" << BSON("roles" << BSON(AuthorizationManager::ROLE_NAME_FIELD_NAME << roleName.getRole() << AuthorizationManager::ROLE_DB_FIELD_NAME << roleName.getDB()))), false, true, writeConcern, &nMatched); // Must invalidate even on bad status - what if the write succeeded but the GLE failed? authzManager->invalidateUserCache(); if (!status.isOK()) { ErrorCodes::Error code = status.code() == ErrorCodes::UnknownError ? ErrorCodes::UserModificationFailed : status.code(); return appendCommandStatus( result, Status(code, str::stream() << "Failed to remove role " << roleName.getFullName() << " from all users: " << status.reason())); } // Remove this role from all other roles status = authzManager->updateAuthzDocuments( txn, NamespaceString("admin.system.roles"), BSON("roles" << BSON("$elemMatch" << BSON(AuthorizationManager::ROLE_NAME_FIELD_NAME << roleName.getRole() << AuthorizationManager::ROLE_DB_FIELD_NAME << roleName.getDB()))), BSON("$pull" << BSON("roles" << BSON(AuthorizationManager::ROLE_NAME_FIELD_NAME << roleName.getRole() << AuthorizationManager::ROLE_DB_FIELD_NAME << roleName.getDB()))), false, true, writeConcern, &nMatched); // Must invalidate even on bad status - what if the write succeeded but the GLE failed? authzManager->invalidateUserCache(); if (!status.isOK()) { ErrorCodes::Error code = status.code() == ErrorCodes::UnknownError ? ErrorCodes::RoleModificationFailed : status.code(); return appendCommandStatus( result, Status(code, str::stream() << "Removed role " << roleName.getFullName() << " from all users but failed to remove from all roles: " << status.reason())); } audit::logDropRole(ClientBasic::getCurrent(), roleName); // Finally, remove the actual role document status = authzManager->removeRoleDocuments( txn, BSON(AuthorizationManager::ROLE_NAME_FIELD_NAME << roleName.getRole() << AuthorizationManager::ROLE_DB_FIELD_NAME << roleName.getDB()), writeConcern, &nMatched); // Must invalidate even on bad status - what if the write succeeded but the GLE failed? authzManager->invalidateUserCache(); if (!status.isOK()) { return appendCommandStatus( result, Status(status.code(), str::stream() << "Removed role " << roleName.getFullName() << " from all users and roles but failed to actually delete" " the role itself: " << status.reason())); } dassert(nMatched == 0 || nMatched == 1); if (nMatched == 0) { return appendCommandStatus( result, Status(ErrorCodes::RoleNotFound, str::stream() << "Role '" << roleName.getFullName() << "' not found")); } return true; } } 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." << endl; } virtual Status checkAuthForCommand(ClientBasic* client, const std::string& dbname, const BSONObj& cmdObj) { AuthorizationSession* authzSession = client->getAuthorizationSession(); 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(); } bool run(OperationContext* txn, const string& dbname, BSONObj& cmdObj, int options, string& errmsg, BSONObjBuilder& result, bool fromRepl) { BSONObj writeConcern; Status status = auth::parseDropAllRolesFromDatabaseCommand(cmdObj, dbname, &writeConcern); if (!status.isOK()) { return appendCommandStatus(result, status); } AuthorizationManager* authzManager = getGlobalAuthorizationManager(); AuthzDocumentsUpdateGuard updateGuard(authzManager); if (!updateGuard.tryLock("Drop roles from database")) { return appendCommandStatus( result, Status(ErrorCodes::LockBusy, "Could not lock auth data update lock.")); } status = requireAuthSchemaVersion26Final(txn, authzManager); if (!status.isOK()) { return appendCommandStatus(result, status); } // Remove these roles from all users int nMatched; status = authzManager->updateAuthzDocuments( txn, AuthorizationManager::usersCollectionNamespace, BSON("roles" << BSON(AuthorizationManager::ROLE_DB_FIELD_NAME << dbname)), BSON("$pull" << BSON("roles" << BSON(AuthorizationManager::ROLE_DB_FIELD_NAME << dbname))), false, true, writeConcern, &nMatched); // Must invalidate even on bad status - what if the write succeeded but the GLE failed? authzManager->invalidateUserCache(); if (!status.isOK()) { ErrorCodes::Error code = status.code() == ErrorCodes::UnknownError ? ErrorCodes::UserModificationFailed : status.code(); return appendCommandStatus( result, Status(code, str::stream() << "Failed to remove roles from \"" << dbname << "\" db from all users: " << status.reason())); } // Remove these roles from all other roles std::string sourceFieldName = str::stream() << "roles." << AuthorizationManager::ROLE_DB_FIELD_NAME; status = authzManager->updateAuthzDocuments( txn, AuthorizationManager::rolesCollectionNamespace, BSON(sourceFieldName << dbname), BSON("$pull" << BSON("roles" << BSON(AuthorizationManager::ROLE_DB_FIELD_NAME << dbname))), false, true, writeConcern, &nMatched); // Must invalidate even on bad status - what if the write succeeded but the GLE failed? authzManager->invalidateUserCache(); if (!status.isOK()) { ErrorCodes::Error code = status.code() == ErrorCodes::UnknownError ? ErrorCodes::RoleModificationFailed : status.code(); return appendCommandStatus( result, Status(code, str::stream() << "Failed to remove roles from \"" << dbname << "\" db from all roles: " << status.reason())); } audit::logDropAllRolesFromDatabase(ClientBasic::getCurrent(), dbname); // Finally, remove the actual role documents status = authzManager->removeRoleDocuments( txn, BSON(AuthorizationManager::ROLE_DB_FIELD_NAME << dbname), writeConcern, &nMatched); // Must invalidate even on bad status - what if the write succeeded but the GLE failed? authzManager->invalidateUserCache(); if (!status.isOK()) { return appendCommandStatus( result, Status(status.code(), str::stream() << "Removed roles from \"" << dbname << "\" db " " from all users and roles but failed to actually delete" " those roles themselves: " << status.reason())); } result.append("n", nMatched); return true; } } cmdDropAllRolesFromDatabase; class CmdRolesInfo: public Command { public: virtual bool slaveOk() const { return false; } virtual bool slaveOverrideOk() const { return true; } virtual bool isWriteCommandForConfigServer() const { return false; } CmdRolesInfo() : Command("rolesInfo") {} virtual void help(stringstream& ss) const { ss << "Returns information about roles." << endl; } virtual Status checkAuthForCommand(ClientBasic* client, const std::string& dbname, const BSONObj& cmdObj) { AuthorizationSession* authzSession = client->getAuthorizationSession(); 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(); } bool run(OperationContext* txn, const string& dbname, BSONObj& cmdObj, int options, string& errmsg, BSONObjBuilder& result, bool fromRepl) { auth::RolesInfoArgs args; Status status = auth::parseRolesInfoCommand(cmdObj, dbname, &args); if (!status.isOK()) { return appendCommandStatus(result, status); } status = requireAuthSchemaVersion26UpgradeOrFinal(txn, getGlobalAuthorizationManager()); if (!status.isOK()) { return appendCommandStatus(result, status); } BSONArrayBuilder rolesArrayBuilder; if (args.allForDB) { std::vector rolesDocs; status = getGlobalAuthorizationManager()->getRoleDescriptionsForDB( dbname, args.showPrivileges, args.showBuiltinRoles, &rolesDocs); if (!status.isOK()) { return appendCommandStatus(result, status); } for (size_t i = 0; i < rolesDocs.size(); ++i) { rolesArrayBuilder.append(rolesDocs[i]); } } else { for (size_t i = 0; i < args.roleNames.size(); ++i) { BSONObj roleDetails; status = getGlobalAuthorizationManager()->getRoleDescription( args.roleNames[i], args.showPrivileges, &roleDetails); if (status.code() == ErrorCodes::RoleNotFound) { continue; } if (!status.isOK()) { return appendCommandStatus(result, status); } rolesArrayBuilder.append(roleDetails); } } result.append("roles", rolesArrayBuilder.arr()); return true; } } cmdRolesInfo; class CmdInvalidateUserCache: public Command { public: virtual bool slaveOk() const { return true; } virtual bool adminOnly() const { return true; } virtual bool isWriteCommandForConfigServer() const { return false; } CmdInvalidateUserCache() : Command("invalidateUserCache") {} virtual void help(stringstream& ss) const { ss << "Invalidates the in-memory cache of user information" << endl; } virtual Status checkAuthForCommand(ClientBasic* client, const std::string& dbname, const BSONObj& cmdObj) { AuthorizationSession* authzSession = client->getAuthorizationSession(); if (!authzSession->isAuthorizedForActionsOnResource( ResourcePattern::forClusterResource(), ActionType::invalidateUserCache)) { return Status(ErrorCodes::Unauthorized, "Not authorized to invalidate user cache"); } return Status::OK(); } bool run(OperationContext* txn, const string& dbname, BSONObj& cmdObj, int options, string& errmsg, BSONObjBuilder& result, bool fromRepl) { AuthorizationManager* authzManager = getGlobalAuthorizationManager(); authzManager->invalidateUserCache(); return true; } } cmdInvalidateUserCache; class CmdGetCacheGeneration: public Command { public: virtual bool slaveOk() const { return true; } virtual bool adminOnly() const { return true; } virtual bool isWriteCommandForConfigServer() const { return false; } CmdGetCacheGeneration() : Command("_getUserCacheGeneration") {} virtual void help(stringstream& ss) const { ss << "internal" << endl; } virtual Status checkAuthForCommand(ClientBasic* client, const std::string& dbname, const BSONObj& cmdObj) { AuthorizationSession* authzSession = client->getAuthorizationSession(); if (!authzSession->isAuthorizedForActionsOnResource( ResourcePattern::forClusterResource(), ActionType::internal)) { return Status(ErrorCodes::Unauthorized, "Not authorized to get cache generation"); } return Status::OK(); } bool run(OperationContext* txn, const string& dbname, BSONObj& cmdObj, int options, string& errmsg, BSONObjBuilder& result, bool fromRepl) { AuthorizationManager* authzManager = getGlobalAuthorizationManager(); result.append("cacheGeneration", authzManager->getCacheGeneration()); return true; } } CmdGetCacheGeneration; /** * 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" << endl; } 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 = client->getAuthorizationSession(); 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(); } static UserName extractUserNameFromBSON(const BSONObj& userObj) { std::string name; std::string db; Status status = bsonExtractStringField(userObj, AuthorizationManager::USER_NAME_FIELD_NAME, &name); uassertStatusOK(status); status = bsonExtractStringField(userObj, AuthorizationManager::USER_DB_FIELD_NAME, &db); uassertStatusOK(status); return UserName(name, db); } /** * Extracts the UserName from the user document and adds it to set of existing users. * This function is written so it can used with stdx::bind over the result set of a query * on admin.system.users to add the user names of all existing users to the "usersToDrop" * set used in the command body. */ static void extractAndInsertUserName(unordered_set* existingUsers, const BSONObj& userObj) { UserName userName = extractUserNameFromBSON(userObj); existingUsers->insert(userName); } static RoleName extractRoleNameFromBSON(const BSONObj& roleObj) { std::string name; std::string db; Status status = bsonExtractStringField(roleObj, AuthorizationManager::ROLE_NAME_FIELD_NAME, &name); uassertStatusOK(status); status = bsonExtractStringField(roleObj, AuthorizationManager::ROLE_DB_FIELD_NAME, &db); uassertStatusOK(status); return RoleName(name, db); } /** * Extracts the RoleName from the role document and adds it to set of existing roles. * This function is written so it can used with stdx::bind over the result set of a query * on admin.system.roles to add the role names of all existing roles to the "rolesToDrop" * set used in the command body. */ static void extractAndInsertRoleName(unordered_set* existingRoles, const BSONObj& roleObj) { RoleName roleName = extractRoleNameFromBSON(roleObj); existingRoles->insert(roleName); } /** * Audits the fact that we are creating or updating the user described by userObj. */ static void auditCreateOrUpdateUser(const BSONObj& userObj, bool create) { UserName userName = extractUserNameFromBSON(userObj); std::vector roles; uassertStatusOK(auth::parseRoleNamesFromBSONArray(BSONArray(userObj["roles"].Obj()), userName.getDB(), &roles)); BSONObj customData; if (userObj.hasField("customData")) { customData = userObj["customData"].Obj(); } if (create) { audit::logCreateUser(ClientBasic::getCurrent(), userName, userObj["credentials"].Obj().hasField("MONGODB-CR"), userObj.hasField("customData") ? &customData : NULL, roles); } else { audit::logUpdateUser(ClientBasic::getCurrent(), userName, userObj["credentials"].Obj().hasField("MONGODB-CR"), userObj.hasField("customData") ? &customData : NULL, &roles); } } /** * Audits the fact that we are creating or updating the role described by roleObj. */ static void auditCreateOrUpdateRole(const BSONObj& roleObj, bool create) { RoleName roleName = extractRoleNameFromBSON(roleObj); std::vector roles; std::vector privileges; uassertStatusOK(auth::parseRoleNamesFromBSONArray(BSONArray(roleObj["roles"].Obj()), roleName.getDB(), &roles)); uassertStatusOK(auth::parseAndValidatePrivilegeArray( BSONArray(roleObj["privileges"].Obj()), &privileges)); if (create) { audit::logCreateRole(ClientBasic::getCurrent(), roleName, roles, privileges); } else { audit::logUpdateRole(ClientBasic::getCurrent(), roleName, &roles, &privileges); } } /** * Designed to be used with stdx::bind to be called on every user object in the result * set of a query over the tempUsersCollection provided to the command. For each user * in the temp collection that is defined on the given db, adds that user to the actual * admin.system.users collection. * Also removes any users it encounters from the usersToDrop set. */ static void addUser(OperationContext* txn, AuthorizationManager* authzManager, const StringData& db, bool update, const BSONObj& writeConcern, unordered_set* usersToDrop, const BSONObj& userObj) { UserName userName = extractUserNameFromBSON(userObj); if (!db.empty() && userName.getDB() != db) { return; } if (update && usersToDrop->count(userName)) { auditCreateOrUpdateUser(userObj, false); Status status = authzManager->updatePrivilegeDocument(txn, userName, userObj, writeConcern); if (!status.isOK()) { // Match the behavior of mongorestore to continue on failure warning() << "Could not update user " << userName << " in _mergeAuthzCollections command: " << status << endl; } } else { auditCreateOrUpdateUser(userObj, true); Status status = authzManager->insertPrivilegeDocument(txn, userName.getDB().toString(), userObj, writeConcern); if (!status.isOK()) { // Match the behavior of mongorestore to continue on failure warning() << "Could not insert user " << userName << " in _mergeAuthzCollections command: " << status << endl; } } usersToDrop->erase(userName); } /** * Designed to be used with stdx::bind to be called on every role object in the result * set of a query over the tempRolesCollection provided to the command. For each role * in the temp collection that is defined on the given db, adds that role to the actual * admin.system.roles collection. * Also removes any roles it encounters from the rolesToDrop set. */ static void addRole(OperationContext* txn, AuthorizationManager* authzManager, const StringData& db, bool update, const BSONObj& writeConcern, unordered_set* rolesToDrop, const BSONObj roleObj) { RoleName roleName = extractRoleNameFromBSON(roleObj); if (!db.empty() && roleName.getDB() != db) { return; } if (update && rolesToDrop->count(roleName)) { auditCreateOrUpdateRole(roleObj, false); Status status = authzManager->updateRoleDocument(txn, roleName, roleObj, writeConcern); if (!status.isOK()) { // Match the behavior of mongorestore to continue on failure warning() << "Could not update role " << roleName << " in _mergeAuthzCollections command: " << status << endl; } } else { auditCreateOrUpdateRole(roleObj, true); Status status = authzManager->insertRoleDocument(txn, roleObj, writeConcern); if (!status.isOK()) { // Match the behavior of mongorestore to continue on failure warning() << "Could not insert role " << roleName << " in _mergeAuthzCollections command: " << status << endl; } } rolesToDrop->erase(roleName); } /** * Moves all user objects from usersCollName into admin.system.users. If drop is true, * removes any users that were in admin.system.users but not in usersCollName. */ Status processUsers(OperationContext* txn, AuthorizationManager* authzManager, const StringData& usersCollName, const StringData& db, bool drop, const BSONObj& writeConcern) { // When the "drop" argument has been provided, we use this set to store the users // that are currently in the system, and remove from it as we encounter // same-named users in the collection we are restoring from. Once we've fully // moved over the temp users collection into its final location, we drop // any users that previously existed there but weren't in the temp collection. // This is so that we can completely replace the system.users // collection with the users from the temp collection, without removing all // users at the beginning and thus potentially locking ourselves out by having // no users in the whole system for a time. unordered_set usersToDrop; if (drop) { // Create map of the users currently in the DB BSONObj query = db.empty() ? BSONObj() : BSON(AuthorizationManager::USER_DB_FIELD_NAME << db); BSONObj fields = BSON(AuthorizationManager::USER_NAME_FIELD_NAME << 1 << AuthorizationManager::USER_DB_FIELD_NAME << 1); Status status = authzManager->queryAuthzDocument( txn, AuthorizationManager::usersCollectionNamespace, query, fields, stdx::bind(&CmdMergeAuthzCollections::extractAndInsertUserName, &usersToDrop, stdx::placeholders::_1)); if (!status.isOK()) { return status; } } Status status = authzManager->queryAuthzDocument( txn, NamespaceString(usersCollName), db.empty() ? BSONObj() : BSON(AuthorizationManager::USER_DB_FIELD_NAME << db), BSONObj(), stdx::bind(&CmdMergeAuthzCollections::addUser, txn, authzManager, db, drop, writeConcern, &usersToDrop, stdx::placeholders::_1)); if (!status.isOK()) { return status; } if (drop) { int numRemoved; for (unordered_set::iterator it = usersToDrop.begin(); it != usersToDrop.end(); ++it) { const UserName& userName = *it; audit::logDropUser(ClientBasic::getCurrent(), userName); status = authzManager->removePrivilegeDocuments( txn, BSON(AuthorizationManager::USER_NAME_FIELD_NAME << userName.getUser().toString() << AuthorizationManager::USER_DB_FIELD_NAME << userName.getDB().toString() ), writeConcern, &numRemoved); if (!status.isOK()) { return status; } dassert(numRemoved == 1); } } return Status::OK(); } /** * Moves all user objects from usersCollName into admin.system.users. If drop is true, * removes any users that were in admin.system.users but not in usersCollName. */ Status processRoles(OperationContext* txn, AuthorizationManager* authzManager, const StringData& rolesCollName, const StringData& db, bool drop, const BSONObj& writeConcern) { // When the "drop" argument has been provided, we use this set to store the roles // that are currently in the system, and remove from it as we encounter // same-named roles in the collection we are restoring from. Once we've fully // moved over the temp roles collection into its final location, we drop // any roles that previously existed there but weren't in the temp collection. // This is so that we can completely replace the system.roles // collection with the roles from the temp collection, without removing all // roles at the beginning and thus potentially locking ourselves out. unordered_set rolesToDrop; if (drop) { // Create map of the roles currently in the DB BSONObj query = db.empty() ? BSONObj() : BSON(AuthorizationManager::ROLE_DB_FIELD_NAME << db); BSONObj fields = BSON(AuthorizationManager::ROLE_NAME_FIELD_NAME << 1 << AuthorizationManager::ROLE_DB_FIELD_NAME << 1); Status status = authzManager->queryAuthzDocument( txn, AuthorizationManager::rolesCollectionNamespace, query, fields, stdx::bind(&CmdMergeAuthzCollections::extractAndInsertRoleName, &rolesToDrop, stdx::placeholders::_1)); if (!status.isOK()) { return status; } } Status status = authzManager->queryAuthzDocument( txn, NamespaceString(rolesCollName), db.empty() ? BSONObj() : BSON(AuthorizationManager::ROLE_DB_FIELD_NAME << db), BSONObj(), stdx::bind(&CmdMergeAuthzCollections::addRole, txn, authzManager, db, drop, writeConcern, &rolesToDrop, stdx::placeholders::_1)); if (!status.isOK()) { return status; } if (drop) { int numRemoved; for (unordered_set::iterator it = rolesToDrop.begin(); it != rolesToDrop.end(); ++it) { const RoleName& roleName = *it; audit::logDropRole(ClientBasic::getCurrent(), roleName); status = authzManager->removeRoleDocuments( txn, BSON(AuthorizationManager::ROLE_NAME_FIELD_NAME << roleName.getRole().toString() << AuthorizationManager::ROLE_DB_FIELD_NAME << roleName.getDB().toString() ), writeConcern, &numRemoved); if (!status.isOK()) { return status; } dassert(numRemoved == 1); } } return Status::OK(); } bool run(OperationContext* txn, const string& dbname, BSONObj& cmdObj, int options, string& errmsg, BSONObjBuilder& result, bool fromRepl) { auth::MergeAuthzCollectionsArgs args; Status status = auth::parseMergeAuthzCollectionsCommand(cmdObj, &args); if (!status.isOK()) { return appendCommandStatus(result, status); } if (args.usersCollName.empty() && args.rolesCollName.empty()) { return appendCommandStatus( result, Status(ErrorCodes::BadValue, "Must provide at least one of \"tempUsersCollection\" and " "\"tempRolescollection\"")); } AuthorizationManager* authzManager = getGlobalAuthorizationManager(); AuthzDocumentsUpdateGuard updateGuard(authzManager); if (!updateGuard.tryLock("_mergeAuthzCollections")) { return appendCommandStatus( result, Status(ErrorCodes::LockBusy, "Could not lock auth data update lock.")); } status = requireAuthSchemaVersion26Final(txn, authzManager); if (!status.isOK()) { return appendCommandStatus(result, status); } if (!args.usersCollName.empty()) { Status status = processUsers(txn, authzManager, args.usersCollName, args.db, args.drop, args.writeConcern); if (!status.isOK()) { return appendCommandStatus(result, status); } } if (!args.rolesCollName.empty()) { Status status = processRoles(txn, authzManager, args.rolesCollName, args.db, args.drop, args.writeConcern); if (!status.isOK()) { return appendCommandStatus(result, status); } } return true; } } cmdMergeAuthzCollections; CmdAuthSchemaUpgrade::CmdAuthSchemaUpgrade() : Command("authSchemaUpgrade") {} CmdAuthSchemaUpgrade::~CmdAuthSchemaUpgrade() {} bool CmdAuthSchemaUpgrade::slaveOk() const { return false; } bool CmdAuthSchemaUpgrade::adminOnly() const { return true; } bool CmdAuthSchemaUpgrade::isWriteCommandForConfigServer() const { return false; } void CmdAuthSchemaUpgrade::help(stringstream& ss) const { ss << "Upgrades the auth data storage schema"; } Status CmdAuthSchemaUpgrade::checkAuthForCommand(ClientBasic* client, const std::string& dbname, const BSONObj& cmdObj) { AuthorizationSession* authzSession = client->getAuthorizationSession(); if (!authzSession->isAuthorizedForActionsOnResource( ResourcePattern::forClusterResource(), ActionType::authSchemaUpgrade)) { return Status(ErrorCodes::Unauthorized, "Not authorized to run authSchemaUpgrade command."); } return Status::OK(); } }