/**
* 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();
}
}