diff options
author | Sara Golemon <sara.golemon@mongodb.com> | 2020-06-23 14:20:48 +0000 |
---|---|---|
committer | Evergreen Agent <no-reply@evergreen.mongodb.com> | 2020-07-02 15:34:37 +0000 |
commit | ebe622f95a071d7d769298b68ecf45b6cdff8e39 (patch) | |
tree | 28fe923dbe17deac6e73fffcac4074d9d860c72c /src/mongo/db/commands/user_management_commands.cpp | |
parent | 986028e17b939b4246b3d071ca6945a15604f0fc (diff) | |
download | mongo-ebe622f95a071d7d769298b68ecf45b6cdff8e39.tar.gz |
SERVER-49077 IDLify UMC Mutation commands
Diffstat (limited to 'src/mongo/db/commands/user_management_commands.cpp')
-rw-r--r-- | src/mongo/db/commands/user_management_commands.cpp | 1927 |
1 files changed, 785 insertions, 1142 deletions
diff --git a/src/mongo/db/commands/user_management_commands.cpp b/src/mongo/db/commands/user_management_commands.cpp index ab52778e48c..8fc24ce6d79 100644 --- a/src/mongo/db/commands/user_management_commands.cpp +++ b/src/mongo/db/commands/user_management_commands.cpp @@ -58,6 +58,7 @@ #include "mongo/db/commands.h" #include "mongo/db/commands/run_aggregate.h" #include "mongo/db/commands/user_management_commands_common.h" +#include "mongo/db/commands/user_management_commands_gen.h" #include "mongo/db/concurrency/d_concurrency.h" #include "mongo/db/dbdirectclient.h" #include "mongo/db/jsobj.h" @@ -291,7 +292,7 @@ Status updateAuthzDocuments(OperationContext* opCtx, const BSONObj& updatePattern, bool upsert, bool multi, - long long* nMatched) { + std::int64_t* numMatched) { try { DBDirectClient client(opCtx); @@ -317,7 +318,7 @@ Status updateAuthzDocuments(OperationContext* opCtx, return Status(ErrorCodes::FailedToParse, errmsg); } if (response.getOk()) { - *nMatched = response.getN(); + *numMatched = response.getN(); } return response.toStatus(); } catch (const DBException& e) { @@ -342,14 +343,14 @@ Status updateOneAuthzDocument(OperationContext* opCtx, const BSONObj& query, const BSONObj& updatePattern, bool upsert) { - long long nMatched; - Status status = - updateAuthzDocuments(opCtx, collectionName, query, updatePattern, upsert, false, &nMatched); + std::int64_t numMatched; + Status status = updateAuthzDocuments( + opCtx, collectionName, query, updatePattern, upsert, false, &numMatched); if (!status.isOK()) { return status; } - dassert(nMatched == 1 || nMatched == 0); - if (nMatched == 0) { + dassert(numMatched == 1 || numMatched == 0); + if (numMatched == 0) { return Status(ErrorCodes::NoMatchingDocument, "No document found"); } return Status::OK(); @@ -364,7 +365,7 @@ Status updateOneAuthzDocument(OperationContext* opCtx, Status removeAuthzDocuments(OperationContext* opCtx, const NamespaceString& collectionName, const BSONObj& query, - long long* numRemoved) { + std::int64_t* numRemoved) { try { DBDirectClient client(opCtx); @@ -445,7 +446,9 @@ Status updateRoleDocument(OperationContext* opCtx, const RoleName& role, const B * Removes roles matching the given query. * Writes into *numRemoved the number of role documents that were modified. */ -Status removeRoleDocuments(OperationContext* opCtx, const BSONObj& query, long long* numRemoved) { +Status removeRoleDocuments(OperationContext* opCtx, + const BSONObj& query, + std::int64_t* numRemoved) { Status status = removeAuthzDocuments( opCtx, AuthorizationManager::rolesCollectionNamespace, query, numRemoved); if (status.code() == ErrorCodes::UnknownError) { @@ -520,7 +523,7 @@ Status updatePrivilegeDocument(OperationContext* opCtx, */ Status removePrivilegeDocuments(OperationContext* opCtx, const BSONObj& query, - long long* numRemoved) { + std::int64_t* numRemoved) { Status status = removeAuthzDocuments( opCtx, AuthorizationManager::usersCollectionNamespace, query, numRemoved); if (status.code() == ErrorCodes::UnknownError) { @@ -656,43 +659,47 @@ StatusWith<AuthzLockGuard> requireReadableAuthSchema26Upgrade(OperationContext* return std::move(lk); } -Status buildCredentials(BSONObjBuilder* builder, const auth::CreateOrUpdateUserArgs& args) { - if (!args.hasPassword) { +template <typename T> +void buildCredentials(BSONObjBuilder* builder, const UserName& userName, const T& cmd) { + if (cmd.getPwd() == boost::none) { // Must be external user. builder->append("external", true); - return Status::OK(); + return; } bool buildSCRAMSHA1 = false, buildSCRAMSHA256 = false; - if (args.mechanisms.empty()) { - buildSCRAMSHA1 = sequenceContains(saslGlobalParams.authenticationMechanisms, "SCRAM-SHA-1"); - buildSCRAMSHA256 = - sequenceContains(saslGlobalParams.authenticationMechanisms, "SCRAM-SHA-256"); - } else { - for (const auto& mech : args.mechanisms) { + if (auto mechanisms = cmd.getMechanisms(); mechanisms && !mechanisms->empty()) { + for (const auto& mech : mechanisms.get()) { if (mech == "SCRAM-SHA-1") { buildSCRAMSHA1 = true; } else if (mech == "SCRAM-SHA-256") { buildSCRAMSHA256 = true; } else { - return {ErrorCodes::BadValue, - str::stream() << "Unknown auth mechanism '" << mech << "'"}; + uasserted(ErrorCodes::BadValue, + str::stream() << "Unknown auth mechanism '" << mech << "'"); } - if (!sequenceContains(saslGlobalParams.authenticationMechanisms, mech)) { - return {ErrorCodes::BadValue, - str::stream() << mech << " not supported in authMechanisms"}; - } + uassert(ErrorCodes::BadValue, + str::stream() << mech << " not supported in authMechanisms", + sequenceContains(saslGlobalParams.authenticationMechanisms, mech)); } + + } else { + buildSCRAMSHA1 = sequenceContains(saslGlobalParams.authenticationMechanisms, "SCRAM-SHA-1"); + buildSCRAMSHA256 = + sequenceContains(saslGlobalParams.authenticationMechanisms, "SCRAM-SHA-256"); } + auto password = cmd.getPwd().get(); + const bool digestPassword = cmd.getDigestPassword(); + if (buildSCRAMSHA1) { // Add SCRAM-SHA-1 credentials. std::string hashedPwd; - if (args.digestPassword) { - hashedPwd = createPasswordDigest(args.userName.getUser(), args.password); + if (digestPassword) { + hashedPwd = createPasswordDigest(userName.getUser(), password); } else { - hashedPwd = args.password; + hashedPwd = password.toString(); } auto sha1Cred = scram::Secrets<SHA1Block>::generateCredentials( hashedPwd, saslGlobalParams.scramSHA1IterationCount.load()); @@ -700,57 +707,51 @@ Status buildCredentials(BSONObjBuilder* builder, const auth::CreateOrUpdateUserA } if (buildSCRAMSHA256) { - if (!args.digestPassword) { - return {ErrorCodes::BadValue, "Use of SCRAM-SHA-256 requires undigested passwords"}; - } - const auto swPwd = icuSaslPrep(args.password); - if (!swPwd.isOK()) { - return swPwd.getStatus(); - } + uassert(ErrorCodes::BadValue, + "Use of SCRAM-SHA-256 requires undigested passwords", + digestPassword); + + auto prepPwd = uassertStatusOK(icuSaslPrep(password)); auto sha256Cred = scram::Secrets<SHA256Block>::generateCredentials( - swPwd.getValue(), saslGlobalParams.scramSHA256IterationCount.load()); + prepPwd, saslGlobalParams.scramSHA256IterationCount.load()); builder->append("SCRAM-SHA-256", sha256Cred); } - - return Status::OK(); } -Status trimCredentials(OperationContext* opCtx, - BSONObjBuilder* queryBuilder, - BSONObjBuilder* unsetBuilder, - const auth::CreateOrUpdateUserArgs& args) { +void trimCredentials(OperationContext* opCtx, + const UserName& userName, + BSONObjBuilder* queryBuilder, + BSONObjBuilder* unsetBuilder, + const std::vector<StringData>& mechanisms) { auto* authzManager = AuthorizationManager::get(opCtx->getServiceContext()); + BSONObj userObj; - const auto status = authzManager->getUserDescription(opCtx, args.userName, &userObj); - if (!status.isOK()) { - return status; - } + uassertStatusOK(authzManager->getUserDescription(opCtx, userName, &userObj)); const auto& credsElem = userObj["credentials"]; - if (credsElem.eoo() || (credsElem.type() != Object)) { - return {ErrorCodes::UnsupportedFormat, - "Unable to trim credentials from a user document with no credentials"}; - } + uassert(ErrorCodes::UnsupportedFormat, + "Unable to trim credentials from a user document with no credentials", + credsElem.type() == Object); const auto& creds = credsElem.Obj(); queryBuilder->append("credentials", creds); bool keepSCRAMSHA1 = false, keepSCRAMSHA256 = false; - for (const auto& mech : args.mechanisms) { - if (!creds.hasField(mech)) { - return {ErrorCodes::BadValue, - "mechanisms field must be a subset of previously set mechanisms"}; - } + for (const auto& mech : mechanisms) { + uassert(ErrorCodes::BadValue, + "mechanisms field must be a subset of previously set mechanisms", + creds.hasField(mech)); + if (mech == "SCRAM-SHA-1") { keepSCRAMSHA1 = true; } else if (mech == "SCRAM-SHA-256") { keepSCRAMSHA256 = true; } } - if (!(keepSCRAMSHA1 || keepSCRAMSHA256)) { - return {ErrorCodes::BadValue, - "mechanisms field must contain at least one previously set known mechanism"}; - } + + uassert(ErrorCodes::BadValue, + "mechanisms field must contain at least one previously set known mechanism", + keepSCRAMSHA1 || keepSCRAMSHA256); if (!keepSCRAMSHA1) { unsetBuilder->append("credentials.SCRAM-SHA-1", ""); @@ -758,487 +759,391 @@ Status trimCredentials(OperationContext* opCtx, if (!keepSCRAMSHA256) { unsetBuilder->append("credentials.SCRAM-SHA-256", ""); } - - return Status::OK(); } -class CmdCreateUser : public BasicCommand { -public: - CmdCreateUser() : BasicCommand("createUser") {} - - AllowedOnSecondary secondaryAllowed(ServiceContext*) const override { - return AllowedOnSecondary::kNever; - } - - bool supportsWriteConcern(const BSONObj& cmd) const override { - return true; +template <typename T> +BSONArray vectorToBSON(const std::vector<T>& vec) { + BSONArrayBuilder builder; + for (const auto& val : vec) { + builder.append(val.toBSON()); } + return builder.arr(); +} - std::string help() const override { - return "Adds a user to the system"; - } - - Status checkAuthForCommand(Client* client, - const std::string& dbname, - const BSONObj& cmdObj) const override { - return auth::checkAuthForCreateUserCommand(client, dbname, cmdObj); - } - - bool run(OperationContext* opCtx, - const std::string& dbname, - const BSONObj& cmdObj, - BSONObjBuilder& result) override { - auth::CreateOrUpdateUserArgs args; - Status status = auth::parseCreateOrUpdateUserCommands(cmdObj, "createUser", dbname, &args); - uassertStatusOK(status); - - if (args.userName.getDB() == "local") { - uasserted(ErrorCodes::BadValue, "Cannot create users in the local database"); - } - - if (!args.hasPassword && args.userName.getDB() != "$external") { - uasserted(ErrorCodes::BadValue, - "Must provide a 'pwd' field for all user documents, except those" - " with '$external' as the user's source db"); - } - - if ((args.hasPassword) && args.userName.getDB() == "$external") { - uasserted(ErrorCodes::BadValue, - "Cannot set the password for users defined on the '$external' " - "database"); - } - - if (!args.hasRoles) { - uasserted(ErrorCodes::BadValue, "\"createUser\" command requires a \"roles\" array"); - } - -#ifdef MONGO_CONFIG_SSL - if (args.userName.getDB() == "$external" && getSSLManager() && - getSSLManager()->getSSLConfiguration().isClusterMember(args.userName.getUser())) { - uasserted(ErrorCodes::BadValue, - "Cannot create an x.509 user with a subjectname " - "that would be recognized as an internal " - "cluster member."); - } -#endif - - BSONObjBuilder userObjBuilder; - userObjBuilder.append( - "_id", str::stream() << args.userName.getDB() << "." << args.userName.getUser()); - UUID::gen().appendToBuilder(&userObjBuilder, AuthorizationManager::USERID_FIELD_NAME); - userObjBuilder.append(AuthorizationManager::USER_NAME_FIELD_NAME, args.userName.getUser()); - userObjBuilder.append(AuthorizationManager::USER_DB_FIELD_NAME, args.userName.getDB()); - - ServiceContext* serviceContext = opCtx->getClient()->getServiceContext(); - AuthorizationManager* authzManager = AuthorizationManager::get(serviceContext); - - auto lk = uassertStatusOK(requireWritableAuthSchema28SCRAM(opCtx, authzManager)); +template <typename RequestT, typename ReplyT> +class CmdUMCTyped : public TypedCommand<CmdUMCTyped<RequestT, ReplyT>> { +public: + using Request = RequestT; + using Reply = ReplyT; + using TC = TypedCommand<CmdUMCTyped<RequestT, ReplyT>>; - int authzVersion; - status = authzManager->getAuthorizationVersion(opCtx, &authzVersion); - uassertStatusOK(status); + class Invocation final : public TC::InvocationBase { + public: + using TC::InvocationBase::InvocationBase; + using TC::InvocationBase::request; - BSONObjBuilder credentialsBuilder(userObjBuilder.subobjStart("credentials")); - status = buildCredentials(&credentialsBuilder, args); - uassertStatusOK(status); - credentialsBuilder.done(); + Reply typedRun(OperationContext* opCtx); - if (args.authenticationRestrictions && !args.authenticationRestrictions->isEmpty()) { - credentialsBuilder.append("authenticationRestrictions", - *args.authenticationRestrictions); + private: + bool supportsWriteConcern() const final { + return true; } - if (args.hasCustomData) { - userObjBuilder.append("customData", args.customData); + void doCheckAuthorization(OperationContext* opCtx) const final { + auth::checkAuthForTypedCommand(opCtx->getClient(), request()); } - userObjBuilder.append("roles", rolesVectorToBSONArray(args.roles)); - - BSONObj userObj = userObjBuilder.obj(); - V2UserDocumentParser parser; - status = parser.checkValidUserDocument(userObj); - uassertStatusOK(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(opCtx, args.roles[i], &ignored); - uassertStatusOK(status); + NamespaceString ns() const override { + return NamespaceString(request().getDbName(), ""); } + }; - audit::logCreateUser(Client::getCurrent(), - args.userName, - args.hasPassword, - args.hasCustomData ? &args.customData : nullptr, - args.roles, - args.authenticationRestrictions); - status = insertPrivilegeDocument(opCtx, userObj); - authzManager->invalidateUserByName(opCtx, args.userName); - uassertStatusOK(status); - return true; - } - - StringData sensitiveFieldName() const final { - return "pwd"_sd; + typename TC::AllowedOnSecondary secondaryAllowed(ServiceContext*) const final { + return TC::AllowedOnSecondary::kNever; } +}; -} cmdCreateUser; - -class CmdUpdateUser : public BasicCommand { +class CmdCreateUser : public CmdUMCTyped<CreateUserCommand, void> { public: - CmdUpdateUser() : BasicCommand("updateUser") {} - - AllowedOnSecondary secondaryAllowed(ServiceContext*) const override { - return AllowedOnSecondary::kNever; - } - - bool supportsWriteConcern(const BSONObj& cmd) const override { - return true; - } + static constexpr StringData kPwdField = "pwd"_sd; - std::string help() const override { - return "Used to update a user, for example to change its password"; - } - - Status checkAuthForCommand(Client* client, - const std::string& dbname, - const BSONObj& cmdObj) const override { - return auth::checkAuthForUpdateUserCommand(client, dbname, cmdObj); + StringData sensitiveFieldName() const final { + return kPwdField; } +} cmdCreateUser; - bool run(OperationContext* opCtx, - const std::string& dbname, - const BSONObj& cmdObj, - BSONObjBuilder& result) override { - auth::CreateOrUpdateUserArgs args; - Status status = auth::parseCreateOrUpdateUserCommands(cmdObj, "updateUser", dbname, &args); - uassertStatusOK(status); - - if (!args.hasPassword && !args.hasCustomData && !args.hasRoles && - !args.authenticationRestrictions && args.mechanisms.empty()) { - uasserted(ErrorCodes::BadValue, - "Must specify at least one field to update in updateUser"); - } +template <> +void CmdUMCTyped<CreateUserCommand, void>::Invocation::typedRun(OperationContext* opCtx) { + const auto& cmd = request(); + const auto& dbname = cmd.getDbName(); - if (args.hasPassword && args.userName.getDB() == "$external") { - uasserted(ErrorCodes::BadValue, - "Cannot set the password for users defined on the '$external' " - "database"); - } + // Validate input + uassert(ErrorCodes::BadValue, "Cannot create users in the local database", dbname != "local"); - BSONObjBuilder queryBuilder; - queryBuilder.append(AuthorizationManager::USER_NAME_FIELD_NAME, args.userName.getUser()); - queryBuilder.append(AuthorizationManager::USER_DB_FIELD_NAME, args.userName.getDB()); + uassert(ErrorCodes::BadValue, + "Username cannot contain NULL characters", + cmd.getCommandParameter().find('\0') == std::string::npos); + UserName userName(cmd.getCommandParameter(), dbname); - BSONObjBuilder updateSetBuilder; - BSONObjBuilder updateUnsetBuilder; - if (args.hasPassword) { - BSONObjBuilder credentialsBuilder(updateSetBuilder.subobjStart("credentials")); - status = buildCredentials(&credentialsBuilder, args); - uassertStatusOK(status); - credentialsBuilder.done(); - } else if (!args.mechanisms.empty()) { - status = trimCredentials(opCtx, &queryBuilder, &updateUnsetBuilder, args); - uassertStatusOK(status); - } + uassert(ErrorCodes::BadValue, + "Must provide a 'pwd' field for all user documents, except those" + " with '$external' as the user's source db", + (cmd.getPwd() != boost::none) || (dbname == "$external")); - if (args.hasCustomData) { - updateSetBuilder.append("customData", args.customData); - } + uassert(ErrorCodes::BadValue, + "Cannot set the password for users defined on the '$external' database", + (cmd.getPwd() == boost::none) || (dbname != "$external")); - if (args.authenticationRestrictions) { - if (args.authenticationRestrictions->isEmpty()) { - updateUnsetBuilder.append("authenticationRestrictions", ""); - } else { - auto swParsedRestrictions = - parseAuthenticationRestriction(*args.authenticationRestrictions); - uassertStatusOK(swParsedRestrictions.getStatus()); + uassert(ErrorCodes::BadValue, + "mechanisms field must not be empty", + (cmd.getMechanisms() == boost::none) || !cmd.getMechanisms()->empty()); - updateSetBuilder.append("authenticationRestrictions", - *args.authenticationRestrictions); - } - } +#ifdef MONGO_CONFIG_SSL + uassert(ErrorCodes::BadValue, + "Cannot create an x.509 user with a subjectname that would be " + "recognized as an internal cluster member", + (dbname != "$external") || !getSSLManager() || + !getSSLManager()->getSSLConfiguration().isClusterMember(userName.getUser())); +#endif - if (args.hasRoles) { - updateSetBuilder.append("roles", rolesVectorToBSONArray(args.roles)); - } + // Synthesize a user document + BSONObjBuilder userObjBuilder; + userObjBuilder.append("_id", userName.getUnambiguousName()); + UUID::gen().appendToBuilder(&userObjBuilder, AuthorizationManager::USERID_FIELD_NAME); + userObjBuilder.append(AuthorizationManager::USER_NAME_FIELD_NAME, userName.getUser()); + userObjBuilder.append(AuthorizationManager::USER_DB_FIELD_NAME, userName.getDB()); - BSONObj updateSet = updateSetBuilder.done(); - BSONObj updateUnset = updateUnsetBuilder.done(); - BSONObjBuilder updateDocumentBuilder; - if (!updateSet.isEmpty()) { - updateDocumentBuilder << "$set" << updateSet; - } - if (!updateUnset.isEmpty()) { - updateDocumentBuilder << "$unset" << updateUnset; - } + auto* serviceContext = opCtx->getClient()->getServiceContext(); + auto* authzManager = AuthorizationManager::get(serviceContext); - ServiceContext* serviceContext = opCtx->getClient()->getServiceContext(); - AuthorizationManager* authzManager = AuthorizationManager::get(serviceContext); + auto lk = uassertStatusOK(requireWritableAuthSchema28SCRAM(opCtx, authzManager)); - auto lk = uassertStatusOK(requireWritableAuthSchema28SCRAM(opCtx, authzManager)); + int authzVersion; + uassertStatusOK(authzManager->getAuthorizationVersion(opCtx, &authzVersion)); - // 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(opCtx, args.roles[i], &ignored); - uassertStatusOK(status); - } - } + BSONObjBuilder credentialsBuilder(userObjBuilder.subobjStart("credentials")); + buildCredentials(&credentialsBuilder, userName, cmd); + credentialsBuilder.done(); - audit::logUpdateUser(Client::getCurrent(), - args.userName, - args.hasPassword, - args.hasCustomData ? &args.customData : nullptr, - args.hasRoles ? &args.roles : nullptr, - args.authenticationRestrictions); - - status = updatePrivilegeDocument( - opCtx, args.userName, queryBuilder.done(), updateDocumentBuilder.done()); - // Must invalidate even on bad status - what if the write succeeded but the GLE failed? - authzManager->invalidateUserByName(opCtx, args.userName); - uassertStatusOK(status); - return true; + if (auto ar = cmd.getAuthenticationRestrictions(); ar && !ar->empty()) { + userObjBuilder.append("authenticationRestrictions", vectorToBSON(ar.get())); } - StringData sensitiveFieldName() const final { - return "pwd"_sd; + if (auto customData = cmd.getCustomData(); customData) { + userObjBuilder.append("customData", customData.get()); } -} cmdUpdateUser; - -class CmdDropUser : public BasicCommand { -public: - CmdDropUser() : BasicCommand("dropUser") {} + auto resolvedRoles = auth::resolveRoleNames(cmd.getRoles(), dbname); + userObjBuilder.append("roles", vectorToBSON(resolvedRoles)); + BSONObj userObj = userObjBuilder.obj(); - AllowedOnSecondary secondaryAllowed(ServiceContext*) const override { - return AllowedOnSecondary::kNever; - } + // Validate contents + uassertStatusOK(V2UserDocumentParser().checkValidUserDocument(userObj)); - bool supportsWriteConcern(const BSONObj& cmd) const override { - return true; - } + // Role existence has to be checked after acquiring the update lock + for (const auto& role : cmd.getRoles()) { + BSONObj ignored; + uassertStatusOK( + authzManager->getRoleDescription(opCtx, role.getRoleName(dbname), &ignored)); + } + + // Audit this event. + auto optCustomData = cmd.getCustomData(); + BSONArray authRestrictionsArray; + if (auto ar = cmd.getAuthenticationRestrictions()) { + authRestrictionsArray = vectorToBSON(ar.get()); + } + audit::logCreateUser(opCtx->getClient(), + userName, + cmd.getPwd() != boost::none, + optCustomData ? &(optCustomData.get()) : nullptr, + resolvedRoles, + authRestrictionsArray); + + // Must invalidate even on bad status + auto status = insertPrivilegeDocument(opCtx, userObj); + authzManager->invalidateUserByName(opCtx, userName); + uassertStatusOK(status); +} - std::string help() const override { - return "Drops a single user."; - } +class CmdUpdateUser : public CmdUMCTyped<UpdateUserCommand, void> { +public: + static constexpr StringData kPwdField = "pwd"_sd; - Status checkAuthForCommand(Client* client, - const std::string& dbname, - const BSONObj& cmdObj) const override { - return auth::checkAuthForDropUserCommand(client, dbname, cmdObj); + StringData sensitiveFieldName() const final { + return kPwdField; } +} cmdUpdateUser; - bool run(OperationContext* opCtx, - const std::string& dbname, - const BSONObj& cmdObj, - BSONObjBuilder& result) override { - UserName userName; - Status status = auth::parseAndValidateDropUserCommand(cmdObj, dbname, &userName); - uassertStatusOK(status); - - ServiceContext* serviceContext = opCtx->getClient()->getServiceContext(); - AuthorizationManager* authzManager = AuthorizationManager::get(serviceContext); - - auto lk = uassertStatusOK(requireWritableAuthSchema28SCRAM(opCtx, authzManager)); +template <> +void CmdUMCTyped<UpdateUserCommand, void>::Invocation::typedRun(OperationContext* opCtx) { + const auto& cmd = request(); + const auto& dbname = cmd.getDbName(); + UserName userName(cmd.getCommandParameter(), dbname); - audit::logDropUser(Client::getCurrent(), userName); - - long long nMatched; - status = removePrivilegeDocuments(opCtx, - BSON(AuthorizationManager::USER_NAME_FIELD_NAME - << userName.getUser() - << AuthorizationManager::USER_DB_FIELD_NAME - << userName.getDB()), - &nMatched); - // Must invalidate even on bad status - what if the write succeeded but the GLE failed? - authzManager->invalidateUserByName(opCtx, userName); - uassertStatusOK(status); + uassert(ErrorCodes::BadValue, + "mechanisms field must not be empty", + (cmd.getMechanisms() == boost::none) || !cmd.getMechanisms()->empty()); - if (nMatched == 0) { - uasserted(ErrorCodes::UserNotFound, - str::stream() << "User '" << userName.getFullName() << "' not found"); - } + // Create the update filter (query) object. + BSONObjBuilder queryBuilder; + queryBuilder.append(AuthorizationManager::USER_NAME_FIELD_NAME, userName.getUser()); + queryBuilder.append(AuthorizationManager::USER_DB_FIELD_NAME, userName.getDB()); - return true; - } + // Create set/update mutators. + BSONObjBuilder updateSetBuilder; + BSONObjBuilder updateUnsetBuilder; -} cmdDropUser; + if (auto pwd = cmd.getPwd()) { + uassert(ErrorCodes::BadValue, + "Cannot set the password for users defined on the '$external' database", + userName.getDB() != "$external"); -class CmdDropAllUsersFromDatabase : public BasicCommand { -public: - CmdDropAllUsersFromDatabase() : BasicCommand("dropAllUsersFromDatabase") {} - - AllowedOnSecondary secondaryAllowed(ServiceContext*) const override { - return AllowedOnSecondary::kNever; + BSONObjBuilder credentialsBuilder(updateSetBuilder.subobjStart("credentials")); + buildCredentials(&credentialsBuilder, userName, cmd); + credentialsBuilder.done(); + } else if (auto mechanisms = cmd.getMechanisms()) { + trimCredentials(opCtx, userName, &queryBuilder, &updateUnsetBuilder, mechanisms.get()); } - bool supportsWriteConcern(const BSONObj& cmd) const override { - return true; + if (auto customData = cmd.getCustomData()) { + updateSetBuilder.append("customData", customData.get()); } - std::string help() const override { - return "Drops all users for a single database."; + if (auto ar = cmd.getAuthenticationRestrictions()) { + if (ar->empty()) { + updateUnsetBuilder.append("authenticationRestrictions", ""); + } else { + updateSetBuilder.append("authenticationRestrictions", vectorToBSON(ar.get())); + } } - Status checkAuthForCommand(Client* client, - const std::string& dbname, - const BSONObj& cmdObj) const override { - return auth::checkAuthForDropAllUsersFromDatabaseCommand(client, dbname); + boost::optional<std::vector<RoleName>> optResolvedRoles; + if (auto roles = cmd.getRoles()) { + optResolvedRoles = auth::resolveRoleNames(roles.get(), dbname); + updateSetBuilder.append("roles", vectorToBSON(optResolvedRoles.get())); } - bool run(OperationContext* opCtx, - const std::string& dbname, - const BSONObj& cmdObj, - BSONObjBuilder& result) override { - Status status = auth::parseAndValidateDropAllUsersFromDatabaseCommand(cmdObj, dbname); - uassertStatusOK(status); - ServiceContext* serviceContext = opCtx->getClient()->getServiceContext(); - AuthorizationManager* authzManager = AuthorizationManager::get(serviceContext); - - auto lk = uassertStatusOK(requireWritableAuthSchema28SCRAM(opCtx, authzManager)); - - audit::logDropAllUsersFromDatabase(Client::getCurrent(), dbname); + BSONObj updateSet = updateSetBuilder.done(); + BSONObj updateUnset = updateUnsetBuilder.done(); - long long numRemoved; - status = removePrivilegeDocuments( - opCtx, BSON(AuthorizationManager::USER_DB_FIELD_NAME << dbname), &numRemoved); - // Must invalidate even on bad status - what if the write succeeded but the GLE failed? - authzManager->invalidateUsersFromDB(opCtx, dbname); - uassertStatusOK(status); + uassert(ErrorCodes::BadValue, + "Must specify at least one field to update in updateUser", + !updateSet.isEmpty() || !updateUnset.isEmpty()); - result.append("n", numRemoved); - return true; + // Merge set/update builders into a single update document. + BSONObjBuilder updateDocumentBuilder; + if (!updateSet.isEmpty()) { + updateDocumentBuilder.append("$set", updateSet); + } + if (!updateUnset.isEmpty()) { + updateDocumentBuilder.append("$unset", updateUnset); } -} cmdDropAllUsersFromDatabase; - -class CmdGrantRolesToUser : public BasicCommand { -public: - CmdGrantRolesToUser() : BasicCommand("grantRolesToUser") {} + auto* serviceContext = opCtx->getClient()->getServiceContext(); + auto* authzManager = AuthorizationManager::get(serviceContext); - AllowedOnSecondary secondaryAllowed(ServiceContext*) const override { - return AllowedOnSecondary::kNever; - } + auto lk = uassertStatusOK(requireWritableAuthSchema28SCRAM(opCtx, authzManager)); - bool supportsWriteConcern(const BSONObj& cmd) const override { - return true; + // Role existence has to be checked after acquiring the update lock + if (auto roles = cmd.getRoles()) { + for (const auto& role : roles.get()) { + BSONObj ignored; + uassertStatusOK( + authzManager->getRoleDescription(opCtx, role.getRoleName(dbname), &ignored)); + } } - std::string help() const override { - return "Grants roles to a user."; + // Audit this event. + auto optCustomData = cmd.getCustomData(); + BSONArray authRestrictions; + if (auto ar = cmd.getAuthenticationRestrictions()) { + authRestrictions = vectorToBSON(ar.get()); } + audit::logUpdateUser(opCtx->getClient(), + userName, + cmd.getPwd() != boost::none, + optCustomData ? &(optCustomData.get()) : nullptr, + optResolvedRoles ? &(optResolvedRoles.get()) : nullptr, + authRestrictions); - Status checkAuthForCommand(Client* client, - const std::string& dbname, - const BSONObj& cmdObj) const override { - return auth::checkAuthForGrantRolesToUserCommand(client, dbname, cmdObj); - } + auto status = + updatePrivilegeDocument(opCtx, userName, queryBuilder.done(), updateDocumentBuilder.done()); - bool run(OperationContext* opCtx, - const std::string& dbname, - const BSONObj& cmdObj, - BSONObjBuilder& result) override { - std::string userNameString; - std::vector<RoleName> roles; - Status status = auth::parseRolePossessionManipulationCommands( - cmdObj, "grantRolesToUser", dbname, &userNameString, &roles); - uassertStatusOK(status); + // Must invalidate even on bad status - what if the write succeeded but the GLE failed? + authzManager->invalidateUserByName(opCtx, userName); + uassertStatusOK(status); +} - ServiceContext* serviceContext = opCtx->getClient()->getServiceContext(); - AuthorizationManager* authzManager = AuthorizationManager::get(serviceContext); +CmdUMCTyped<DropUserCommand, void> cmdDropUser; +template <> +void CmdUMCTyped<DropUserCommand, void>::Invocation::typedRun(OperationContext* opCtx) { + const auto& cmd = request(); + const auto& dbname = cmd.getDbName(); + UserName userName(cmd.getCommandParameter(), dbname); - auto lk = uassertStatusOK(requireWritableAuthSchema28SCRAM(opCtx, authzManager)); + auto* serviceContext = opCtx->getClient()->getServiceContext(); + auto* authzManager = AuthorizationManager::get(serviceContext); + auto lk = uassertStatusOK(requireWritableAuthSchema28SCRAM(opCtx, authzManager)); - UserName userName(userNameString, dbname); - stdx::unordered_set<RoleName> userRoles; - status = getCurrentUserRoles(opCtx, authzManager, userName, &userRoles); - uassertStatusOK(status); + audit::logDropUser(Client::getCurrent(), userName); - for (std::vector<RoleName>::iterator it = roles.begin(); it != roles.end(); ++it) { - RoleName& roleName = *it; - BSONObj roleDoc; - status = authzManager->getRoleDescription(opCtx, roleName, &roleDoc); - uassertStatusOK(status); + std::int64_t numMatched; + auto status = removePrivilegeDocuments( + opCtx, + BSON(AuthorizationManager::USER_NAME_FIELD_NAME + << userName.getUser() << AuthorizationManager::USER_DB_FIELD_NAME << userName.getDB()), + &numMatched); - userRoles.insert(roleName); - } + // Must invalidate even on bad status - what if the write succeeded but the GLE failed? + authzManager->invalidateUserByName(opCtx, userName); + uassertStatusOK(status); - audit::logGrantRolesToUser(Client::getCurrent(), userName, roles); - BSONArray newRolesBSONArray = roleSetToBSONArray(userRoles); - status = updatePrivilegeDocument( - opCtx, userName, BSON("$set" << BSON("roles" << newRolesBSONArray))); - // Must invalidate even on bad status - what if the write succeeded but the GLE failed? - authzManager->invalidateUserByName(opCtx, userName); - uassertStatusOK(status); - return true; - } + uassert(ErrorCodes::UserNotFound, + str::stream() << "User '" << userName.getFullName() << "' not found", + numMatched > 0); +} -} cmdGrantRolesToUser; +CmdUMCTyped<DropAllUsersFromDatabaseCommand, DropAllUsersFromDatabaseReply> + cmdDropAllUsersFromDatabase; +template <> +DropAllUsersFromDatabaseReply +CmdUMCTyped<DropAllUsersFromDatabaseCommand, DropAllUsersFromDatabaseReply>::Invocation::typedRun( + OperationContext* opCtx) { + const auto& cmd = request(); + const auto& dbname = cmd.getDbName(); + + auto* client = opCtx->getClient(); + auto* serviceContext = client->getServiceContext(); + auto* authzManager = AuthorizationManager::get(serviceContext); + auto lk = uassertStatusOK(requireWritableAuthSchema28SCRAM(opCtx, authzManager)); + + audit::logDropAllUsersFromDatabase(client, dbname); + + std::int64_t numRemoved; + auto status = removePrivilegeDocuments( + opCtx, BSON(AuthorizationManager::USER_DB_FIELD_NAME << dbname), &numRemoved); + + // Must invalidate even on bad status - what if the write succeeded but the GLE failed? + authzManager->invalidateUsersFromDB(opCtx, dbname); + uassertStatusOK(status); + + DropAllUsersFromDatabaseReply reply; + reply.setCount(numRemoved); + return reply; +} -class CmdRevokeRolesFromUser : public BasicCommand { -public: - CmdRevokeRolesFromUser() : BasicCommand("revokeRolesFromUser") {} +CmdUMCTyped<GrantRolesToUserCommand, void> cmdGrantRolesToUser; +template <> +void CmdUMCTyped<GrantRolesToUserCommand, void>::Invocation::typedRun(OperationContext* opCtx) { + const auto& cmd = request(); + const auto& dbname = cmd.getDbName(); + UserName userName(cmd.getCommandParameter(), dbname); - AllowedOnSecondary secondaryAllowed(ServiceContext*) const override { - return AllowedOnSecondary::kNever; - } + uassert(ErrorCodes::BadValue, + "grantRolesToUser command requires a non-empty \"roles\" array", + !cmd.getRoles().empty()); - bool supportsWriteConcern(const BSONObj& cmd) const override { - return true; - } + auto* client = opCtx->getClient(); + auto* serviceContext = client->getServiceContext(); + auto* authzManager = AuthorizationManager::get(serviceContext); + auto lk = uassertStatusOK(requireWritableAuthSchema28SCRAM(opCtx, authzManager)); - std::string help() const override { - return "Revokes roles from a user."; - } + stdx::unordered_set<RoleName> userRoles; + uassertStatusOK(getCurrentUserRoles(opCtx, authzManager, userName, &userRoles)); - Status checkAuthForCommand(Client* client, - const std::string& dbname, - const BSONObj& cmdObj) const override { - return auth::checkAuthForRevokeRolesFromUserCommand(client, dbname, cmdObj); + auto resolvedRoleNames = auth::resolveRoleNames(cmd.getRoles(), dbname); + for (const auto& role : resolvedRoleNames) { + BSONObj roleDoc; + uassertStatusOK(authzManager->getRoleDescription(opCtx, role, &roleDoc)); + userRoles.insert(role); } - bool run(OperationContext* opCtx, - const std::string& dbname, - const BSONObj& cmdObj, - BSONObjBuilder& result) override { - std::string userNameString; - std::vector<RoleName> roles; - Status status = auth::parseRolePossessionManipulationCommands( - cmdObj, "revokeRolesFromUser", dbname, &userNameString, &roles); - uassertStatusOK(status); + audit::logGrantRolesToUser(client, userName, resolvedRoleNames); + auto newRolesBSONArray = roleSetToBSONArray(userRoles); + auto status = updatePrivilegeDocument( + opCtx, userName, BSON("$set" << BSON("roles" << newRolesBSONArray))); - ServiceContext* serviceContext = opCtx->getClient()->getServiceContext(); - AuthorizationManager* authzManager = AuthorizationManager::get(serviceContext); + // Must invalidate even on bad status - what if the write succeeded but the GLE failed? + authzManager->invalidateUserByName(opCtx, userName); + uassertStatusOK(status); +} - auto lk = uassertStatusOK(requireWritableAuthSchema28SCRAM(opCtx, authzManager)); +CmdUMCTyped<RevokeRolesFromUserCommand, void> cmdRevokeRolesFromUser; +template <> +void CmdUMCTyped<RevokeRolesFromUserCommand, void>::Invocation::typedRun(OperationContext* opCtx) { + const auto& cmd = request(); + const auto& dbname = cmd.getDbName(); + UserName userName(cmd.getCommandParameter(), dbname); - UserName userName(userNameString, dbname); - stdx::unordered_set<RoleName> userRoles; - status = getCurrentUserRoles(opCtx, authzManager, userName, &userRoles); - uassertStatusOK(status); + uassert(ErrorCodes::BadValue, + "revokeRolesFromUser command requires a non-empty \"roles\" array", + !cmd.getRoles().empty()); - for (std::vector<RoleName>::iterator it = roles.begin(); it != roles.end(); ++it) { - RoleName& roleName = *it; - BSONObj roleDoc; - status = authzManager->getRoleDescription(opCtx, roleName, &roleDoc); - uassertStatusOK(status); + auto* client = opCtx->getClient(); + auto* serviceContext = client->getServiceContext(); + auto* authzManager = AuthorizationManager::get(serviceContext); + auto lk = uassertStatusOK(requireWritableAuthSchema28SCRAM(opCtx, authzManager)); - userRoles.erase(roleName); - } + stdx::unordered_set<RoleName> userRoles; + uassertStatusOK(getCurrentUserRoles(opCtx, authzManager, userName, &userRoles)); - audit::logRevokeRolesFromUser(Client::getCurrent(), userName, roles); - BSONArray newRolesBSONArray = roleSetToBSONArray(userRoles); - status = updatePrivilegeDocument( - opCtx, userName, BSON("$set" << BSON("roles" << newRolesBSONArray))); - // Must invalidate even on bad status - what if the write succeeded but the GLE failed? - authzManager->invalidateUserByName(opCtx, userName); - uassertStatusOK(status); - return true; + auto resolvedUserRoles = auth::resolveRoleNames(cmd.getRoles(), dbname); + for (const auto& role : resolvedUserRoles) { + BSONObj roleDoc; + uassertStatusOK(authzManager->getRoleDescription(opCtx, role, &roleDoc)); + userRoles.erase(role); } -} cmdRevokeRolesFromUser; + audit::logRevokeRolesFromUser(client, userName, resolvedUserRoles); + BSONArray newRolesBSONArray = roleSetToBSONArray(userRoles); + auto status = updatePrivilegeDocument( + opCtx, userName, BSON("$set" << BSON("roles" << newRolesBSONArray))); + + // Must invalidate even on bad status - what if the write succeeded but the GLE failed? + authzManager->invalidateUserByName(opCtx, userName); + uassertStatusOK(status); +} class CmdUsersInfo : public BasicCommand { public: @@ -1396,777 +1301,515 @@ public: } cmdUsersInfo; -class CmdCreateRole : public BasicCommand { -public: - CmdCreateRole() : BasicCommand("createRole") {} - - AllowedOnSecondary secondaryAllowed(ServiceContext*) const override { - return AllowedOnSecondary::kNever; - } - - bool supportsWriteConcern(const BSONObj& cmd) const override { - return true; - } - - std::string help() const override { - return "Adds a role to the system"; - } - - Status checkAuthForCommand(Client* client, - const std::string& dbname, - const BSONObj& cmdObj) const override { - return auth::checkAuthForCreateRoleCommand(client, dbname, cmdObj); - } - - bool run(OperationContext* opCtx, - const std::string& dbname, - const BSONObj& cmdObj, - BSONObjBuilder& result) override { - auth::CreateOrUpdateRoleArgs args; - Status status = auth::parseCreateOrUpdateRoleCommands(cmdObj, "createRole", dbname, &args); - uassertStatusOK(status); - - if (args.roleName.getRole().empty()) { - uasserted(ErrorCodes::BadValue, "Role name must be non-empty"); - } - - if (args.roleName.getDB() == "local") { - uasserted(ErrorCodes::BadValue, "Cannot create roles in the local database"); - } - - if (args.roleName.getDB() == "$external") { - uasserted(ErrorCodes::BadValue, "Cannot create roles in the $external database"); - } - - if (RoleGraph::isBuiltinRole(args.roleName)) { - uasserted(ErrorCodes::BadValue, - "Cannot create roles with the same name as a built-in role"); - } - - if (!args.hasRoles) { - uasserted(ErrorCodes::BadValue, "\"createRole\" command requires a \"roles\" array"); - } - - if (!args.hasPrivileges) { - uasserted(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); - uassertStatusOK(status); - roleObjBuilder.append("privileges", privileges); - - roleObjBuilder.append("roles", rolesVectorToBSONArray(args.roles)); - - if (args.authenticationRestrictions && !args.authenticationRestrictions->isEmpty()) { - roleObjBuilder.append("authenticationRestrictions", - args.authenticationRestrictions.get()); - } - - ServiceContext* serviceContext = opCtx->getClient()->getServiceContext(); - - AuthorizationManager* authzManager = AuthorizationManager::get(serviceContext); - auto lk = uassertStatusOK(requireWritableAuthSchema28SCRAM(opCtx, authzManager)); - - // Role existence has to be checked after acquiring the update lock - status = checkOkayToGrantRolesToRole(opCtx, args.roleName, args.roles, authzManager); - uassertStatusOK(status); - - status = checkOkayToGrantPrivilegesToRole(args.roleName, args.privileges); - uassertStatusOK(status); +CmdUMCTyped<CreateRoleCommand, void> cmdCreateRole; +template <> +void CmdUMCTyped<CreateRoleCommand, void>::Invocation::typedRun(OperationContext* opCtx) { + const auto& cmd = request(); + const auto& dbname = cmd.getDbName(); - audit::logCreateRole(Client::getCurrent(), - args.roleName, - args.roles, - args.privileges, - args.authenticationRestrictions); + uassert( + ErrorCodes::BadValue, "Role name must be non-empty", !cmd.getCommandParameter().empty()); + RoleName roleName(cmd.getCommandParameter(), dbname); - status = insertRoleDocument(opCtx, roleObjBuilder.done()); - uassertStatusOK(status); - return true; - } + uassert(ErrorCodes::BadValue, "Cannot create roles in the local database", dbname != "local"); -} cmdCreateRole; + uassert(ErrorCodes::BadValue, + "Cannot create roles in the $external database", + dbname != "$external"); -class CmdUpdateRole : public BasicCommand { -public: - CmdUpdateRole() : BasicCommand("updateRole") {} + uassert(ErrorCodes::BadValue, + "Cannot create roles with the same name as a built-in role", + !RoleGraph::isBuiltinRole(roleName)); - AllowedOnSecondary secondaryAllowed(ServiceContext*) const override { - return AllowedOnSecondary::kNever; - } + BSONObjBuilder roleObjBuilder; + roleObjBuilder.append("_id", str::stream() << roleName.getDB() << "." << roleName.getRole()); + roleObjBuilder.append(AuthorizationManager::ROLE_NAME_FIELD_NAME, roleName.getRole()); + roleObjBuilder.append(AuthorizationManager::ROLE_DB_FIELD_NAME, roleName.getDB()); - bool supportsWriteConcern(const BSONObj& cmd) const override { - return true; - } + BSONArray privileges; + uassertStatusOK(privilegeVectorToBSONArray(cmd.getPrivileges(), &privileges)); + roleObjBuilder.append("privileges", privileges); - std::string help() const override { - return "Used to update a role"; - } + auto resolvedRoleNames = auth::resolveRoleNames(cmd.getRoles(), dbname); + roleObjBuilder.append("roles", rolesVectorToBSONArray(resolvedRoleNames)); - Status checkAuthForCommand(Client* client, - const std::string& dbname, - const BSONObj& cmdObj) const override { - return auth::checkAuthForUpdateRoleCommand(client, dbname, cmdObj); + boost::optional<BSONArray> bsonAuthRestrictions; + if (auto ar = cmd.getAuthenticationRestrictions(); ar && !ar->empty()) { + bsonAuthRestrictions = vectorToBSON(ar.get()); + roleObjBuilder.append("authenticationRestrictions", bsonAuthRestrictions.get()); } - bool run(OperationContext* opCtx, - const std::string& dbname, - const BSONObj& cmdObj, - BSONObjBuilder& result) override { - auth::CreateOrUpdateRoleArgs args; - Status status = auth::parseCreateOrUpdateRoleCommands(cmdObj, "updateRole", dbname, &args); - uassertStatusOK(status); - - if (!args.hasPrivileges && !args.hasRoles && !args.authenticationRestrictions) { - uasserted(ErrorCodes::BadValue, - "Must specify at least one field to update in updateRole"); - } + auto* client = opCtx->getClient(); + auto* serviceContext = client->getServiceContext(); + auto* authzManager = AuthorizationManager::get(serviceContext); + auto lk = uassertStatusOK(requireWritableAuthSchema28SCRAM(opCtx, authzManager)); - BSONObjBuilder updateSetBuilder; - BSONObjBuilder updateUnsetBuilder; - - if (args.hasPrivileges) { - BSONArray privileges; - status = privilegeVectorToBSONArray(args.privileges, &privileges); - uassertStatusOK(status); - updateSetBuilder.append("privileges", privileges); - } - - if (args.hasRoles) { - updateSetBuilder.append("roles", rolesVectorToBSONArray(args.roles)); - } + // Role existence has to be checked after acquiring the update lock + uassertStatusOK(checkOkayToGrantRolesToRole(opCtx, roleName, resolvedRoleNames, authzManager)); + uassertStatusOK(checkOkayToGrantPrivilegesToRole(roleName, cmd.getPrivileges())); - if (args.authenticationRestrictions) { - if (args.authenticationRestrictions->isEmpty()) { - updateUnsetBuilder.append("authenticationRestrictions", ""); - } else { - updateSetBuilder.append("authenticationRestrictions", - args.authenticationRestrictions.get()); - } - } - - ServiceContext* serviceContext = opCtx->getClient()->getServiceContext(); - AuthorizationManager* authzManager = AuthorizationManager::get(serviceContext); - - auto lk = uassertStatusOK(requireWritableAuthSchema28SCRAM(opCtx, authzManager)); - - // Role existence has to be checked after acquiring the update lock - BSONObj ignored; - status = authzManager->getRoleDescription(opCtx, args.roleName, &ignored); - uassertStatusOK(status); - - if (args.hasRoles) { - status = checkOkayToGrantRolesToRole(opCtx, args.roleName, args.roles, authzManager); - uassertStatusOK(status); - } - - if (args.hasPrivileges) { - status = checkOkayToGrantPrivilegesToRole(args.roleName, args.privileges); - uassertStatusOK(status); - } - - audit::logUpdateRole(Client::getCurrent(), - args.roleName, - args.hasRoles ? &args.roles : nullptr, - args.hasPrivileges ? &args.privileges : nullptr, - args.authenticationRestrictions); - - const auto updateSet = updateSetBuilder.obj(); - const auto updateUnset = updateUnsetBuilder.obj(); - BSONObjBuilder updateDocumentBuilder; - if (!updateSet.isEmpty()) { - updateDocumentBuilder.append("$set", updateSet); - } - if (!updateUnset.isEmpty()) { - updateDocumentBuilder.append("$unset", updateUnset); - } + audit::logCreateRole( + client, roleName, resolvedRoleNames, cmd.getPrivileges(), bsonAuthRestrictions); - status = updateRoleDocument(opCtx, args.roleName, updateDocumentBuilder.obj()); - // Must invalidate even on bad status - what if the write succeeded but the GLE failed? - authzManager->invalidateUserCache(opCtx); - uassertStatusOK(status); - return true; - } - -} cmdUpdateRole; + uassertStatusOK(insertRoleDocument(opCtx, roleObjBuilder.done())); +} -class CmdGrantPrivilegesToRole : public BasicCommand { -public: - CmdGrantPrivilegesToRole() : BasicCommand("grantPrivilegesToRole") {} +CmdUMCTyped<UpdateRoleCommand, void> cmdUpdateRole; +template <> +void CmdUMCTyped<UpdateRoleCommand, void>::Invocation::typedRun(OperationContext* opCtx) { + const auto& cmd = request(); + const auto& dbname = cmd.getDbName(); + RoleName roleName(cmd.getCommandParameter(), dbname); - AllowedOnSecondary secondaryAllowed(ServiceContext*) const override { - return AllowedOnSecondary::kNever; - } + const bool hasRoles = cmd.getRoles() != boost::none; + const bool hasPrivs = cmd.getPrivileges() != boost::none; + const bool hasAuthRestrictions = cmd.getAuthenticationRestrictions() != boost::none; + uassert(ErrorCodes::BadValue, + "Must specify at least one field to update in updateRole", + hasRoles || hasPrivs || hasAuthRestrictions); - bool supportsWriteConcern(const BSONObj& cmd) const override { - return true; - } + BSONObjBuilder updateSetBuilder; + BSONObjBuilder updateUnsetBuilder; - std::string help() const override { - return "Grants privileges to a role"; + if (auto privs = cmd.getPrivileges()) { + BSONArray privileges; + uassertStatusOK(privilegeVectorToBSONArray(privs.get(), &privileges)); + updateSetBuilder.append("privileges", privileges); } - Status checkAuthForCommand(Client* client, - const std::string& dbname, - const BSONObj& cmdObj) const override { - return auth::checkAuthForGrantPrivilegesToRoleCommand(client, dbname, cmdObj); + boost::optional<std::vector<RoleName>> optRoles; + if (auto roles = cmd.getRoles()) { + optRoles = auth::resolveRoleNames(roles.get(), dbname); + updateSetBuilder.append("roles", rolesVectorToBSONArray(*optRoles)); } - bool run(OperationContext* opCtx, - const std::string& dbname, - const BSONObj& cmdObj, - BSONObjBuilder& result) override { - RoleName roleName; - PrivilegeVector privilegesToAdd; - Status status = auth::parseAndValidateRolePrivilegeManipulationCommands( - cmdObj, "grantPrivilegesToRole", dbname, &roleName, &privilegesToAdd); - uassertStatusOK(status); - - ServiceContext* serviceContext = opCtx->getClient()->getServiceContext(); - AuthorizationManager* authzManager = AuthorizationManager::get(serviceContext); - - auto lk = uassertStatusOK(requireWritableAuthSchema28SCRAM(opCtx, authzManager)); - - if (RoleGraph::isBuiltinRole(roleName)) { - uasserted(ErrorCodes::InvalidRoleModification, - str::stream() << roleName.getFullName() - << " is a built-in role and cannot be modified."); - } - - status = checkOkayToGrantPrivilegesToRole(roleName, privilegesToAdd); - uassertStatusOK(status); - - BSONObj roleDoc; - status = authzManager->getRoleDescription(opCtx, - roleName, - PrivilegeFormat::kShowSeparate, - AuthenticationRestrictionsFormat::kOmit, - &roleDoc); - uassertStatusOK(status); - - PrivilegeVector privileges; - status = auth::parseAndValidatePrivilegeArray(BSONArray(roleDoc["privileges"].Obj()), - &privileges); - - uassertStatusOK(status); - - for (PrivilegeVector::iterator it = privilegesToAdd.begin(); it != privilegesToAdd.end(); - ++it) { - Privilege::addPrivilegeToPrivilegeVector(&privileges, *it); + BSONArray authRest; + if (auto ar = cmd.getAuthenticationRestrictions()) { + if (ar->empty()) { + updateUnsetBuilder.append("authenticationRestrictions", ""); + } else { + authRest = vectorToBSON(ar.get()); + updateSetBuilder.append("authenticationRestrictions", authRest); } - - // Build up update modifier object to $set privileges. - mutablebson::Document updateObj; - mutablebson::Element setElement = updateObj.makeElementObject("$set"); - status = updateObj.root().pushBack(setElement); - uassertStatusOK(status); - mutablebson::Element privilegesElement = updateObj.makeElementArray("privileges"); - status = setElement.pushBack(privilegesElement); - uassertStatusOK(status); - status = Privilege::getBSONForPrivileges(privileges, privilegesElement); - uassertStatusOK(status); - - BSONObjBuilder updateBSONBuilder; - updateObj.writeTo(&updateBSONBuilder); - - audit::logGrantPrivilegesToRole(Client::getCurrent(), roleName, privilegesToAdd); - - status = updateRoleDocument(opCtx, roleName, updateBSONBuilder.done()); - // Must invalidate even on bad status - what if the write succeeded but the GLE failed? - authzManager->invalidateUserCache(opCtx); - uassertStatusOK(status); - return true; } -} cmdGrantPrivilegesToRole; + auto* client = opCtx->getClient(); + auto* serviceContext = client->getServiceContext(); + auto* authzManager = AuthorizationManager::get(serviceContext); + auto lk = uassertStatusOK(requireWritableAuthSchema28SCRAM(opCtx, authzManager)); -class CmdRevokePrivilegesFromRole : public BasicCommand { -public: - CmdRevokePrivilegesFromRole() : BasicCommand("revokePrivilegesFromRole") {} + // Role existence has to be checked after acquiring the update lock + BSONObj ignored; + uassertStatusOK(authzManager->getRoleDescription(opCtx, roleName, &ignored)); - AllowedOnSecondary secondaryAllowed(ServiceContext*) const override { - return AllowedOnSecondary::kNever; + if (optRoles) { + uassertStatusOK(checkOkayToGrantRolesToRole(opCtx, roleName, *optRoles, authzManager)); } - bool supportsWriteConcern(const BSONObj& cmd) const override { - return true; + auto privs = cmd.getPrivileges(); + if (privs) { + uassertStatusOK(checkOkayToGrantPrivilegesToRole(roleName, privs.get())); } - std::string help() const override { - return "Revokes privileges from a role"; - } + audit::logUpdateRole( + client, roleName, optRoles ? &*optRoles : nullptr, privs ? &*privs : nullptr, authRest); - Status checkAuthForCommand(Client* client, - const std::string& dbname, - const BSONObj& cmdObj) const override { - return auth::checkAuthForRevokePrivilegesFromRoleCommand(client, dbname, cmdObj); + const auto updateSet = updateSetBuilder.obj(); + const auto updateUnset = updateUnsetBuilder.obj(); + BSONObjBuilder updateDocumentBuilder; + if (!updateSet.isEmpty()) { + updateDocumentBuilder.append("$set", updateSet); + } + if (!updateUnset.isEmpty()) { + updateDocumentBuilder.append("$unset", updateUnset); } - bool run(OperationContext* opCtx, - const std::string& dbname, - const BSONObj& cmdObj, - BSONObjBuilder& result) override { - RoleName roleName; - PrivilegeVector privilegesToRemove; - Status status = auth::parseAndValidateRolePrivilegeManipulationCommands( - cmdObj, "revokePrivilegesFromRole", dbname, &roleName, &privilegesToRemove); - uassertStatusOK(status); - - ServiceContext* serviceContext = opCtx->getClient()->getServiceContext(); - AuthorizationManager* authzManager = AuthorizationManager::get(serviceContext); - auto lk = uassertStatusOK(requireWritableAuthSchema28SCRAM(opCtx, authzManager)); - - if (RoleGraph::isBuiltinRole(roleName)) { - uasserted(ErrorCodes::InvalidRoleModification, - str::stream() << roleName.getFullName() - << " is a built-in role and cannot be modified."); - } - - BSONObj roleDoc; - status = authzManager->getRoleDescription(opCtx, - roleName, - PrivilegeFormat::kShowSeparate, - AuthenticationRestrictionsFormat::kOmit, - &roleDoc); - uassertStatusOK(status); + auto status = updateRoleDocument(opCtx, roleName, updateDocumentBuilder.obj()); + // Must invalidate even on bad status - what if the write succeeded but the GLE failed? + authzManager->invalidateUserCache(opCtx); + uassertStatusOK(status); +} - PrivilegeVector privileges; - status = auth::parseAndValidatePrivilegeArray(BSONArray(roleDoc["privileges"].Obj()), - &privileges); - uassertStatusOK(status); +CmdUMCTyped<GrantPrivilegesToRoleCommand, void> cmdGrantPrivilegesToRole; +template <> +void CmdUMCTyped<GrantPrivilegesToRoleCommand, void>::Invocation::typedRun( + OperationContext* opCtx) { + const auto& cmd = request(); + const auto& dbname = cmd.getDbName(); + RoleName roleName(cmd.getCommandParameter(), dbname); + + uassert(ErrorCodes::BadValue, + "grantPrivilegesToRole command requires a non-empty \"privileges\" array", + !cmd.getPrivileges().empty()); + + uassert(ErrorCodes::BadValue, + str::stream() << roleName.getFullName() << " is a built-in role and cannot be modified", + !RoleGraph::isBuiltinRole(roleName)); + + auto* client = opCtx->getClient(); + auto* serviceContext = client->getServiceContext(); + auto* authzManager = AuthorizationManager::get(serviceContext); + auto lk = uassertStatusOK(requireWritableAuthSchema28SCRAM(opCtx, authzManager)); + + uassertStatusOK(checkOkayToGrantPrivilegesToRole(roleName, cmd.getPrivileges())); + + BSONObj roleDoc; + uassertStatusOK(authzManager->getRoleDescription(opCtx, + roleName, + PrivilegeFormat::kShowSeparate, + AuthenticationRestrictionsFormat::kOmit, + &roleDoc)); + + PrivilegeVector privileges; + uassertStatusOK( + auth::parseAndValidatePrivilegeArray(BSONArray(roleDoc["privileges"].Obj()), &privileges)); + + for (const auto& priv : cmd.getPrivileges()) { + Privilege::addPrivilegeToPrivilegeVector(&privileges, priv); + } + + // Build up update modifier object to $set privileges. + mutablebson::Document updateObj; + mutablebson::Element setElement = updateObj.makeElementObject("$set"); + uassertStatusOK(updateObj.root().pushBack(setElement)); + mutablebson::Element privilegesElement = updateObj.makeElementArray("privileges"); + uassertStatusOK(setElement.pushBack(privilegesElement)); + uassertStatusOK(Privilege::getBSONForPrivileges(privileges, privilegesElement)); + + BSONObjBuilder updateBSONBuilder; + updateObj.writeTo(&updateBSONBuilder); + + audit::logGrantPrivilegesToRole(client, roleName, cmd.getPrivileges()); + + auto status = updateRoleDocument(opCtx, roleName, updateBSONBuilder.done()); + // Must invalidate even on bad status - what if the write succeeded but the GLE failed? + authzManager->invalidateUserCache(opCtx); + uassertStatusOK(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); - } +CmdUMCTyped<RevokePrivilegesFromRoleCommand, void> cmdRevokePrivilegesFromRole; +template <> +void CmdUMCTyped<RevokePrivilegesFromRoleCommand, void>::Invocation::typedRun( + OperationContext* opCtx) { + const auto& cmd = request(); + const auto& dbname = cmd.getDbName(); + RoleName roleName(cmd.getCommandParameter(), dbname); + + uassert(ErrorCodes::BadValue, + "revokePrivilegesFromRole command requires a non-empty \"privileges\" array", + !cmd.getPrivileges().empty()); + + uassert(ErrorCodes::BadValue, + str::stream() << roleName.getFullName() << " is a built-in role and cannot be modified", + !RoleGraph::isBuiltinRole(roleName)); + + auto* client = opCtx->getClient(); + auto* serviceContext = client->getServiceContext(); + auto* authzManager = AuthorizationManager::get(serviceContext); + auto lk = uassertStatusOK(requireWritableAuthSchema28SCRAM(opCtx, authzManager)); + + BSONObj roleDoc; + uassertStatusOK(authzManager->getRoleDescription(opCtx, + roleName, + PrivilegeFormat::kShowSeparate, + AuthenticationRestrictionsFormat::kOmit, + &roleDoc)); + + PrivilegeVector privileges; + uassertStatusOK( + auth::parseAndValidatePrivilegeArray(BSONArray(roleDoc["privileges"].Obj()), &privileges)); + + for (const auto& rmPriv : cmd.getPrivileges()) { + for (auto it = privileges.begin(); it != privileges.end(); ++it) { + if (it->getResourcePattern() == rmPriv.getResourcePattern()) { + it->removeActions(rmPriv.getActions()); + if (it->getActions().empty()) { + privileges.erase(it); break; } } } - - // Build up update modifier object to $set privileges. - mutablebson::Document updateObj; - mutablebson::Element setElement = updateObj.makeElementObject("$set"); - status = updateObj.root().pushBack(setElement); - uassertStatusOK(status); - mutablebson::Element privilegesElement = updateObj.makeElementArray("privileges"); - status = setElement.pushBack(privilegesElement); - uassertStatusOK(status); - status = Privilege::getBSONForPrivileges(privileges, privilegesElement); - uassertStatusOK(status); - - audit::logRevokePrivilegesFromRole(Client::getCurrent(), roleName, privilegesToRemove); - - BSONObjBuilder updateBSONBuilder; - updateObj.writeTo(&updateBSONBuilder); - status = updateRoleDocument(opCtx, roleName, updateBSONBuilder.done()); - // Must invalidate even on bad status - what if the write succeeded but the GLE failed? - authzManager->invalidateUserCache(opCtx); - uassertStatusOK(status); - return true; } -} cmdRevokePrivilegesFromRole; - -class CmdGrantRolesToRole : public BasicCommand { -public: - CmdGrantRolesToRole() : BasicCommand("grantRolesToRole") {} + // Build up update modifier object to $set privileges. + mutablebson::Document updateObj; + mutablebson::Element setElement = updateObj.makeElementObject("$set"); + uassertStatusOK(updateObj.root().pushBack(setElement)); + mutablebson::Element privilegesElement = updateObj.makeElementArray("privileges"); + uassertStatusOK(setElement.pushBack(privilegesElement)); + uassertStatusOK(Privilege::getBSONForPrivileges(privileges, privilegesElement)); - AllowedOnSecondary secondaryAllowed(ServiceContext*) const override { - return AllowedOnSecondary::kNever; - } + audit::logRevokePrivilegesFromRole(client, roleName, cmd.getPrivileges()); - bool supportsWriteConcern(const BSONObj& cmd) const override { - return true; - } + BSONObjBuilder updateBSONBuilder; + updateObj.writeTo(&updateBSONBuilder); - std::string help() const override { - return "Grants roles to another role."; - } + auto status = updateRoleDocument(opCtx, roleName, updateBSONBuilder.done()); + // Must invalidate even on bad status - what if the write succeeded but the GLE failed? + authzManager->invalidateUserCache(opCtx); + uassertStatusOK(status); +} - Status checkAuthForCommand(Client* client, - const std::string& dbname, - const BSONObj& cmdObj) const override { - return auth::checkAuthForGrantRolesToRoleCommand(client, dbname, cmdObj); - } +CmdUMCTyped<GrantRolesToRoleCommand, void> cmdGrantRolesToRole; +template <> +void CmdUMCTyped<GrantRolesToRoleCommand, void>::Invocation::typedRun(OperationContext* opCtx) { + const auto& cmd = request(); + const auto& dbname = cmd.getDbName(); + RoleName roleName(cmd.getCommandParameter(), dbname); - bool run(OperationContext* opCtx, - const std::string& dbname, - const BSONObj& cmdObj, - BSONObjBuilder& result) override { - std::string roleNameString; - std::vector<RoleName> rolesToAdd; - Status status = auth::parseRolePossessionManipulationCommands( - cmdObj, "grantRolesToRole", dbname, &roleNameString, &rolesToAdd); - uassertStatusOK(status); + uassert(ErrorCodes::BadValue, + "grantRolesToRole command requires a non-empty \"roles\" array", + !cmd.getRoles().empty()); - RoleName roleName(roleNameString, dbname); - if (RoleGraph::isBuiltinRole(roleName)) { - uasserted(ErrorCodes::InvalidRoleModification, - str::stream() << roleName.getFullName() - << " is a built-in role and cannot be modified."); - } + uassert(ErrorCodes::BadValue, + str::stream() << roleName.getFullName() << " is a built-in role and cannot be modified", + !RoleGraph::isBuiltinRole(roleName)); - ServiceContext* serviceContext = opCtx->getClient()->getServiceContext(); - AuthorizationManager* authzManager = AuthorizationManager::get(serviceContext); + auto rolesToAdd = auth::resolveRoleNames(cmd.getRoles(), dbname); - auto lk = uassertStatusOK(requireWritableAuthSchema28SCRAM(opCtx, authzManager)); + auto* client = opCtx->getClient(); + auto* serviceContext = client->getServiceContext(); + auto* authzManager = AuthorizationManager::get(serviceContext); + auto lk = uassertStatusOK(requireWritableAuthSchema28SCRAM(opCtx, authzManager)); - // Role existence has to be checked after acquiring the update lock - BSONObj roleDoc; - status = authzManager->getRoleDescription(opCtx, roleName, &roleDoc); - uassertStatusOK(status); + // Role existence has to be checked after acquiring the update lock + BSONObj roleDoc; + uassertStatusOK(authzManager->getRoleDescription(opCtx, roleName, &roleDoc)); - // Check for cycles - status = checkOkayToGrantRolesToRole(opCtx, roleName, rolesToAdd, authzManager); - uassertStatusOK(status); + // Check for cycles + uassertStatusOK(checkOkayToGrantRolesToRole(opCtx, roleName, rolesToAdd, authzManager)); - // Add new roles to existing roles - std::vector<RoleName> directRoles; - status = auth::parseRoleNamesFromBSONArray( - BSONArray(roleDoc["roles"].Obj()), roleName.getDB(), &directRoles); - uassertStatusOK(status); - for (auto it = rolesToAdd.begin(); it != rolesToAdd.end(); ++it) { - const RoleName& roleToAdd = *it; - if (!sequenceContains(directRoles, roleToAdd)) // Don't double-add role - directRoles.push_back(*it); + // Add new roles to existing roles + std::vector<RoleName> directRoles; + uassertStatusOK(auth::parseRoleNamesFromBSONArray( + BSONArray(roleDoc["roles"].Obj()), roleName.getDB(), &directRoles)); + for (const auto& roleToAdd : rolesToAdd) { + if (!sequenceContains(directRoles, roleToAdd)) { + // Don't double-add role + directRoles.push_back(roleToAdd); } - - audit::logGrantRolesToRole(Client::getCurrent(), roleName, rolesToAdd); - - status = updateRoleDocument( - opCtx, roleName, BSON("$set" << BSON("roles" << rolesVectorToBSONArray(directRoles)))); - // Must invalidate even on bad status - what if the write succeeded but the GLE failed? - authzManager->invalidateUserCache(opCtx); - uassertStatusOK(status); - return true; } -} cmdGrantRolesToRole; + audit::logGrantRolesToRole(client, roleName, rolesToAdd); -class CmdRevokeRolesFromRole : public BasicCommand { -public: - CmdRevokeRolesFromRole() : BasicCommand("revokeRolesFromRole") {} + auto status = updateRoleDocument( + opCtx, roleName, BSON("$set" << BSON("roles" << rolesVectorToBSONArray(directRoles)))); + // Must invalidate even on bad status - what if the write succeeded but the GLE failed? + authzManager->invalidateUserCache(opCtx); + uassertStatusOK(status); +} - AllowedOnSecondary secondaryAllowed(ServiceContext*) const override { - return AllowedOnSecondary::kNever; - } +CmdUMCTyped<RevokeRolesFromRoleCommand, void> cmdRevokeRolesFromRole; +template <> +void CmdUMCTyped<RevokeRolesFromRoleCommand, void>::Invocation::typedRun(OperationContext* opCtx) { + const auto& cmd = request(); + const auto& dbname = cmd.getDbName(); + RoleName roleName(cmd.getCommandParameter(), dbname); - bool supportsWriteConcern(const BSONObj& cmd) const override { - return true; - } + uassert(ErrorCodes::BadValue, + "revokeRolesFromRole command requires a non-empty \"roles\" array", + !cmd.getRoles().empty()); - std::string help() const override { - return "Revokes roles from another role."; - } + uassert(ErrorCodes::BadValue, + str::stream() << roleName.getFullName() << " is a built-in role and cannot be modified", + !RoleGraph::isBuiltinRole(roleName)); - Status checkAuthForCommand(Client* client, - const std::string& dbname, - const BSONObj& cmdObj) const override { - return auth::checkAuthForRevokeRolesFromRoleCommand(client, dbname, cmdObj); - } + auto rolesToRemove = auth::resolveRoleNames(cmd.getRoles(), dbname); - bool run(OperationContext* opCtx, - const std::string& dbname, - const BSONObj& cmdObj, - BSONObjBuilder& result) override { - std::string roleNameString; - std::vector<RoleName> rolesToRemove; - Status status = auth::parseRolePossessionManipulationCommands( - cmdObj, "revokeRolesFromRole", dbname, &roleNameString, &rolesToRemove); - uassertStatusOK(status); + auto* client = opCtx->getClient(); + auto* serviceContext = client->getServiceContext(); + auto* authzManager = AuthorizationManager::get(serviceContext); + auto lk = uassertStatusOK(requireWritableAuthSchema28SCRAM(opCtx, authzManager)); - ServiceContext* serviceContext = opCtx->getClient()->getServiceContext(); - AuthorizationManager* authzManager = AuthorizationManager::get(serviceContext); - - auto lk = uassertStatusOK(requireWritableAuthSchema28SCRAM(opCtx, authzManager)); - - RoleName roleName(roleNameString, dbname); - if (RoleGraph::isBuiltinRole(roleName)) { - uasserted(ErrorCodes::InvalidRoleModification, - str::stream() << roleName.getFullName() - << " is a built-in role and cannot be modified."); - } + BSONObj roleDoc; + uassertStatusOK(authzManager->getRoleDescription(opCtx, roleName, &roleDoc)); - BSONObj roleDoc; - status = authzManager->getRoleDescription(opCtx, roleName, &roleDoc); - uassertStatusOK(status); - - std::vector<RoleName> roles; - status = auth::parseRoleNamesFromBSONArray( - BSONArray(roleDoc["roles"].Obj()), roleName.getDB(), &roles); - uassertStatusOK(status); + std::vector<RoleName> roles; + uassertStatusOK(auth::parseRoleNamesFromBSONArray( + BSONArray(roleDoc["roles"].Obj()), roleName.getDB(), &roles)); - for (std::vector<RoleName>::const_iterator it = rolesToRemove.begin(); - it != rolesToRemove.end(); - ++it) { - std::vector<RoleName>::iterator itToRm = std::find(roles.begin(), roles.end(), *it); - if (itToRm != roles.end()) { - roles.erase(itToRm); - } + for (const auto& roleToRemove : rolesToRemove) { + if (auto it = std::find(roles.begin(), roles.end(), roleToRemove); it != roles.end()) { + roles.erase(it); } - - audit::logRevokeRolesFromRole(Client::getCurrent(), roleName, rolesToRemove); - - status = updateRoleDocument( - opCtx, roleName, BSON("$set" << BSON("roles" << rolesVectorToBSONArray(roles)))); - // Must invalidate even on bad status - what if the write succeeded but the GLE failed? - authzManager->invalidateUserCache(opCtx); - uassertStatusOK(status); - return true; } -} cmdRevokeRolesFromRole; - -class CmdDropRole : public BasicCommand { -public: - CmdDropRole() : BasicCommand("dropRole") {} + audit::logRevokeRolesFromRole(client, roleName, rolesToRemove); - AllowedOnSecondary secondaryAllowed(ServiceContext*) const override { - return AllowedOnSecondary::kNever; - } - - bool supportsWriteConcern(const BSONObj& cmd) const override { - return true; - } - - std::string help() const override { - return "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."; - } + auto status = updateRoleDocument( + opCtx, roleName, BSON("$set" << BSON("roles" << rolesVectorToBSONArray(roles)))); + // Must invalidate even on bad status - what if the write succeeded but the GLE failed? + authzManager->invalidateUserCache(opCtx); + uassertStatusOK(status); +} - Status checkAuthForCommand(Client* client, - const std::string& dbname, - const BSONObj& cmdObj) const override { - return auth::checkAuthForDropRoleCommand(client, dbname, cmdObj); +CmdUMCTyped<DropRoleCommand, void> cmdDropRole; +template <> +void CmdUMCTyped<DropRoleCommand, void>::Invocation::typedRun(OperationContext* opCtx) { + const auto& cmd = request(); + const auto& dbname = cmd.getDbName(); + RoleName roleName(cmd.getCommandParameter(), dbname); + + uassert(ErrorCodes::BadValue, + str::stream() << roleName.getFullName() << " is a built-in role and cannot be modified", + !RoleGraph::isBuiltinRole(roleName)); + + auto* client = opCtx->getClient(); + auto* serviceContext = client->getServiceContext(); + auto* authzManager = AuthorizationManager::get(serviceContext); + auto lk = uassertStatusOK(requireWritableAuthSchema28SCRAM(opCtx, authzManager)); + + BSONObj roleDoc; + uassertStatusOK(authzManager->getRoleDescription(opCtx, roleName, &roleDoc)); + + // From here on, we always want to invalidate the user cache before returning. + auto invalidateGuard = makeGuard([&] { + try { + authzManager->invalidateUserCache(opCtx); + } catch (const AssertionException& ex) { + LOGV2_WARNING(4907701, "Failed invalidating user cache", "exception"_attr = ex); + } + }); + + // Remove this role from all users + std::int64_t numMatched; + auto status = updateAuthzDocuments( + opCtx, + 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, + &numMatched); + if (!status.isOK()) { + uassertStatusOK(useDefaultCode(status, ErrorCodes::UserModificationFailed) + .withContext(str::stream() + << "Failed to remove role " << roleName.getFullName() + << " from all users")); } - bool run(OperationContext* opCtx, - const std::string& dbname, - const BSONObj& cmdObj, - BSONObjBuilder& result) override { - RoleName roleName; - Status status = auth::parseDropRoleCommand(cmdObj, dbname, &roleName); - uassertStatusOK(status); - - ServiceContext* serviceContext = opCtx->getClient()->getServiceContext(); - AuthorizationManager* authzManager = AuthorizationManager::get(serviceContext); - - auto lk = uassertStatusOK(requireWritableAuthSchema28SCRAM(opCtx, authzManager)); - - if (RoleGraph::isBuiltinRole(roleName)) { - uasserted(ErrorCodes::InvalidRoleModification, - str::stream() << roleName.getFullName() - << " is a built-in role and cannot be modified."); - } - - BSONObj roleDoc; - status = authzManager->getRoleDescription(opCtx, roleName, &roleDoc); - uassertStatusOK(status); - - // From here on, we always want to invalidate the user cache before returning. - auto invalidateGuard = makeGuard([&] { - try { - authzManager->invalidateUserCache(opCtx); - } catch (const DBException& e) { - // Since this may be called after a uassert, we want to catch any uasserts - // that come out of invalidating the user cache and explicitly append it to - // the command response. - CommandHelpers::appendCommandStatusNoThrow(result, e.toStatus()); - } - }); - - // Remove this role from all users - long long nMatched; - status = updateAuthzDocuments( - opCtx, - 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, - &nMatched); - if (!status.isOK()) { - uassertStatusOK(useDefaultCode(status, ErrorCodes::UserModificationFailed) - .withContext(str::stream() - << "Failed to remove role " << roleName.getFullName() - << " from all users")); - } - - // Remove this role from all other roles - status = updateAuthzDocuments( - opCtx, - 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, - &nMatched); - if (!status.isOK()) { - uassertStatusOK( - useDefaultCode(status, ErrorCodes::RoleModificationFailed) - .withContext(str::stream() - << "Removed role " << roleName.getFullName() - << " from all users but failed to remove from all roles")); - } - - audit::logDropRole(Client::getCurrent(), roleName); - // Finally, remove the actual role document - status = removeRoleDocuments(opCtx, - BSON(AuthorizationManager::ROLE_NAME_FIELD_NAME - << roleName.getRole() - << AuthorizationManager::ROLE_DB_FIELD_NAME - << roleName.getDB()), - &nMatched); - if (!status.isOK()) { - uassertStatusOK(status.withContext( - str::stream() << "Removed role " << roleName.getFullName() - << " from all users and roles but failed to actually delete" - " the role itself")); - } - - dassert(nMatched == 0 || nMatched == 1); - if (nMatched == 0) { - uasserted(ErrorCodes::RoleNotFound, - str::stream() << "Role '" << roleName.getFullName() << "' not found"); - } - - return true; + // Remove this role from all other roles + status = updateAuthzDocuments( + opCtx, + 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, + &numMatched); + if (!status.isOK()) { + uassertStatusOK(useDefaultCode(status, ErrorCodes::RoleModificationFailed) + .withContext(str::stream() + << "Removed role " << roleName.getFullName() + << " from all users but failed to remove from all roles")); } -} cmdDropRole; - -class CmdDropAllRolesFromDatabase : public BasicCommand { -public: - CmdDropAllRolesFromDatabase() : BasicCommand("dropAllRolesFromDatabase") {} + audit::logDropRole(client, roleName); - AllowedOnSecondary secondaryAllowed(ServiceContext*) const override { - return AllowedOnSecondary::kNever; + // Finally, remove the actual role document + status = removeRoleDocuments( + opCtx, + BSON(AuthorizationManager::ROLE_NAME_FIELD_NAME + << roleName.getRole() << AuthorizationManager::ROLE_DB_FIELD_NAME << roleName.getDB()), + &numMatched); + if (!status.isOK()) { + uassertStatusOK(status.withContext( + str::stream() << "Removed role " << roleName.getFullName() + << " from all users and roles but failed to actually delete" + " the role itself")); } - bool supportsWriteConcern(const BSONObj& cmd) const override { - return true; + dassert(numMatched == 0 || numMatched == 1); + if (numMatched == 0) { + uasserted(ErrorCodes::RoleNotFound, + str::stream() << "Role '" << roleName.getFullName() << "' not found"); } +} - std::string help() const override { - return "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."; +CmdUMCTyped<DropAllRolesFromDatabaseCommand, DropAllRolesFromDatabaseReply> + cmdDropAllRolesFromDatabase; +template <> +DropAllRolesFromDatabaseReply +CmdUMCTyped<DropAllRolesFromDatabaseCommand, DropAllRolesFromDatabaseReply>::Invocation::typedRun( + OperationContext* opCtx) { + const auto& cmd = request(); + const auto& dbname = cmd.getDbName(); + + auto* client = opCtx->getClient(); + auto* serviceContext = client->getServiceContext(); + auto* authzManager = AuthorizationManager::get(serviceContext); + auto lk = uassertStatusOK(requireWritableAuthSchema28SCRAM(opCtx, authzManager)); + + // From here on, we always want to invalidate the user cache before returning. + auto invalidateGuard = makeGuard([opCtx, authzManager] { + try { + authzManager->invalidateUserCache(opCtx); + } catch (const AssertionException& ex) { + LOGV2_WARNING(4907700, "Failed invalidating user cache", "exception"_attr = ex); + } + }); + + // Remove these roles from all users + std::int64_t numMatched; + auto status = updateAuthzDocuments( + opCtx, + AuthorizationManager::usersCollectionNamespace, + BSON("roles" << BSON(AuthorizationManager::ROLE_DB_FIELD_NAME << dbname)), + BSON("$pull" << BSON("roles" << BSON(AuthorizationManager::ROLE_DB_FIELD_NAME << dbname))), + false, + true, + &numMatched); + if (!status.isOK()) { + uassertStatusOK(useDefaultCode(status, ErrorCodes::UserModificationFailed) + .withContext(str::stream() << "Failed to remove roles from \"" << dbname + << "\" db from all users")); } - Status checkAuthForCommand(Client* client, - const std::string& dbname, - const BSONObj& cmdObj) const override { - return auth::checkAuthForDropAllRolesFromDatabaseCommand(client, dbname); + // Remove these roles from all other roles + std::string sourceFieldName = str::stream() + << "roles." << AuthorizationManager::ROLE_DB_FIELD_NAME; + status = updateAuthzDocuments( + opCtx, + AuthorizationManager::rolesCollectionNamespace, + BSON(sourceFieldName << dbname), + BSON("$pull" << BSON("roles" << BSON(AuthorizationManager::ROLE_DB_FIELD_NAME << dbname))), + false, + true, + &numMatched); + if (!status.isOK()) { + uassertStatusOK(useDefaultCode(status, ErrorCodes::RoleModificationFailed) + .withContext(str::stream() << "Failed to remove roles from \"" << dbname + << "\" db from all roles")); } - bool run(OperationContext* opCtx, - const std::string& dbname, - const BSONObj& cmdObj, - BSONObjBuilder& result) override { - Status status = auth::parseDropAllRolesFromDatabaseCommand(cmdObj, dbname); - uassertStatusOK(status); - - ServiceContext* serviceContext = opCtx->getClient()->getServiceContext(); - AuthorizationManager* authzManager = AuthorizationManager::get(serviceContext); - - auto lk = uassertStatusOK(requireWritableAuthSchema28SCRAM(opCtx, authzManager)); - // From here on, we always want to invalidate the user cache before returning. - auto invalidateGuard = makeGuard([&] { - try { - authzManager->invalidateUserCache(opCtx); - } catch (const DBException& e) { - // Since this may be called after a uassert, we want to catch any uasserts - // that come out of invalidating the user cache and explicitly append it to - // the command response. - CommandHelpers::appendCommandStatusNoThrow(result, e.toStatus()); - } - }); - - // Remove these roles from all users - long long nMatched; - status = updateAuthzDocuments( - opCtx, - AuthorizationManager::usersCollectionNamespace, - BSON("roles" << BSON(AuthorizationManager::ROLE_DB_FIELD_NAME << dbname)), - BSON("$pull" << BSON("roles" - << BSON(AuthorizationManager::ROLE_DB_FIELD_NAME << dbname))), - false, - true, - &nMatched); - if (!status.isOK()) { - uassertStatusOK(useDefaultCode(status, ErrorCodes::UserModificationFailed) - .withContext(str::stream() << "Failed to remove roles from \"" - << dbname << "\" db from all users")); - } - - // Remove these roles from all other roles - std::string sourceFieldName = str::stream() - << "roles." << AuthorizationManager::ROLE_DB_FIELD_NAME; - status = updateAuthzDocuments( - opCtx, - AuthorizationManager::rolesCollectionNamespace, - BSON(sourceFieldName << dbname), - BSON("$pull" << BSON("roles" - << BSON(AuthorizationManager::ROLE_DB_FIELD_NAME << dbname))), - false, - true, - &nMatched); - if (!status.isOK()) { - uassertStatusOK(useDefaultCode(status, ErrorCodes::RoleModificationFailed) - .withContext(str::stream() << "Failed to remove roles from \"" - << dbname << "\" db from all roles")); - } - - audit::logDropAllRolesFromDatabase(Client::getCurrent(), dbname); - // Finally, remove the actual role documents - status = removeRoleDocuments( - opCtx, BSON(AuthorizationManager::ROLE_DB_FIELD_NAME << dbname), &nMatched); - if (!status.isOK()) { - uassertStatusOK(status.withContext( - str::stream() << "Removed roles from \"" << dbname - << "\" db " - " from all users and roles but failed to actually delete" - " those roles themselves")); - } - - result.append("n", nMatched); - - return true; + audit::logDropAllRolesFromDatabase(Client::getCurrent(), dbname); + // Finally, remove the actual role documents + status = removeRoleDocuments( + opCtx, BSON(AuthorizationManager::ROLE_DB_FIELD_NAME << dbname), &numMatched); + if (!status.isOK()) { + uassertStatusOK(status.withContext( + str::stream() << "Removed roles from \"" << dbname + << "\" db " + " from all users and roles but failed to actually delete" + " those roles themselves")); } -} cmdDropAllRolesFromDatabase; + DropAllRolesFromDatabaseReply reply; + reply.setCount(numMatched); + return reply; +} /** * Provides information about one or more roles, the indirect roles they are members of, and @@ -2613,7 +2256,7 @@ public: } if (drop) { - long long numRemoved; + std::int64_t numRemoved; for (const UserName& userName : usersToDrop) { audit::logDropUser(Client::getCurrent(), userName); status = removePrivilegeDocuments(opCtx, @@ -2684,7 +2327,7 @@ public: } if (drop) { - long long numRemoved; + std::int64_t numRemoved; for (stdx::unordered_set<RoleName>::iterator it = rolesToDrop.begin(); it != rolesToDrop.end(); ++it) { |