/**
* 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/config.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/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/sasl_options.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/client.h"
#include "mongo/db/commands.h"
#include "mongo/db/dbdirectclient.h"
#include "mongo/db/jsobj.h"
#include "mongo/db/operation_context.h"
#include "mongo/db/service_context.h"
#include "mongo/platform/unordered_set.h"
#include "mongo/stdx/functional.h"
#include "mongo/stdx/mutex.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"
#include "mongo/util/time_support.h"
namespace mongo {
namespace str = mongoutils::str;
using std::endl;
using std::string;
using std::stringstream;
using std::vector;
namespace {
// Used to obtain mutex that guards modifications to persistent authorization data
const auto getAuthzDataMutex = ServiceContext::declareDecoration();
const Seconds authzDataMutexAcquisitionTimeout{5};
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();
}
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();
}
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();
}
/**
* Used to get all current roles of the user identified by 'userName'.
*/
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();
}
/**
* Checks that every role in "rolesToAdd" exists, that adding each of those roles to "role"
* will not result in a cycle to the role graph, and that every role being added comes from the
* same database as the role it is being added to (or that the role being added to is from the
* "admin" database.
*/
Status checkOkayToGrantRolesToRole(const RoleName& role,
const std::vector rolesToAdd,
AuthorizationManager* authzManager) {
for (std::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.
*/
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();
}
void appendBSONObjToBSONArrayBuilder(BSONArrayBuilder* array, const BSONObj& obj) {
array->append(obj);
}
/**
* Finds all documents matching "query" in "collectionName". For each document returned,
* calls the function resultProcessor on it.
* Should only be called on collections with authorization documents in them
* (ie admin.system.users and admin.system.roles).
*/
Status queryAuthzDocument(OperationContext* txn,
const NamespaceString& collectionName,
const BSONObj& query,
const BSONObj& projection,
const stdx::function& resultProcessor) {
try {
DBDirectClient client(txn);
client.query(resultProcessor, collectionName.ns(), query, &projection);
return Status::OK();
} catch (const DBException& e) {
return e.toStatus();
}
}
/**
* Inserts "document" into "collectionName".
* If there is a duplicate key error, returns a Status with code DuplicateKey.
*
* Should only be called on collections with authorization documents in them
* (ie admin.system.users and admin.system.roles).
*/
Status insertAuthzDocument(OperationContext* txn,
const NamespaceString& collectionName,
const BSONObj& document,
const BSONObj& writeConcern) {
try {
DBDirectClient client(txn);
client.insert(collectionName, document);
// Handle write concern
BSONObjBuilder gleBuilder;
gleBuilder.append("getLastError", 1);
gleBuilder.appendElements(writeConcern);
BSONObj res;
client.runCommand("admin", gleBuilder.done(), res);
string errstr = client.getLastErrorString(res);
if (errstr.empty()) {
return Status::OK();
}
if (res.hasField("code") && res["code"].Int() == ASSERT_ID_DUPKEY) {
return Status(ErrorCodes::DuplicateKey, errstr);
}
return Status(ErrorCodes::UnknownError, errstr);
} catch (const DBException& e) {
return e.toStatus();
}
}
/**
* Updates documents matching "query" according to "updatePattern" in "collectionName".
*
* Should only be called on collections with authorization documents in them
* (ie admin.system.users and admin.system.roles).
*/
Status updateAuthzDocuments(OperationContext* txn,
const NamespaceString& collectionName,
const BSONObj& query,
const BSONObj& updatePattern,
bool upsert,
bool multi,
const BSONObj& writeConcern,
int* nMatched) {
try {
DBDirectClient client(txn);
client.update(collectionName, query, updatePattern, upsert, multi);
// Handle write concern
BSONObjBuilder gleBuilder;
gleBuilder.append("getLastError", 1);
gleBuilder.appendElements(writeConcern);
BSONObj res;
client.runCommand("admin", gleBuilder.done(), res);
string errstr = client.getLastErrorString(res);
if (errstr.empty()) {
*nMatched = res["n"].numberInt();
return Status::OK();
}
return Status(ErrorCodes::UnknownError, errstr);
} catch (const DBException& e) {
return e.toStatus();
}
}
/**
* Update one document matching "query" according to "updatePattern" in "collectionName".
*
* If "upsert" is true and no document matches "query", inserts one using "query" as a
* template.
* If "upsert" is false and no document matches "query", return a Status with the code
* NoMatchingDocument. The Status message in that case is not very descriptive and should
* not be displayed to the end user.
*
* Should only be called on collections with authorization documents in them
* (ie admin.system.users and admin.system.roles).
*/
Status updateOneAuthzDocument(
OperationContext* txn,
const NamespaceString& collectionName,
const BSONObj& query,
const BSONObj& updatePattern,
bool upsert,
const BSONObj& writeConcern) {
int nMatched;
Status status = updateAuthzDocuments(txn,
collectionName,
query,
updatePattern,
upsert,
false,
writeConcern,
&nMatched);
if (!status.isOK()) {
return status;
}
dassert(nMatched == 1 || nMatched == 0);
if (nMatched == 0) {
return Status(ErrorCodes::NoMatchingDocument, "No document found");
}
return Status::OK();
}
/**
* Removes all documents matching "query" from "collectionName".
*
* Should only be called on collections with authorization documents in them
* (ie admin.system.users and admin.system.roles).
*/
Status removeAuthzDocuments(
OperationContext* txn,
const NamespaceString& collectionName,
const BSONObj& query,
const BSONObj& writeConcern,
int* numRemoved) {
try {
DBDirectClient client(txn);
client.remove(collectionName, query);
// Handle write concern
BSONObjBuilder gleBuilder;
gleBuilder.append("getLastError", 1);
gleBuilder.appendElements(writeConcern);
BSONObj res;
client.runCommand("admin", gleBuilder.done(), res);
string errstr = client.getLastErrorString(res);
if (errstr.empty()) {
*numRemoved = res["n"].numberInt();
return Status::OK();
}
return Status(ErrorCodes::UnknownError, errstr);
} catch (const DBException& e) {
return e.toStatus();
}
}
/**
* Creates the given role object in the given database.
* 'writeConcern' contains the arguments to be passed to getLastError to block for
* successful completion of the write.
*/
Status insertRoleDocument(OperationContext* txn,
const BSONObj& roleObj,
const BSONObj& writeConcern) {
Status status = insertAuthzDocument(txn,
AuthorizationManager::rolesCollectionNamespace,
roleObj,
writeConcern);
if (status.isOK()) {
return status;
}
if (status.code() == ErrorCodes::DuplicateKey) {
std::string name = roleObj[AuthorizationManager::ROLE_NAME_FIELD_NAME].String();
std::string source = roleObj[AuthorizationManager::ROLE_DB_FIELD_NAME].String();
return Status(ErrorCodes::DuplicateKey,
str::stream() << "Role \"" << name << "@" << source
<< "\" already exists");
}
if (status.code() == ErrorCodes::UnknownError) {
return Status(ErrorCodes::RoleModificationFailed, status.reason());
}
return status;
}
/**
* Updates the given role object with the given update modifier.
* 'writeConcern' contains the arguments to be passed to getLastError to block for
* successful completion of the write.
*/
Status updateRoleDocument(OperationContext* txn,
const RoleName& role,
const BSONObj& updateObj,
const BSONObj& writeConcern) {
Status status = updateOneAuthzDocument(
txn,
AuthorizationManager::rolesCollectionNamespace,
BSON(AuthorizationManager::ROLE_NAME_FIELD_NAME << role.getRole() <<
AuthorizationManager::ROLE_DB_FIELD_NAME << role.getDB()),
updateObj,
false,
writeConcern);
if (status.isOK()) {
return status;
}
if (status.code() == ErrorCodes::NoMatchingDocument) {
return Status(ErrorCodes::RoleNotFound,
str::stream() << "Role " << role.getFullName()
<< " not found");
}
if (status.code() == ErrorCodes::UnknownError) {
return Status(ErrorCodes::RoleModificationFailed, status.reason());
}
return status;
}
/**
* Removes roles matching the given query.
* Writes into *numRemoved the number of role documents that were modified.
* 'writeConcern' contains the arguments to be passed to getLastError to block for
* successful completion of the write.
*/
Status removeRoleDocuments(OperationContext* txn,
const BSONObj& query,
const BSONObj& writeConcern,
int* numRemoved) {
Status status = removeAuthzDocuments(txn,
AuthorizationManager::rolesCollectionNamespace,
query,
writeConcern,
numRemoved);
if (status.code() == ErrorCodes::UnknownError) {
return Status(ErrorCodes::RoleModificationFailed, status.reason());
}
return status;
}
/**
* Creates the given user object in the given database.
* 'writeConcern' contains the arguments to be passed to getLastError to block for
* successful completion of the write.
*/
Status insertPrivilegeDocument(OperationContext* txn,
const BSONObj& userObj,
const BSONObj& writeConcern) {
Status status = insertAuthzDocument(txn,
AuthorizationManager::usersCollectionNamespace,
userObj,
writeConcern);
if (status.isOK()) {
return status;
}
if (status.code() == ErrorCodes::DuplicateKey) {
std::string name = userObj[AuthorizationManager::USER_NAME_FIELD_NAME].String();
std::string source = userObj[AuthorizationManager::USER_DB_FIELD_NAME].String();
return Status(ErrorCodes::DuplicateKey,
str::stream() << "User \"" << name << "@" << source
<< "\" already exists");
}
if (status.code() == ErrorCodes::UnknownError) {
return Status(ErrorCodes::UserModificationFailed, status.reason());
}
return status;
}
/**
* Updates the given user object with the given update modifier.
* 'writeConcern' contains the arguments to be passed to getLastError to block for
* successful completion of the write.
*/
Status updatePrivilegeDocument(OperationContext* txn,
const UserName& user,
const BSONObj& updateObj,
const BSONObj& writeConcern) {
Status status = updateOneAuthzDocument(
txn,
AuthorizationManager::usersCollectionNamespace,
BSON(AuthorizationManager::USER_NAME_FIELD_NAME << user.getUser() <<
AuthorizationManager::USER_DB_FIELD_NAME << user.getDB()),
updateObj,
false,
writeConcern);
if (status.isOK()) {
return status;
}
if (status.code() == ErrorCodes::NoMatchingDocument) {
return Status(ErrorCodes::UserNotFound,
str::stream() << "User " << user.getFullName()
<< " not found");
}
if (status.code() == ErrorCodes::UnknownError) {
return Status(ErrorCodes::UserModificationFailed, status.reason());
}
return status;
}
/**
* Removes users for the given database matching the given query.
* Writes into *numRemoved the number of user documents that were modified.
* 'writeConcern' contains the arguments to be passed to getLastError to block for
* successful completion of the write.
*/
Status removePrivilegeDocuments(OperationContext* txn,
const BSONObj& query,
const BSONObj& writeConcern,
int* numRemoved) {
Status status = removeAuthzDocuments(txn,
AuthorizationManager::usersCollectionNamespace,
query,
writeConcern,
numRemoved);
if (status.code() == ErrorCodes::UnknownError) {
return Status(ErrorCodes::UserModificationFailed, status.reason());
}
return status;
}
/**
* Updates the auth schema version document to reflect the current state of the system.
* 'foundSchemaVersion' is the authSchemaVersion to update with.
*/
Status writeAuthSchemaVersionIfNeeded(OperationContext* txn,
AuthorizationManager* authzManager,
int foundSchemaVersion) {
Status status = updateOneAuthzDocument(txn,
AuthorizationManager::versionCollectionNamespace,
AuthorizationManager::versionDocumentQuery,
BSON("$set" << BSON(AuthorizationManager::schemaVersionFieldName
<< foundSchemaVersion)),
true, // upsert
BSONObj()); // write concern
if (status == ErrorCodes::NoMatchingDocument) { // SERVER-11492
status = Status::OK();
}
return status;
}
/**
* Returns Status::OK() if the current Auth schema version is at least the auth schema version
* for the MongoDB 2.6 and 3.0 MongoDB-CR/SCRAM mixed auth mode.
* Returns an error otherwise.
*/
Status requireAuthSchemaVersion26Final(OperationContext* txn,
AuthorizationManager* authzManager) {
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 writeAuthSchemaVersionIfNeeded(txn, authzManager, foundSchemaVersion);
}
/**
* Returns Status::OK() if the current Auth schema version is at least the auth schema version
* for MongoDB 2.6 during the upgrade process.
* Returns an error otherwise.
*/
Status requireAuthSchemaVersion26UpgradeOrFinal(OperationContext* txn,
AuthorizationManager* authzManager) {
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();
}
} // namespace
class CmdCreateUser : public Command {
public:
CmdCreateUser() : Command("createUser") {}
virtual bool slaveOk() const {
return false;
}
virtual bool isWriteCommandForConfigServer() const { return true; }
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) {
return auth::checkAuthForCreateUserCommand(client, dbname, cmdObj);
}
bool run(OperationContext* txn,
const string& dbname,
BSONObj& cmdObj,
int options,
string& errmsg,
BSONObjBuilder& result) {
auth::CreateOrUpdateUserArgs args;
Status status = auth::parseCreateOrUpdateUserCommands(cmdObj,
"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_CONFIG_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());
ServiceContext* serviceContext = txn->getClient()->getServiceContext();
AuthorizationManager* authzManager = AuthorizationManager::get(serviceContext);
int authzVersion;
status = authzManager->getAuthorizationVersion(txn, &authzVersion);
if (!status.isOK()) {
return appendCommandStatus(result, status);
}
BSONObjBuilder credentialsBuilder(userObjBuilder.subobjStart("credentials"));
if (!args.hasHashedPassword) {
// Must be an external user
credentialsBuilder.append("external", true);
}
else {
// Add SCRAM credentials for appropriate authSchemaVersions.
if (authzVersion > AuthorizationManager::schemaVersion26Final) {
BSONObj scramCred = scram::generateCredentials(
args.hashedPassword,
saslGlobalParams.scramIterationCount);
credentialsBuilder.append("SCRAM-SHA-1", scramCred);
}
else { // Otherwise default to MONGODB-CR.
credentialsBuilder.append("MONGODB-CR", args.hashedPassword);
}
}
credentialsBuilder.done();
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);
}
stdx::unique_lock lk(getAuthzDataMutex(serviceContext),
authzDataMutexAcquisitionTimeout);
if (!lk) {
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 = insertPrivilegeDocument(txn,
userObj,
args.writeConcern);
return appendCommandStatus(result, status);
}
virtual void redactForLogging(mutablebson::Document* cmdObj) {
auth::redactPasswordData(cmdObj->root());
}
} cmdCreateUser;
class CmdUpdateUser : public Command {
public:
CmdUpdateUser() : Command("updateUser") {}
virtual bool slaveOk() const {
return false;
}
virtual bool isWriteCommandForConfigServer() const { return true; }
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) {
return auth::checkAuthForUpdateUserCommand(client, dbname, cmdObj);
}
bool run(OperationContext* txn, const string& dbname,
BSONObj& cmdObj,
int options,
string& errmsg,
BSONObjBuilder& result) {
auth::CreateOrUpdateUserArgs args;
Status status = auth::parseCreateOrUpdateUserCommands(cmdObj,
"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,
saslGlobalParams.scramIterationCount);
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));
}
ServiceContext* serviceContext = txn->getClient()->getServiceContext();
stdx::unique_lock lk(getAuthzDataMutex(serviceContext),
authzDataMutexAcquisitionTimeout);
if (!lk) {
return appendCommandStatus(
result,
Status(ErrorCodes::LockBusy, "Could not lock auth data update lock."));
}
AuthorizationManager* authzManager = AuthorizationManager::get(serviceContext);
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 = 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) {
auth::redactPasswordData(cmdObj->root());
}
} cmdUpdateUser;
class CmdDropUser : public Command {
public:
CmdDropUser() : Command("dropUser") {}
virtual bool slaveOk() const {
return false;
}
virtual bool isWriteCommandForConfigServer() const { return true; }
virtual void help(stringstream& ss) const {
ss << "Drops a single user." << endl;
}
virtual Status checkAuthForCommand(ClientBasic* client,
const std::string& dbname,
const BSONObj& cmdObj) {
return auth::checkAuthForDropUserCommand(client, dbname, cmdObj);
}
bool run(OperationContext* txn, const string& dbname,
BSONObj& cmdObj,
int options,
string& errmsg,
BSONObjBuilder& result) {
ServiceContext* serviceContext = txn->getClient()->getServiceContext();
stdx::unique_lock lk(getAuthzDataMutex(serviceContext),
authzDataMutexAcquisitionTimeout);
if (!lk) {
return appendCommandStatus(
result,
Status(ErrorCodes::LockBusy, "Could not lock auth data update lock."));
}
AuthorizationManager* authzManager = AuthorizationManager::get(serviceContext);
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 = 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 true; }
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) {
return auth::checkAuthForDropAllUsersFromDatabaseCommand(client, dbname);
}
bool run(OperationContext* txn, const string& dbname,
BSONObj& cmdObj,
int options,
string& errmsg,
BSONObjBuilder& result) {
ServiceContext* serviceContext = txn->getClient()->getServiceContext();
stdx::unique_lock lk(getAuthzDataMutex(serviceContext),
authzDataMutexAcquisitionTimeout);
if (!lk) {
return appendCommandStatus(
result,
Status(ErrorCodes::LockBusy, "Could not lock auth data update lock."));
}
AuthorizationManager* authzManager = AuthorizationManager::get(serviceContext);
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 = 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 true; }
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) {
return auth::checkAuthForGrantRolesToUserCommand(client, dbname, cmdObj);
}
bool run(OperationContext* txn, const string& dbname,
BSONObj& cmdObj,
int options,
string& errmsg,
BSONObjBuilder& result) {
ServiceContext* serviceContext = txn->getClient()->getServiceContext();
stdx::unique_lock lk(getAuthzDataMutex(serviceContext),
authzDataMutexAcquisitionTimeout);
if (!lk) {
return appendCommandStatus(
result,
Status(ErrorCodes::LockBusy, "Could not lock auth data update lock."));
}
AuthorizationManager* authzManager = AuthorizationManager::get(serviceContext);
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 = 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 true; }
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) {
return auth::checkAuthForRevokeRolesFromUserCommand(client, dbname, cmdObj);
}
bool run(OperationContext* txn, const string& dbname,
BSONObj& cmdObj,
int options,
string& errmsg,
BSONObjBuilder& result) {
ServiceContext* serviceContext = txn->getClient()->getServiceContext();
stdx::unique_lock lk(getAuthzDataMutex(serviceContext),
authzDataMutexAcquisitionTimeout);
if (!lk) {
return appendCommandStatus(
result,
Status(ErrorCodes::LockBusy, "Could not lock auth data update lock."));
}
AuthorizationManager* authzManager = AuthorizationManager::get(serviceContext);
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 = 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) {
return auth::checkAuthForUsersInfoCommand(client, dbname, cmdObj);
}
bool run(OperationContext* txn, const string& dbname,
BSONObj& cmdObj,
int options,
string& errmsg,
BSONObjBuilder& result) {
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());
}
BSONObjBuilder projection;
if (!args.showCredentials) {
projection.append("credentials", 0);
}
const stdx::function function = stdx::bind(
appendBSONObjToBSONArrayBuilder,
&usersArrayBuilder,
stdx::placeholders::_1);
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 true; }
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) {
return auth::checkAuthForCreateRoleCommand(client, dbname, cmdObj);
}
bool run(OperationContext* txn, const string& dbname,
BSONObj& cmdObj,
int options,
string& errmsg,
BSONObjBuilder& result) {
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));
ServiceContext* serviceContext = txn->getClient()->getServiceContext();
stdx::unique_lock lk(getAuthzDataMutex(serviceContext),
authzDataMutexAcquisitionTimeout);
if (!lk) {
return appendCommandStatus(
result,
Status(ErrorCodes::LockBusy, "Could not lock auth data update lock."));
}
AuthorizationManager* authzManager = AuthorizationManager::get(serviceContext);
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 = 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 true; }
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) {
return auth::checkAuthForUpdateRoleCommand(client, dbname, cmdObj);
}
bool run(OperationContext* txn, const string& dbname,
BSONObj& cmdObj,
int options,
string& errmsg,
BSONObjBuilder& result) {
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));
}
ServiceContext* serviceContext = txn->getClient()->getServiceContext();
stdx::unique_lock lk(getAuthzDataMutex(serviceContext),
authzDataMutexAcquisitionTimeout);
if (!lk) {
return appendCommandStatus(
result,
Status(ErrorCodes::LockBusy, "Could not lock auth data update lock."));
}
AuthorizationManager* authzManager = AuthorizationManager::get(serviceContext);
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 = 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 true; }
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) {
return auth::checkAuthForGrantPrivilegesToRoleCommand(client, dbname, cmdObj);
}
bool run(OperationContext* txn, const string& dbname,
BSONObj& cmdObj,
int options,
string& errmsg,
BSONObjBuilder& result) {
ServiceContext* serviceContext = txn->getClient()->getServiceContext();
stdx::unique_lock lk(getAuthzDataMutex(serviceContext),
authzDataMutexAcquisitionTimeout);
if (!lk) {
return appendCommandStatus(
result,
Status(ErrorCodes::LockBusy, "Could not lock auth data update lock."));
}
AuthorizationManager* authzManager = AuthorizationManager::get(serviceContext);
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 = 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 true; }
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) {
return auth::checkAuthForRevokePrivilegesFromRoleCommand(client, dbname, cmdObj);
}
bool run(OperationContext* txn, const string& dbname,
BSONObj& cmdObj,
int options,
string& errmsg,
BSONObjBuilder& result) {
ServiceContext* serviceContext = txn->getClient()->getServiceContext();
stdx::unique_lock lk(getAuthzDataMutex(serviceContext),
authzDataMutexAcquisitionTimeout);
if (!lk) {
return appendCommandStatus(
result,
Status(ErrorCodes::LockBusy, "Could not lock auth data update lock."));
}
AuthorizationManager* authzManager = AuthorizationManager::get(serviceContext);
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 = 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 true; }
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) {
return auth::checkAuthForGrantRolesToRoleCommand(client, dbname, cmdObj);
}
bool run(OperationContext* txn, const string& dbname,
BSONObj& cmdObj,
int options,
string& errmsg,
BSONObjBuilder& result) {
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."));
}
ServiceContext* serviceContext = txn->getClient()->getServiceContext();
stdx::unique_lock lk(getAuthzDataMutex(serviceContext),
authzDataMutexAcquisitionTimeout);
if (!lk) {
return appendCommandStatus(
result,
Status(ErrorCodes::LockBusy, "Could not lock auth data update lock."));
}
AuthorizationManager* authzManager = AuthorizationManager::get(serviceContext);
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 = 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 true; }
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) {
return auth::checkAuthForRevokeRolesFromRoleCommand(client, dbname, cmdObj);
}
bool run(OperationContext* txn, const string& dbname,
BSONObj& cmdObj,
int options,
string& errmsg,
BSONObjBuilder& result) {
ServiceContext* serviceContext = txn->getClient()->getServiceContext();
stdx::unique_lock lk(getAuthzDataMutex(serviceContext),
authzDataMutexAcquisitionTimeout);
if (!lk) {
return appendCommandStatus(
result,
Status(ErrorCodes::LockBusy, "Could not lock auth data update lock."));
}
AuthorizationManager* authzManager = AuthorizationManager::get(serviceContext);
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 = 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 true; }
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) {
return auth::checkAuthForDropRoleCommand(client, dbname, cmdObj);
}
bool run(OperationContext* txn, const string& dbname,
BSONObj& cmdObj,
int options,
string& errmsg,
BSONObjBuilder& result) {
ServiceContext* serviceContext = txn->getClient()->getServiceContext();
stdx::unique_lock lk(getAuthzDataMutex(serviceContext),
authzDataMutexAcquisitionTimeout);
if (!lk) {
return appendCommandStatus(
result,
Status(ErrorCodes::LockBusy, "Could not lock auth data update lock."));
}
AuthorizationManager* authzManager = AuthorizationManager::get(serviceContext);
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 = updateAuthzDocuments(
txn,
AuthorizationManager::usersCollectionNamespace,
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 = updateAuthzDocuments(
txn,
AuthorizationManager::rolesCollectionNamespace,
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 = 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 true; }
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) {
return auth::checkAuthForDropAllRolesFromDatabaseCommand(client, dbname);
}
bool run(OperationContext* txn, const string& dbname,
BSONObj& cmdObj,
int options,
string& errmsg,
BSONObjBuilder& result) {
BSONObj writeConcern;
Status status = auth::parseDropAllRolesFromDatabaseCommand(cmdObj,
dbname,
&writeConcern);
if (!status.isOK()) {
return appendCommandStatus(result, status);
}
ServiceContext* serviceContext = txn->getClient()->getServiceContext();
stdx::unique_lock lk(getAuthzDataMutex(serviceContext),
authzDataMutexAcquisitionTimeout);
if (!lk) {
return appendCommandStatus(
result,
Status(ErrorCodes::LockBusy, "Could not lock auth data update lock."));
}
AuthorizationManager* authzManager = AuthorizationManager::get(serviceContext);
status = requireAuthSchemaVersion26Final(txn, authzManager);
if (!status.isOK()) {
return appendCommandStatus(result, status);
}
// Remove these roles from all users
int nMatched;
status = 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 = 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 = 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) {
return auth::checkAuthForRolesInfoCommand(client, dbname, cmdObj);
}
bool run(OperationContext* txn, const string& dbname,
BSONObj& cmdObj,
int options,
string& errmsg,
BSONObjBuilder& result) {
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) {
return auth::checkAuthForInvalidateUserCacheCommand(client);
}
bool run(OperationContext* txn, const string& dbname,
BSONObj& cmdObj,
int options,
string& errmsg,
BSONObjBuilder& result) {
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) {
return auth::checkAuthForGetUserCacheGenerationCommand(client);
}
bool run(OperationContext* txn,
const string& dbname,
BSONObj& cmdObj,
int options,
string& errmsg,
BSONObjBuilder& result) {
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 true; }
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) {
return auth::checkAuthForMergeAuthzCollectionsCommand(client, cmdObj);
}
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,
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 = 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 = insertPrivilegeDocument(txn,
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,
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 = 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 = 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,
StringData usersCollName,
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 = queryAuthzDocument(
txn,
AuthorizationManager::usersCollectionNamespace,
query,
fields,
stdx::bind(&CmdMergeAuthzCollections::extractAndInsertUserName,
&usersToDrop,
stdx::placeholders::_1));
if (!status.isOK()) {
return status;
}
}
Status status = 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 = 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,
StringData rolesCollName,
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 = queryAuthzDocument(
txn,
AuthorizationManager::rolesCollectionNamespace,
query,
fields,
stdx::bind(&CmdMergeAuthzCollections::extractAndInsertRoleName,
&rolesToDrop,
stdx::placeholders::_1));
if (!status.isOK()) {
return status;
}
}
Status status = 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 = 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) {
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\""));
}
ServiceContext* serviceContext = txn->getClient()->getServiceContext();
stdx::unique_lock lk(getAuthzDataMutex(serviceContext),
authzDataMutexAcquisitionTimeout);
if (!lk) {
return appendCommandStatus(
result,
Status(ErrorCodes::LockBusy, "Could not lock auth data update lock."));
}
AuthorizationManager* authzManager = AuthorizationManager::get(serviceContext);
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;
} // namespace mongo