From ebe622f95a071d7d769298b68ecf45b6cdff8e39 Mon Sep 17 00:00:00 2001 From: Sara Golemon Date: Tue, 23 Jun 2020 14:20:48 +0000 Subject: SERVER-49077 IDLify UMC Mutation commands --- src/mongo/db/auth/SConscript | 1 + src/mongo/db/auth/auth_types.idl | 16 + src/mongo/db/auth/authorization_session.h | 6 +- src/mongo/db/auth/authorization_session_impl.cpp | 9 +- src/mongo/db/auth/authorization_session_impl.h | 6 +- src/mongo/db/auth/privilege.cpp | 31 + src/mongo/db/auth/privilege.h | 1 + src/mongo/db/auth/role_name.cpp | 5 + src/mongo/db/auth/role_name.h | 1 + src/mongo/db/auth/role_name_or_string.cpp | 71 + src/mongo/db/auth/role_name_or_string.h | 75 + .../db/auth/user_management_commands_parser.cpp | 330 ---- .../db/auth/user_management_commands_parser.h | 101 - src/mongo/db/commands/SConscript | 1 + src/mongo/db/commands/user_management_commands.cpp | 1965 ++++++++------------ src/mongo/db/commands/user_management_commands.idl | 263 +++ .../commands/user_management_commands_common.cpp | 393 ++-- .../db/commands/user_management_commands_common.h | 78 +- 18 files changed, 1429 insertions(+), 1924 deletions(-) create mode 100644 src/mongo/db/auth/role_name_or_string.cpp create mode 100644 src/mongo/db/auth/role_name_or_string.h create mode 100644 src/mongo/db/commands/user_management_commands.idl (limited to 'src/mongo/db') diff --git a/src/mongo/db/auth/SConscript b/src/mongo/db/auth/SConscript index 13a1ba54cc1..3123a63a5a6 100644 --- a/src/mongo/db/auth/SConscript +++ b/src/mongo/db/auth/SConscript @@ -26,6 +26,7 @@ env.Library( 'auth_decorations.cpp', 'user_name.cpp', 'role_name.cpp', + 'role_name_or_string.cpp', ], LIBDEPS=[ '$BUILD_DIR/mongo/base', diff --git a/src/mongo/db/auth/auth_types.idl b/src/mongo/db/auth/auth_types.idl index e638668bd44..683688591a0 100644 --- a/src/mongo/db/auth/auth_types.idl +++ b/src/mongo/db/auth/auth_types.idl @@ -30,7 +30,9 @@ global: cpp_namespace: "mongo" cpp_includes: - "mongo/db/auth/user_name.h" + - "mongo/db/auth/privilege.h" - "mongo/db/auth/role_name.h" + - "mongo/db/auth/role_name_or_string.h" imports: - "mongo/idl/basic_types.idl" @@ -49,3 +51,17 @@ types: cpp_type: "RoleName" deserializer: "mongo::RoleName::parseFromBSON" serializer: "mongo::RoleName::serializeToBSON" + + RoleNameOrString: + bson_serialization_type: any + description: Wrapper for RoleName which allows specifying just a string. + cpp_type: "RoleNameOrString" + deserializer: "mongo::RoleNameOrString::parseFromBSON" + serializer: "mongo::RoleNameOrString::serializeToBSON" + + Privilege: + bson_serialization_type: object + description: "A struct representing a privilege grant" + cpp_type: "Privilege" + deserializer: "mongo::Privilege::fromBSON" + serializer: "mongo::Privilege::toBSON" diff --git a/src/mongo/db/auth/authorization_session.h b/src/mongo/db/auth/authorization_session.h index 61ba195a0c3..b18d8409099 100644 --- a/src/mongo/db/auth/authorization_session.h +++ b/src/mongo/db/auth/authorization_session.h @@ -48,10 +48,6 @@ namespace mongo { -namespace auth { - -struct CreateOrUpdateRoleArgs; -} // namespace auth class Client; /** @@ -258,7 +254,7 @@ public: virtual bool isAuthorizedToParseNamespaceElement(const BSONElement& elem) = 0; // Checks if this connection has the privileges necessary to create a new role - virtual bool isAuthorizedToCreateRole(const auth::CreateOrUpdateRoleArgs& args) = 0; + virtual bool isAuthorizedToCreateRole(const RoleName& roleName) = 0; // Utility function for isAuthorizedForActionsOnResource( // ResourcePattern::forDatabaseName(role.getDB()), ActionType::grantAnyRole) diff --git a/src/mongo/db/auth/authorization_session_impl.cpp b/src/mongo/db/auth/authorization_session_impl.cpp index 73ec76620b1..f888dede347 100644 --- a/src/mongo/db/auth/authorization_session_impl.cpp +++ b/src/mongo/db/auth/authorization_session_impl.cpp @@ -533,12 +533,11 @@ bool AuthorizationSessionImpl::isAuthorizedToParseNamespaceElement(const BSONEle return true; } -bool AuthorizationSessionImpl::isAuthorizedToCreateRole( - const struct auth::CreateOrUpdateRoleArgs& args) { +bool AuthorizationSessionImpl::isAuthorizedToCreateRole(const RoleName& roleName) { // A user is allowed to create a role under either of two conditions. // The user may create a role if the authorization system says they are allowed to. - if (isAuthorizedForActionsOnResource(ResourcePattern::forDatabaseName(args.roleName.getDB()), + if (isAuthorizedForActionsOnResource(ResourcePattern::forDatabaseName(roleName.getDB()), ActionType::createRole)) { return true; } @@ -547,7 +546,7 @@ bool AuthorizationSessionImpl::isAuthorizedToCreateRole( // role. This implies they have obtained the role through an external authorization mechanism. if (_externalState->shouldAllowLocalhost()) { for (const auto& user : _authenticatedUsers) { - if (user->hasRole(args.roleName)) { + if (user->hasRole(roleName)) { return true; } } @@ -555,7 +554,7 @@ bool AuthorizationSessionImpl::isAuthorizedToCreateRole( "Not authorized to create the first role in the system using the " "localhost exception. The user needs to acquire the role through " "external authentication first.", - "role"_attr = args.roleName); + "role"_attr = roleName); } return false; diff --git a/src/mongo/db/auth/authorization_session_impl.h b/src/mongo/db/auth/authorization_session_impl.h index 9e1417eaccf..b7896daf518 100644 --- a/src/mongo/db/auth/authorization_session_impl.h +++ b/src/mongo/db/auth/authorization_session_impl.h @@ -46,10 +46,6 @@ namespace mongo { -namespace auth { -struct CreateOrUpdateRoleArgs; -} - class Client; /** @@ -144,7 +140,7 @@ public: bool isAuthorizedToParseNamespaceElement(const BSONElement& elem) override; - bool isAuthorizedToCreateRole(const auth::CreateOrUpdateRoleArgs& args) override; + bool isAuthorizedToCreateRole(const RoleName& roleName) override; bool isAuthorizedToGrantRole(const RoleName& role) override; diff --git a/src/mongo/db/auth/privilege.cpp b/src/mongo/db/auth/privilege.cpp index b0f6b2b44b5..033ec7cbb08 100644 --- a/src/mongo/db/auth/privilege.cpp +++ b/src/mongo/db/auth/privilege.cpp @@ -84,6 +84,37 @@ BSONObj Privilege::toBSON() const { return pp.toBSON(); } +Privilege Privilege::fromBSON(BSONObj obj) { + ParsedPrivilege pp; + std::string errmsg; + if (!pp.parseBSON(obj, &errmsg)) { + uasserted(ErrorCodes::BadValue, + str::stream() << "Unable to parse privilege document: " << obj + << ", error: " << errmsg); + } + Privilege ret; + std::vector unrecognized; + uassertStatusOK(ParsedPrivilege::parsedPrivilegeToPrivilege(pp, &ret, &unrecognized)); + + if (!unrecognized.empty()) { + StringBuilder sb; + sb << "Unrecognized action"; + if (unrecognized.size() > 1) { + sb << 's'; + } + sb << ": "; + for (std::size_t i = 0; i < unrecognized.size(); ++i) { + if (i > 0) { + sb << ", "; + } + sb << unrecognized[i]; + } + uasserted(ErrorCodes::BadValue, sb.str()); + } + + return ret; +} + Status Privilege::getBSONForPrivileges(const PrivilegeVector& privileges, mutablebson::Element resultArray) try { for (auto& currPriv : privileges) { diff --git a/src/mongo/db/auth/privilege.h b/src/mongo/db/auth/privilege.h index e6625863bbf..f610e8adf94 100644 --- a/src/mongo/db/auth/privilege.h +++ b/src/mongo/db/auth/privilege.h @@ -87,6 +87,7 @@ public: // Checks if the given actions are present in the Privilege. bool includesActions(const ActionSet& actions) const; + static Privilege fromBSON(BSONObj obj); BSONObj toBSON() const; private: diff --git a/src/mongo/db/auth/role_name.cpp b/src/mongo/db/auth/role_name.cpp index 224d8e8985a..11eb857c02a 100644 --- a/src/mongo/db/auth/role_name.cpp +++ b/src/mongo/db/auth/role_name.cpp @@ -90,5 +90,10 @@ void RoleName::_serializeToSubObj(BSONObjBuilder* sub) const { sub->append(AuthorizationManager::ROLE_DB_FIELD_NAME, getDB()); } +BSONObj RoleName::toBSON() const { + BSONObjBuilder bob; + _serializeToSubObj(&bob); + return bob.obj(); +} } // namespace mongo diff --git a/src/mongo/db/auth/role_name.h b/src/mongo/db/auth/role_name.h index ccabd86b4a2..41129b017fc 100644 --- a/src/mongo/db/auth/role_name.h +++ b/src/mongo/db/auth/role_name.h @@ -56,6 +56,7 @@ public: static RoleName parseFromBSON(const BSONElement& elem); void serializeToBSON(StringData fieldName, BSONObjBuilder* bob) const; void serializeToBSON(BSONArrayBuilder* bob) const; + BSONObj toBSON() const; /** * Gets the name of the role excluding the "@dbname" component. diff --git a/src/mongo/db/auth/role_name_or_string.cpp b/src/mongo/db/auth/role_name_or_string.cpp new file mode 100644 index 00000000000..62aa6aa9088 --- /dev/null +++ b/src/mongo/db/auth/role_name_or_string.cpp @@ -0,0 +1,71 @@ +/** + * Copyright (C) 2020-present MongoDB, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the Server Side Public License, version 1, + * as published by MongoDB, Inc. + * + * 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 + * Server Side Public License for more details. + * + * You should have received a copy of the Server Side 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 Server Side 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. + */ + +#include "mongo/db/auth/role_name_or_string.h" + +namespace mongo { + +RoleName RoleNameOrString::getRoleName(StringData dbname) const { + if (std::holds_alternative(_roleName)) { + return std::get(_roleName); + } else { + dassert(std::holds_alternative(_roleName)); + return RoleName(std::get(_roleName), dbname); + } +} + +RoleNameOrString RoleNameOrString::parseFromBSON(const BSONElement& elem) { + if (elem.type() == Object) { + return RoleNameOrString(RoleName::parseFromBSON(elem)); + } else if (elem.type() == String) { + return RoleNameOrString(elem.checkAndGetStringData()); + } else { + uasserted(ErrorCodes::BadValue, "Role name must be either a document or string"); + } +} + +void RoleNameOrString::serializeToBSON(StringData fieldName, BSONObjBuilder* bob) const { + if (std::holds_alternative(_roleName)) { + std::get(_roleName).serializeToBSON(fieldName, bob); + } else { + dassert(std::holds_alternative(_roleName)); + bob->append(fieldName, std::get(_roleName)); + } +} + +void RoleNameOrString::serializeToBSON(BSONArrayBuilder* bob) const { + if (std::holds_alternative(_roleName)) { + std::get(_roleName).serializeToBSON(bob); + } else { + dassert(std::holds_alternative(_roleName)); + bob->append(std::get(_roleName)); + } +} + +} // namespace mongo diff --git a/src/mongo/db/auth/role_name_or_string.h b/src/mongo/db/auth/role_name_or_string.h new file mode 100644 index 00000000000..d75e1f332a9 --- /dev/null +++ b/src/mongo/db/auth/role_name_or_string.h @@ -0,0 +1,75 @@ +/** + * Copyright (C) 2020-present MongoDB, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the Server Side Public License, version 1, + * as published by MongoDB, Inc. + * + * 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 + * Server Side Public License for more details. + * + * You should have received a copy of the Server Side 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 Server Side Public License in all respects for + * all of the code used other than as permitted herein. If you modify file(s) + * with this exception, you may extend this exception to your version of the + * file(s), but you are not obligated to do so. If you do not wish to do so, + * delete this exception statement from your version. If you delete this + * exception statement from all source files in the program, then also delete + * it in the license file. + */ + +#pragma once + +#include +#include + +#include "mongo/db/auth/role_name.h" + +#include "mongo/base/string_data.h" +#include "mongo/bson/bsonelement.h" +#include "mongo/bson/bsonobjbuilder.h" + +namespace mongo { + +/** + * Wraps the RoleName type for IDL parsing. + * + * User management commands which accept a foreign role name + * allow specifying the name as either a { role: "", db: "" } document + * or as a simple string where the db name is implied. + * + * This class will parse either form, and allow always returning + * a fully qualified RoleName. + */ +class RoleNameOrString { +public: + RoleNameOrString() = delete; + explicit RoleNameOrString(std::string role) : _roleName(std::move(role)) {} + explicit RoleNameOrString(StringData role) : _roleName(role.toString()) {} + explicit RoleNameOrString(RoleName role) : _roleName(std::move(role)) {} + + // IDL support. + static RoleNameOrString parseFromBSON(const BSONElement& elem); + void serializeToBSON(StringData fieldName, BSONObjBuilder* bob) const; + void serializeToBSON(BSONArrayBuilder* bob) const; + + /** + * Returns the fully qualified RoleName if present, + * or constructs a RoleName using the parsed role and provided dbname. + */ + RoleName getRoleName(StringData dbname) const; + +private: + std::variant _roleName; +}; + +} // namespace mongo diff --git a/src/mongo/db/auth/user_management_commands_parser.cpp b/src/mongo/db/auth/user_management_commands_parser.cpp index 0d380888ac9..e85a86cbfda 100644 --- a/src/mongo/db/auth/user_management_commands_parser.cpp +++ b/src/mongo/db/auth/user_management_commands_parser.cpp @@ -144,192 +144,6 @@ Status parseRoleNamesFromBSONArray(const BSONArray& rolesArray, parsedRoleNames); } -Status parseRolePossessionManipulationCommands(const BSONObj& cmdObj, - StringData cmdName, - const std::string& dbname, - std::string* parsedName, - vector* parsedRoleNames) { - stdx::unordered_set validFieldNames; - validFieldNames.insert(cmdName.toString()); - validFieldNames.insert("roles"); - - Status status = _checkNoExtraFields(cmdObj, cmdName, validFieldNames); - if (!status.isOK()) { - return status; - } - - status = bsonExtractStringField(cmdObj, cmdName, parsedName); - if (!status.isOK()) { - return status; - } - - BSONElement rolesElement; - status = bsonExtractTypedField(cmdObj, "roles", Array, &rolesElement); - if (!status.isOK()) { - return status; - } - - status = parseRoleNamesFromBSONArray(BSONArray(rolesElement.Obj()), dbname, parsedRoleNames); - if (!status.isOK()) { - return status; - } - - if (!parsedRoleNames->size()) { - return Status(ErrorCodes::BadValue, - str::stream() << cmdName - << " command requires a non-empty " - "\"roles\" array"); - } - return Status::OK(); -} - -Status parseCreateOrUpdateUserCommands(const BSONObj& cmdObj, - StringData cmdName, - const std::string& dbname, - CreateOrUpdateUserArgs* parsedArgs) { - stdx::unordered_set validFieldNames; - validFieldNames.insert(cmdName.toString()); - validFieldNames.insert("customData"); - validFieldNames.insert("digestPassword"); - validFieldNames.insert("pwd"); - validFieldNames.insert("roles"); - validFieldNames.insert("authenticationRestrictions"); - validFieldNames.insert("mechanisms"); - - Status status = _checkNoExtraFields(cmdObj, cmdName, validFieldNames); - if (!status.isOK()) { - return status; - } - - BSONObjBuilder userObjBuilder; - - // Parse user name - std::string userName; - status = bsonExtractStringField(cmdObj, cmdName, &userName); - if (!status.isOK()) { - return status; - } - if (userName.find('\0') != std::string::npos) { - return Status(ErrorCodes::BadValue, "Username cannot contain NULL characters"); - } - - parsedArgs->userName = UserName(userName, dbname); - - // Parse mechanisms - if (cmdObj.hasField("mechanisms")) { - const auto mechsElem = cmdObj["mechanisms"]; - if (mechsElem.type() != Array) { - return {ErrorCodes::UnsupportedFormat, "mechanisms field must be an array"}; - } - const auto mechs = mechsElem.Obj(); - if (mechs.nFields() == 0) { - return {ErrorCodes::UnsupportedFormat, "mechanisms field must not be empty"}; - } - for (auto elem : mechs) { - if (elem.type() != String) { - return {ErrorCodes::BadValue, "mechanisms field must be an array of strings"}; - } - parsedArgs->mechanisms.push_back(elem.String()); - } - } - - // Parse password - if (cmdObj.hasField("pwd")) { - status = bsonExtractStringField(cmdObj, "pwd", &parsedArgs->password); - if (!status.isOK()) { - return status; - } - if (parsedArgs->password.empty()) { - return Status(ErrorCodes::BadValue, "User passwords must not be empty"); - } - parsedArgs->hasPassword = true; - - // True if the server should digest the password - status = bsonExtractBooleanFieldWithDefault( - cmdObj, "digestPassword", true, &parsedArgs->digestPassword); - if (!status.isOK()) { - return status; - } - } - - // Parse custom data - if (cmdObj.hasField("customData")) { - BSONElement element; - status = bsonExtractTypedField(cmdObj, "customData", Object, &element); - if (!status.isOK()) { - return status; - } - parsedArgs->customData = element.Obj(); - parsedArgs->hasCustomData = true; - } - - // Parse authentication restrictions - if (cmdObj.hasField("authenticationRestrictions")) { - BSONElement element = cmdObj["authenticationRestrictions"]; - if (element.type() != Array) { - return Status(ErrorCodes::BadValue, "authenticationRestrictions must be an array"); - } - parsedArgs->authenticationRestrictions = - BSONArray(cmdObj["authenticationRestrictions"].Obj()); - } - - // Parse roles - if (cmdObj.hasField("roles")) { - BSONElement rolesElement; - status = bsonExtractTypedField(cmdObj, "roles", Array, &rolesElement); - if (!status.isOK()) { - return status; - } - status = - parseRoleNamesFromBSONArray(BSONArray(rolesElement.Obj()), dbname, &parsedArgs->roles); - if (!status.isOK()) { - return status; - } - parsedArgs->hasRoles = true; - } - - return Status::OK(); -} - -Status parseAndValidateDropUserCommand(const BSONObj& cmdObj, - const std::string& dbname, - UserName* parsedUserName) { - stdx::unordered_set validFieldNames; - validFieldNames.insert("dropUser"); - - Status status = _checkNoExtraFields(cmdObj, "dropUser", validFieldNames); - if (!status.isOK()) { - return status; - } - - std::string user; - status = bsonExtractStringField(cmdObj, "dropUser", &user); - if (!status.isOK()) { - return status; - } - - *parsedUserName = UserName(user, dbname); - return Status::OK(); -} - -Status parseFromDatabaseCommand(const BSONObj& cmdObj, - const std::string& dbname, - std::string command) { - stdx::unordered_set validFieldNames; - validFieldNames.insert(command); - - Status status = _checkNoExtraFields(cmdObj, command, validFieldNames); - if (!status.isOK()) { - return status; - } - - return Status::OK(); -} -Status parseAndValidateDropAllUsersFromDatabaseCommand(const BSONObj& cmdObj, - const std::string& dbname) { - return parseFromDatabaseCommand(cmdObj, dbname, "dropAllUsersFromDatabase"); -} - Status parseUsersInfoCommand(const BSONObj& cmdObj, StringData dbname, UsersInfoArgs* parsedArgs) { stdx::unordered_set validFieldNames; validFieldNames.insert("usersInfo"); @@ -525,150 +339,6 @@ Status parseAndValidatePrivilegeArray(const BSONArray& privileges, return Status::OK(); } -Status parseCreateOrUpdateRoleCommands(const BSONObj& cmdObj, - StringData cmdName, - const std::string& dbname, - CreateOrUpdateRoleArgs* parsedArgs) { - stdx::unordered_set validFieldNames; - validFieldNames.insert(cmdName.toString()); - validFieldNames.insert("privileges"); - validFieldNames.insert("roles"); - validFieldNames.insert("authenticationRestrictions"); - - Status status = _checkNoExtraFields(cmdObj, cmdName, validFieldNames); - if (!status.isOK()) { - return status; - } - - // Parse role name - std::string roleName; - status = bsonExtractStringField(cmdObj, cmdName, &roleName); - if (!status.isOK()) { - return status; - } - if (roleName.find('\0') != std::string::npos) { - return Status(ErrorCodes::BadValue, "Role name cannot contain NULL characters"); - } - - parsedArgs->roleName = RoleName(roleName, dbname); - - // Parse privileges - if (cmdObj.hasField("privileges")) { - BSONElement privilegesElement; - status = bsonExtractTypedField(cmdObj, "privileges", Array, &privilegesElement); - if (!status.isOK()) { - return status; - } - status = parseAndValidatePrivilegeArray(BSONArray(privilegesElement.Obj()), - &parsedArgs->privileges); - if (!status.isOK()) { - return status; - } - parsedArgs->hasPrivileges = true; - } - - // Parse roles - if (cmdObj.hasField("roles")) { - BSONElement rolesElement; - status = bsonExtractTypedField(cmdObj, "roles", Array, &rolesElement); - if (!status.isOK()) { - return status; - } - status = - parseRoleNamesFromBSONArray(BSONArray(rolesElement.Obj()), dbname, &parsedArgs->roles); - if (!status.isOK()) { - return status; - } - parsedArgs->hasRoles = true; - } - - // Parse restrictions - if (cmdObj.hasField("authenticationRestrictions")) { - BSONElement restrictionsElement; - status = bsonExtractTypedField( - cmdObj, "authenticationRestrictions", Array, &restrictionsElement); - if (!status.isOK()) { - return status; - } - auto restrictions = getRawAuthenticationRestrictions(BSONArray(restrictionsElement.Obj())); - if (!restrictions.isOK()) { - return restrictions.getStatus(); - } - parsedArgs->authenticationRestrictions = restrictions.getValue(); - } - - return Status::OK(); -} - -Status parseAndValidateRolePrivilegeManipulationCommands(const BSONObj& cmdObj, - StringData cmdName, - const std::string& dbname, - RoleName* parsedRoleName, - PrivilegeVector* parsedPrivileges) { - stdx::unordered_set validFieldNames; - validFieldNames.insert(cmdName.toString()); - validFieldNames.insert("privileges"); - - Status status = _checkNoExtraFields(cmdObj, cmdName, validFieldNames); - if (!status.isOK()) { - return status; - } - - BSONObjBuilder roleObjBuilder; - - // Parse role name - std::string roleName; - status = bsonExtractStringField(cmdObj, cmdName, &roleName); - if (!status.isOK()) { - return status; - } - *parsedRoleName = RoleName(roleName, dbname); - - // Parse privileges - BSONElement privilegesElement; - status = bsonExtractTypedField(cmdObj, "privileges", Array, &privilegesElement); - if (!status.isOK()) { - return status; - } - status = parseAndValidatePrivilegeArray(BSONArray(privilegesElement.Obj()), parsedPrivileges); - if (!status.isOK()) { - return status; - } - if (!parsedPrivileges->size()) { - return Status(ErrorCodes::BadValue, - str::stream() << cmdName - << " command requires a non-empty " - "\"privileges\" array"); - } - - return Status::OK(); -} - -Status parseDropRoleCommand(const BSONObj& cmdObj, - const std::string& dbname, - RoleName* parsedRoleName) { - stdx::unordered_set validFieldNames; - validFieldNames.insert("dropRole"); - - Status status = _checkNoExtraFields(cmdObj, "dropRole", validFieldNames); - if (!status.isOK()) { - return status; - } - - std::string user; - status = bsonExtractStringField(cmdObj, "dropRole", &user); - if (!status.isOK()) { - return status; - } - - *parsedRoleName = RoleName(user, dbname); - return Status::OK(); -} - -Status parseDropAllRolesFromDatabaseCommand(const BSONObj& cmdObj, const std::string& dbname) { - return parseFromDatabaseCommand(cmdObj, dbname, "dropAllRolesFromDatabase"); -} - Status parseMergeAuthzCollectionsCommand(const BSONObj& cmdObj, MergeAuthzCollectionsArgs* parsedArgs) { stdx::unordered_set validFieldNames; diff --git a/src/mongo/db/auth/user_management_commands_parser.h b/src/mongo/db/auth/user_management_commands_parser.h index d249871a63b..b3f7073924f 100644 --- a/src/mongo/db/auth/user_management_commands_parser.h +++ b/src/mongo/db/auth/user_management_commands_parser.h @@ -45,63 +45,6 @@ namespace mongo { namespace auth { -struct CreateOrUpdateUserArgs { - UserName userName; - bool hasPassword; - std::string password; - bool hasCustomData; - BSONObj customData; - bool hasRoles; - std::vector roles; - boost::optional authenticationRestrictions; - std::vector mechanisms; - bool digestPassword; - - CreateOrUpdateUserArgs() - : hasPassword(false), hasCustomData(false), hasRoles(false), digestPassword(true) {} -}; - -/** - * Takes a command object describing an invocation of the "createUser" or "updateUser" commands - * (which command it is is specified in "cmdName") on the database "dbname", and parses out all - * the arguments into the "parsedArgs" output param. - */ -Status parseCreateOrUpdateUserCommands(const BSONObj& cmdObj, - StringData cmdName, - const std::string& dbname, - CreateOrUpdateUserArgs* parsedArgs); - -/** - * Takes a command object describing an invocation of one of "grantRolesToUser", - * "revokeRolesFromUser", "grantDelegateRolesToUser", "revokeDelegateRolesFromUser", - * "grantRolesToRole", and "revokeRolesFromRoles" (which command it is is specified in the - * "cmdName" argument), and parses out (into the parsedName out param) the user/role name of - * the user/roles being modified, the roles being granted or revoked, and the write concern to - * use. - */ -Status parseRolePossessionManipulationCommands(const BSONObj& cmdObj, - StringData cmdName, - const std::string& dbname, - std::string* parsedName, - std::vector* parsedRoleNames); - -/** - * Takes a command object describing an invocation of the "dropUser" command and parses out - * the UserName of the user to be removed. - * Also validates the input and returns a non-ok Status if there is anything wrong. - */ -Status parseAndValidateDropUserCommand(const BSONObj& cmdObj, - const std::string& dbname, - UserName* parsedUserName); - -/** - * Takes a command object describing an invocation of the "dropAllUsersFromDatabase" command and - * parses out the write concern. - * Also validates the input and returns a non-ok Status if there is anything wrong. - */ -Status parseAndValidateDropAllUsersFromDatabaseCommand(const BSONObj& cmdObj, - const std::string& dbname); - struct UsersInfoArgs { enum class Target { kExplicitUsers, kDB, kGlobal }; @@ -135,50 +78,6 @@ struct RolesInfoArgs { */ Status parseRolesInfoCommand(const BSONObj& cmdObj, StringData dbname, RolesInfoArgs* parsedArgs); -struct CreateOrUpdateRoleArgs { - RoleName roleName; - bool hasRoles = false; - std::vector roles; - bool hasPrivileges = false; - PrivilegeVector privileges; - boost::optional authenticationRestrictions; -}; - -/** - * Takes a command object describing an invocation of the "createRole" or "updateRole" commands - * (which command it is is specified in "cmdName") on the database "dbname", and parses out all - * the arguments into the "parsedArgs" output param. - */ -Status parseCreateOrUpdateRoleCommands(const BSONObj& cmdObj, - StringData cmdName, - const std::string& dbname, - CreateOrUpdateRoleArgs* parsedArgs); - -/** - * Takes a command object describing an invocation of the "grantPrivilegesToRole" or - * "revokePrivilegesFromRole" commands, and parses out the role name of the - * role being modified, the privileges being granted or revoked, and the write concern to use. - */ -Status parseAndValidateRolePrivilegeManipulationCommands(const BSONObj& cmdObj, - StringData cmdName, - const std::string& dbname, - RoleName* parsedRoleName, - PrivilegeVector* parsedPrivileges); - -/** - * Takes a command object describing an invocation of the "dropRole" command and parses out - * the RoleName of the role to be removed. - */ -Status parseDropRoleCommand(const BSONObj& cmdObj, - const std::string& dbname, - RoleName* parsedRoleName); - -/** - * Takes a command object describing an invocation of the "dropAllRolesFromDatabase" command and - * parses out the write concern. - */ -Status parseDropAllRolesFromDatabaseCommand(const BSONObj& cmdObj, const std::string& dbname); - /** * Parses the privileges described in "privileges" into a vector of Privilege objects. * Returns Status::OK() upon successfully parsing all the elements of "privileges". diff --git a/src/mongo/db/commands/SConscript b/src/mongo/db/commands/SConscript index 7bbb99a7e9b..68bcb1bb041 100644 --- a/src/mongo/db/commands/SConscript +++ b/src/mongo/db/commands/SConscript @@ -146,6 +146,7 @@ env.Library( 'user_management_commands_common.cpp', env.Idlc('drop_connections.idl')[0], env.Idlc('rwc_defaults_commands.idl')[0], + env.Idlc('user_management_commands.idl')[0], ], LIBDEPS=[ '$BUILD_DIR/mongo/db/read_write_concern_defaults', 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 requireReadableAuthSchema26Upgrade(OperationContext* return std::move(lk); } -Status buildCredentials(BSONObjBuilder* builder, const auth::CreateOrUpdateUserArgs& args) { - if (!args.hasPassword) { +template +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::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::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& 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,527 +759,431 @@ 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; - } - - 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); +template +BSONArray vectorToBSON(const std::vector& vec) { + BSONArrayBuilder builder; + for (const auto& val : vec) { + builder.append(val.toBSON()); } + return builder.arr(); +} - 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); +template +class CmdUMCTyped : public TypedCommand> { +public: + using Request = RequestT; + using Reply = ReplyT; + using TC = TypedCommand>; - if (args.userName.getDB() == "local") { - uasserted(ErrorCodes::BadValue, "Cannot create users in the local database"); - } + class Invocation final : public TC::InvocationBase { + public: + using TC::InvocationBase::InvocationBase; + using TC::InvocationBase::request; - 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"); - } + Reply typedRun(OperationContext* opCtx); - if ((args.hasPassword) && args.userName.getDB() == "$external") { - uasserted(ErrorCodes::BadValue, - "Cannot set the password for users defined on the '$external' " - "database"); + private: + bool supportsWriteConcern() const final { + return true; } - if (!args.hasRoles) { - uasserted(ErrorCodes::BadValue, "\"createUser\" command requires a \"roles\" array"); + void doCheckAuthorization(OperationContext* opCtx) const final { + auth::checkAuthForTypedCommand(opCtx->getClient(), request()); } -#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."); + NamespaceString ns() const override { + return NamespaceString(request().getDbName(), ""); } -#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); + typename TC::AllowedOnSecondary secondaryAllowed(ServiceContext*) const final { + return TC::AllowedOnSecondary::kNever; + } +}; - auto lk = uassertStatusOK(requireWritableAuthSchema28SCRAM(opCtx, authzManager)); +class CmdCreateUser : public CmdUMCTyped { +public: + static constexpr StringData kPwdField = "pwd"_sd; - int authzVersion; - status = authzManager->getAuthorizationVersion(opCtx, &authzVersion); - uassertStatusOK(status); + StringData sensitiveFieldName() const final { + return kPwdField; + } +} cmdCreateUser; - BSONObjBuilder credentialsBuilder(userObjBuilder.subobjStart("credentials")); - status = buildCredentials(&credentialsBuilder, args); - uassertStatusOK(status); - credentialsBuilder.done(); +template <> +void CmdUMCTyped::Invocation::typedRun(OperationContext* opCtx) { + const auto& cmd = request(); + const auto& dbname = cmd.getDbName(); - if (args.authenticationRestrictions && !args.authenticationRestrictions->isEmpty()) { - credentialsBuilder.append("authenticationRestrictions", - *args.authenticationRestrictions); - } + // Validate input + uassert(ErrorCodes::BadValue, "Cannot create users in the local database", dbname != "local"); - if (args.hasCustomData) { - userObjBuilder.append("customData", args.customData); - } + uassert(ErrorCodes::BadValue, + "Username cannot contain NULL characters", + cmd.getCommandParameter().find('\0') == std::string::npos); + UserName userName(cmd.getCommandParameter(), dbname); - userObjBuilder.append("roles", rolesVectorToBSONArray(args.roles)); + 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")); - BSONObj userObj = userObjBuilder.obj(); - V2UserDocumentParser parser; - status = parser.checkValidUserDocument(userObj); - uassertStatusOK(status); + uassert(ErrorCodes::BadValue, + "Cannot set the password for users defined on the '$external' database", + (cmd.getPwd() == boost::none) || (dbname != "$external")); - // 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); - } + uassert(ErrorCodes::BadValue, + "mechanisms field must not be empty", + (cmd.getMechanisms() == boost::none) || !cmd.getMechanisms()->empty()); - 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; - } +#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 - StringData sensitiveFieldName() const final { - return "pwd"_sd; - } + // 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()); -} cmdCreateUser; + auto* serviceContext = opCtx->getClient()->getServiceContext(); + auto* authzManager = AuthorizationManager::get(serviceContext); -class CmdUpdateUser : public BasicCommand { -public: - CmdUpdateUser() : BasicCommand("updateUser") {} + auto lk = uassertStatusOK(requireWritableAuthSchema28SCRAM(opCtx, authzManager)); - AllowedOnSecondary secondaryAllowed(ServiceContext*) const override { - return AllowedOnSecondary::kNever; - } + int authzVersion; + uassertStatusOK(authzManager->getAuthorizationVersion(opCtx, &authzVersion)); - bool supportsWriteConcern(const BSONObj& cmd) const override { - return true; - } + BSONObjBuilder credentialsBuilder(userObjBuilder.subobjStart("credentials")); + buildCredentials(&credentialsBuilder, userName, cmd); + credentialsBuilder.done(); - std::string help() const override { - return "Used to update a user, for example to change its password"; + if (auto ar = cmd.getAuthenticationRestrictions(); ar && !ar->empty()) { + userObjBuilder.append("authenticationRestrictions", vectorToBSON(ar.get())); } - Status checkAuthForCommand(Client* client, - const std::string& dbname, - const BSONObj& cmdObj) const override { - return auth::checkAuthForUpdateUserCommand(client, dbname, cmdObj); + if (auto customData = cmd.getCustomData(); customData) { + userObjBuilder.append("customData", customData.get()); } - 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); + auto resolvedRoles = auth::resolveRoleNames(cmd.getRoles(), dbname); + userObjBuilder.append("roles", vectorToBSON(resolvedRoles)); + BSONObj userObj = userObjBuilder.obj(); - 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"); - } + // Validate contents + uassertStatusOK(V2UserDocumentParser().checkValidUserDocument(userObj)); - if (args.hasPassword && args.userName.getDB() == "$external") { - uasserted(ErrorCodes::BadValue, - "Cannot set the password for users defined on the '$external' " - "database"); - } + // 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); +} - BSONObjBuilder queryBuilder; - queryBuilder.append(AuthorizationManager::USER_NAME_FIELD_NAME, args.userName.getUser()); - queryBuilder.append(AuthorizationManager::USER_DB_FIELD_NAME, args.userName.getDB()); +class CmdUpdateUser : public CmdUMCTyped { +public: + static constexpr StringData kPwdField = "pwd"_sd; - 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); - } + StringData sensitiveFieldName() const final { + return kPwdField; + } +} cmdUpdateUser; - if (args.hasCustomData) { - updateSetBuilder.append("customData", args.customData); - } +template <> +void CmdUMCTyped::Invocation::typedRun(OperationContext* opCtx) { + const auto& cmd = request(); + const auto& dbname = cmd.getDbName(); + UserName userName(cmd.getCommandParameter(), dbname); - 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); - } - } + // 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()); - if (args.hasRoles) { - updateSetBuilder.append("roles", rolesVectorToBSONArray(args.roles)); - } + // Create set/update mutators. + BSONObjBuilder updateSetBuilder; + BSONObjBuilder updateUnsetBuilder; - BSONObj updateSet = updateSetBuilder.done(); - BSONObj updateUnset = updateUnsetBuilder.done(); - BSONObjBuilder updateDocumentBuilder; - if (!updateSet.isEmpty()) { - updateDocumentBuilder << "$set" << updateSet; - } - if (!updateUnset.isEmpty()) { - updateDocumentBuilder << "$unset" << updateUnset; - } + if (auto pwd = cmd.getPwd()) { + uassert(ErrorCodes::BadValue, + "Cannot set the password for users defined on the '$external' database", + userName.getDB() != "$external"); - ServiceContext* serviceContext = opCtx->getClient()->getServiceContext(); - AuthorizationManager* authzManager = AuthorizationManager::get(serviceContext); + BSONObjBuilder credentialsBuilder(updateSetBuilder.subobjStart("credentials")); + buildCredentials(&credentialsBuilder, userName, cmd); + credentialsBuilder.done(); + } else if (auto mechanisms = cmd.getMechanisms()) { + trimCredentials(opCtx, userName, &queryBuilder, &updateUnsetBuilder, mechanisms.get()); + } - auto lk = uassertStatusOK(requireWritableAuthSchema28SCRAM(opCtx, authzManager)); + if (auto customData = cmd.getCustomData()) { + updateSetBuilder.append("customData", customData.get()); + } - // 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); - } + if (auto ar = cmd.getAuthenticationRestrictions()) { + if (ar->empty()) { + updateUnsetBuilder.append("authenticationRestrictions", ""); + } else { + updateSetBuilder.append("authenticationRestrictions", vectorToBSON(ar.get())); } - - 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; } - StringData sensitiveFieldName() const final { - return "pwd"_sd; + boost::optional> optResolvedRoles; + if (auto roles = cmd.getRoles()) { + optResolvedRoles = auth::resolveRoleNames(roles.get(), dbname); + updateSetBuilder.append("roles", vectorToBSON(optResolvedRoles.get())); } -} cmdUpdateUser; - -class CmdDropUser : public BasicCommand { -public: - CmdDropUser() : BasicCommand("dropUser") {} - - AllowedOnSecondary secondaryAllowed(ServiceContext*) const override { - return AllowedOnSecondary::kNever; - } + BSONObj updateSet = updateSetBuilder.done(); + BSONObj updateUnset = updateUnsetBuilder.done(); - bool supportsWriteConcern(const BSONObj& cmd) const override { - return true; - } + uassert(ErrorCodes::BadValue, + "Must specify at least one field to update in updateUser", + !updateSet.isEmpty() || !updateUnset.isEmpty()); - std::string help() const override { - return "Drops a single user."; + // Merge set/update builders into a single update document. + BSONObjBuilder updateDocumentBuilder; + if (!updateSet.isEmpty()) { + updateDocumentBuilder.append("$set", updateSet); } - - Status checkAuthForCommand(Client* client, - const std::string& dbname, - const BSONObj& cmdObj) const override { - return auth::checkAuthForDropUserCommand(client, dbname, cmdObj); + if (!updateUnset.isEmpty()) { + updateDocumentBuilder.append("$unset", updateUnset); } - 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)); + auto* serviceContext = opCtx->getClient()->getServiceContext(); + auto* authzManager = AuthorizationManager::get(serviceContext); - 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); + auto lk = uassertStatusOK(requireWritableAuthSchema28SCRAM(opCtx, authzManager)); - if (nMatched == 0) { - uasserted(ErrorCodes::UserNotFound, - str::stream() << "User '" << userName.getFullName() << "' not found"); + // 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)); } - - return true; } -} cmdDropUser; - -class CmdDropAllUsersFromDatabase : public BasicCommand { -public: - CmdDropAllUsersFromDatabase() : BasicCommand("dropAllUsersFromDatabase") {} - - AllowedOnSecondary secondaryAllowed(ServiceContext*) const override { - return AllowedOnSecondary::kNever; + // 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); - bool supportsWriteConcern(const BSONObj& cmd) const override { - return true; - } + auto status = + updatePrivilegeDocument(opCtx, userName, queryBuilder.done(), updateDocumentBuilder.done()); - std::string help() const override { - return "Drops all users for a single database."; - } + // Must invalidate even on bad status - what if the write succeeded but the GLE failed? + authzManager->invalidateUserByName(opCtx, userName); + uassertStatusOK(status); +} - Status checkAuthForCommand(Client* client, - const std::string& dbname, - const BSONObj& cmdObj) const override { - return auth::checkAuthForDropAllUsersFromDatabaseCommand(client, dbname); - } +CmdUMCTyped cmdDropUser; +template <> +void CmdUMCTyped::Invocation::typedRun(OperationContext* opCtx) { + const auto& cmd = request(); + const auto& dbname = cmd.getDbName(); + UserName userName(cmd.getCommandParameter(), dbname); - 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* serviceContext = opCtx->getClient()->getServiceContext(); + auto* authzManager = AuthorizationManager::get(serviceContext); + auto lk = uassertStatusOK(requireWritableAuthSchema28SCRAM(opCtx, authzManager)); - auto lk = uassertStatusOK(requireWritableAuthSchema28SCRAM(opCtx, authzManager)); + audit::logDropUser(Client::getCurrent(), userName); - audit::logDropAllUsersFromDatabase(Client::getCurrent(), dbname); + std::int64_t numMatched; + auto status = removePrivilegeDocuments( + opCtx, + BSON(AuthorizationManager::USER_NAME_FIELD_NAME + << userName.getUser() << AuthorizationManager::USER_DB_FIELD_NAME << userName.getDB()), + &numMatched); - 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); + // Must invalidate even on bad status - what if the write succeeded but the GLE failed? + authzManager->invalidateUserByName(opCtx, userName); + uassertStatusOK(status); - result.append("n", numRemoved); - return true; - } + uassert(ErrorCodes::UserNotFound, + str::stream() << "User '" << userName.getFullName() << "' not found", + numMatched > 0); +} -} cmdDropAllUsersFromDatabase; +CmdUMCTyped + cmdDropAllUsersFromDatabase; +template <> +DropAllUsersFromDatabaseReply +CmdUMCTyped::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 CmdGrantRolesToUser : public BasicCommand { -public: - CmdGrantRolesToUser() : BasicCommand("grantRolesToUser") {} +CmdUMCTyped cmdGrantRolesToUser; +template <> +void CmdUMCTyped::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 "Grants roles to a user."; - } + stdx::unordered_set userRoles; + uassertStatusOK(getCurrentUserRoles(opCtx, authzManager, userName, &userRoles)); - Status checkAuthForCommand(Client* client, - const std::string& dbname, - const BSONObj& cmdObj) const override { - return auth::checkAuthForGrantRolesToUserCommand(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 roles; - Status status = auth::parseRolePossessionManipulationCommands( - cmdObj, "grantRolesToUser", 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 cmdRevokeRolesFromUser; +template <> +void CmdUMCTyped::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 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::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.insert(roleName); - } + stdx::unordered_set userRoles; + uassertStatusOK(getCurrentUserRoles(opCtx, authzManager, userName, &userRoles)); - 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; + auto resolvedUserRoles = auth::resolveRoleNames(cmd.getRoles(), dbname); + for (const auto& role : resolvedUserRoles) { + BSONObj roleDoc; + uassertStatusOK(authzManager->getRoleDescription(opCtx, role, &roleDoc)); + userRoles.erase(role); } -} cmdGrantRolesToUser; + audit::logRevokeRolesFromUser(client, userName, resolvedUserRoles); + BSONArray newRolesBSONArray = roleSetToBSONArray(userRoles); + auto status = updatePrivilegeDocument( + opCtx, userName, BSON("$set" << BSON("roles" << newRolesBSONArray))); -class CmdRevokeRolesFromUser : public BasicCommand { + // 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: - CmdRevokeRolesFromUser() : BasicCommand("revokeRolesFromUser") {} + CmdUsersInfo() : BasicCommand("usersInfo") {} AllowedOnSecondary secondaryAllowed(ServiceContext*) const override { - return AllowedOnSecondary::kNever; + return AllowedOnSecondary::kOptIn; } bool supportsWriteConcern(const BSONObj& cmd) const override { - return true; + return false; } std::string help() const override { - return "Revokes roles from a user."; + return "Returns information about users."; } Status checkAuthForCommand(Client* client, const std::string& dbname, const BSONObj& cmdObj) const override { - return auth::checkAuthForRevokeRolesFromUserCommand(client, dbname, cmdObj); + return auth::checkAuthForUsersInfoCommand(client, dbname, cmdObj); } bool run(OperationContext* opCtx, const std::string& dbname, const BSONObj& cmdObj, BSONObjBuilder& result) override { - std::string userNameString; - std::vector roles; - Status status = auth::parseRolePossessionManipulationCommands( - cmdObj, "revokeRolesFromUser", dbname, &userNameString, &roles); - uassertStatusOK(status); - - ServiceContext* serviceContext = opCtx->getClient()->getServiceContext(); - AuthorizationManager* authzManager = AuthorizationManager::get(serviceContext); - - auto lk = uassertStatusOK(requireWritableAuthSchema28SCRAM(opCtx, authzManager)); - - UserName userName(userNameString, dbname); - stdx::unordered_set userRoles; - status = getCurrentUserRoles(opCtx, authzManager, userName, &userRoles); + auth::UsersInfoArgs args; + Status status = auth::parseUsersInfoCommand(cmdObj, dbname, &args); uassertStatusOK(status); - for (std::vector::iterator it = roles.begin(); it != roles.end(); ++it) { - RoleName& roleName = *it; - BSONObj roleDoc; - status = authzManager->getRoleDescription(opCtx, roleName, &roleDoc); - uassertStatusOK(status); + AuthorizationManager* authzManager = AuthorizationManager::get(opCtx->getServiceContext()); + auto lk = uassertStatusOK(requireReadableAuthSchema26Upgrade(opCtx, authzManager)); - userRoles.erase(roleName); - } - - 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; - } - -} cmdRevokeRolesFromUser; - -class CmdUsersInfo : public BasicCommand { -public: - CmdUsersInfo() : BasicCommand("usersInfo") {} - - AllowedOnSecondary secondaryAllowed(ServiceContext*) const override { - return AllowedOnSecondary::kOptIn; - } - - bool supportsWriteConcern(const BSONObj& cmd) const override { - return false; - } - - std::string help() const override { - return "Returns information about users."; - } - - Status checkAuthForCommand(Client* client, - const std::string& dbname, - const BSONObj& cmdObj) const override { - return auth::checkAuthForUsersInfoCommand(client, dbname, cmdObj); - } - - bool run(OperationContext* opCtx, - const std::string& dbname, - const BSONObj& cmdObj, - BSONObjBuilder& result) override { - auth::UsersInfoArgs args; - Status status = auth::parseUsersInfoCommand(cmdObj, dbname, &args); - uassertStatusOK(status); - - AuthorizationManager* authzManager = AuthorizationManager::get(opCtx->getServiceContext()); - auto lk = uassertStatusOK(requireReadableAuthSchema26Upgrade(opCtx, authzManager)); - - if ((args.target != auth::UsersInfoArgs::Target::kExplicitUsers || args.filter) && - (args.showPrivileges || - args.authenticationRestrictionsFormat == AuthenticationRestrictionsFormat::kShow)) { - uasserted(ErrorCodes::IllegalOperation, - "Privilege or restriction details require exact-match usersInfo " - "queries."); + if ((args.target != auth::UsersInfoArgs::Target::kExplicitUsers || args.filter) && + (args.showPrivileges || + args.authenticationRestrictionsFormat == AuthenticationRestrictionsFormat::kShow)) { + uasserted(ErrorCodes::IllegalOperation, + "Privilege or restriction details require exact-match usersInfo " + "queries."); } BSONArrayBuilder usersArrayBuilder; @@ -1396,777 +1301,515 @@ public: } cmdUsersInfo; -class CmdCreateRole : public BasicCommand { -public: - CmdCreateRole() : BasicCommand("createRole") {} +CmdUMCTyped cmdCreateRole; +template <> +void CmdUMCTyped::Invocation::typedRun(OperationContext* opCtx) { + const auto& cmd = request(); + const auto& dbname = cmd.getDbName(); - AllowedOnSecondary secondaryAllowed(ServiceContext*) const override { - return AllowedOnSecondary::kNever; - } + uassert( + ErrorCodes::BadValue, "Role name must be non-empty", !cmd.getCommandParameter().empty()); + RoleName roleName(cmd.getCommandParameter(), dbname); - bool supportsWriteConcern(const BSONObj& cmd) const override { - return true; - } + uassert(ErrorCodes::BadValue, "Cannot create roles in the local database", dbname != "local"); - std::string help() const override { - return "Adds a role to the system"; - } + uassert(ErrorCodes::BadValue, + "Cannot create roles in the $external database", + dbname != "$external"); - 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); + uassert(ErrorCodes::BadValue, + "Cannot create roles with the same name as a built-in role", + !RoleGraph::isBuiltinRole(roleName)); - if (args.roleName.getRole().empty()) { - uasserted(ErrorCodes::BadValue, "Role name must be non-empty"); - } + 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()); - if (args.roleName.getDB() == "local") { - uasserted(ErrorCodes::BadValue, "Cannot create roles in the local database"); - } + BSONArray privileges; + uassertStatusOK(privilegeVectorToBSONArray(cmd.getPrivileges(), &privileges)); + roleObjBuilder.append("privileges", privileges); - 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()); - } + auto resolvedRoleNames = auth::resolveRoleNames(cmd.getRoles(), dbname); + roleObjBuilder.append("roles", rolesVectorToBSONArray(resolvedRoleNames)); - 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); - - audit::logCreateRole(Client::getCurrent(), - args.roleName, - args.roles, - args.privileges, - args.authenticationRestrictions); - - status = insertRoleDocument(opCtx, roleObjBuilder.done()); - uassertStatusOK(status); - return true; + boost::optional bsonAuthRestrictions; + if (auto ar = cmd.getAuthenticationRestrictions(); ar && !ar->empty()) { + bsonAuthRestrictions = vectorToBSON(ar.get()); + roleObjBuilder.append("authenticationRestrictions", bsonAuthRestrictions.get()); } -} cmdCreateRole; - -class CmdUpdateRole : public BasicCommand { -public: - CmdUpdateRole() : BasicCommand("updateRole") {} + auto* client = opCtx->getClient(); + auto* serviceContext = client->getServiceContext(); + auto* authzManager = AuthorizationManager::get(serviceContext); + auto lk = uassertStatusOK(requireWritableAuthSchema28SCRAM(opCtx, authzManager)); - AllowedOnSecondary secondaryAllowed(ServiceContext*) const override { - return AllowedOnSecondary::kNever; - } + // Role existence has to be checked after acquiring the update lock + uassertStatusOK(checkOkayToGrantRolesToRole(opCtx, roleName, resolvedRoleNames, authzManager)); + uassertStatusOK(checkOkayToGrantPrivilegesToRole(roleName, cmd.getPrivileges())); - bool supportsWriteConcern(const BSONObj& cmd) const override { - return true; - } + audit::logCreateRole( + client, roleName, resolvedRoleNames, cmd.getPrivileges(), bsonAuthRestrictions); - std::string help() const override { - return "Used to update a role"; - } - - Status checkAuthForCommand(Client* client, - const std::string& dbname, - const BSONObj& cmdObj) const override { - return auth::checkAuthForUpdateRoleCommand(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, "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"); - } - - 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)); - } - - 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); - } - - 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; - } + uassertStatusOK(insertRoleDocument(opCtx, roleObjBuilder.done())); +} -} cmdUpdateRole; +CmdUMCTyped cmdUpdateRole; +template <> +void CmdUMCTyped::Invocation::typedRun(OperationContext* opCtx) { + const auto& cmd = request(); + const auto& dbname = cmd.getDbName(); + RoleName roleName(cmd.getCommandParameter(), dbname); -class CmdGrantPrivilegesToRole : public BasicCommand { -public: - CmdGrantPrivilegesToRole() : BasicCommand("grantPrivilegesToRole") {} + 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); - AllowedOnSecondary secondaryAllowed(ServiceContext*) const override { - return AllowedOnSecondary::kNever; - } + BSONObjBuilder updateSetBuilder; + BSONObjBuilder updateUnsetBuilder; - bool supportsWriteConcern(const BSONObj& cmd) const override { - return true; - } - - 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> 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 cmdGrantPrivilegesToRole; +template <> +void CmdUMCTyped::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 cmdRevokePrivilegesFromRole; +template <> +void CmdUMCTyped::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; + // 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)); -class CmdGrantRolesToRole : public BasicCommand { -public: - CmdGrantRolesToRole() : BasicCommand("grantRolesToRole") {} + audit::logRevokePrivilegesFromRole(client, roleName, cmd.getPrivileges()); - AllowedOnSecondary secondaryAllowed(ServiceContext*) const override { - return AllowedOnSecondary::kNever; - } - - 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 cmdGrantRolesToRole; +template <> +void CmdUMCTyped::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 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 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 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 cmdRevokeRolesFromRole; +template <> +void CmdUMCTyped::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 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); + BSONObj roleDoc; + uassertStatusOK(authzManager->getRoleDescription(opCtx, roleName, &roleDoc)); - auto lk = uassertStatusOK(requireWritableAuthSchema28SCRAM(opCtx, authzManager)); + std::vector roles; + uassertStatusOK(auth::parseRoleNamesFromBSONArray( + BSONArray(roleDoc["roles"].Obj()), roleName.getDB(), &roles)); - RoleName roleName(roleNameString, dbname); - if (RoleGraph::isBuiltinRole(roleName)) { - uasserted(ErrorCodes::InvalidRoleModification, - str::stream() << roleName.getFullName() - << " is a built-in role and cannot be modified."); + for (const auto& roleToRemove : rolesToRemove) { + if (auto it = std::find(roles.begin(), roles.end(), roleToRemove); it != roles.end()) { + roles.erase(it); } - - BSONObj roleDoc; - status = authzManager->getRoleDescription(opCtx, roleName, &roleDoc); - uassertStatusOK(status); - - std::vector roles; - status = auth::parseRoleNamesFromBSONArray( - BSONArray(roleDoc["roles"].Obj()), roleName.getDB(), &roles); - uassertStatusOK(status); - - for (std::vector::const_iterator it = rolesToRemove.begin(); - it != rolesToRemove.end(); - ++it) { - std::vector::iterator itToRm = std::find(roles.begin(), roles.end(), *it); - if (itToRm != roles.end()) { - roles.erase(itToRm); - } - } - - 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") {} - - AllowedOnSecondary secondaryAllowed(ServiceContext*) const override { - return AllowedOnSecondary::kNever; } - bool supportsWriteConcern(const BSONObj& cmd) const override { - return true; - } + audit::logRevokeRolesFromRole(client, roleName, rolesToRemove); - 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 cmdDropRole; +template <> +void CmdUMCTyped::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; + audit::logDropRole(client, roleName); -class CmdDropAllRolesFromDatabase : public BasicCommand { -public: - CmdDropAllRolesFromDatabase() : BasicCommand("dropAllRolesFromDatabase") {} - - 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 + cmdDropAllRolesFromDatabase; +template <> +DropAllRolesFromDatabaseReply +CmdUMCTyped::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::iterator it = rolesToDrop.begin(); it != rolesToDrop.end(); ++it) { diff --git a/src/mongo/db/commands/user_management_commands.idl b/src/mongo/db/commands/user_management_commands.idl new file mode 100644 index 00000000000..53437d600c0 --- /dev/null +++ b/src/mongo/db/commands/user_management_commands.idl @@ -0,0 +1,263 @@ +# Copyright (C) 2020-present MongoDB, Inc. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the Server Side Public License, version 1, +# as published by MongoDB, Inc. +# +# 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 +# Server Side Public License for more details. +# +# You should have received a copy of the Server Side 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 Server Side 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. +# +global: + cpp_namespace: "mongo" + +imports: + - "mongo/idl/basic_types.idl" + - "mongo/db/auth/auth_types.idl" + - "mongo/db/auth/address_restriction.idl" + +structs: + dropAllUsersFromDatabaseReply: + description: "Response for dropAllUsersFromDatabase command" + strict: false + fields: + n: + description: "Number of users dropped from database" + type: int + cpp_name: count + + dropAllRolesFromDatabaseReply: + description: "Response for dropAllRolesFromDatabase command" + strict: false + fields: + n: + description: "Number of roles dropped from database" + type: int + cpp_name: count + +commands: + createUser: + description: "Create a user" + namespace: type + type: string + cpp_name: CreateUserCommand + strict: true + fields: + pwd: + description: "Initial user password" + type: string + optional: true + customData: + description: "Any arbitrary data" + type: object + optional: true + roles: + description: "Initial roles to grant to the user" + type: array + digestPassword: + description: "True if the server should digest the password, false for pre-digested" + type: bool + default: true + writeConcern: + description: "The level of write concern for the creation operation" + type: object + optional: true + authenticationRestrictions: + description: "Authentication restrictions to enforce on the user" + type: array + optional: true + mechanisms: + description: "List of valid authentication mechanisms for the user" + type: array + optional: true + + updateUser: + description: "Modify a user" + namespace: type + type: string + cpp_name: UpdateUserCommand + strict: true + fields: + pwd: + description: "New user password" + type: string + optional: true + customData: + description: "Any arbitrary data" + type: object + optional: true + roles: + description: "New set of roles for the user" + type: array + optional: true + digestPassword: + description: "True if the server should digest the password, false for pre-digested" + type: bool + default: true + writeConcern: + description: "The level of write concern for the update operation" + type: object + optional: true + authenticationRestrictions: + description: "Authentication restrictions to enforce on the user" + type: array + optional: true + mechanisms: + description: "List of valid authentication mechanisms for the user" + type: array + optional: true + + dropUser: + description: "Drop a single user" + namespace: type + type: string + cpp_name: DropUserCommand + strict: true + + dropAllUsersFromDatabase: + description: "Drop all users in the database" + namespace: ignored + cpp_name: DropAllUsersFromDatabaseCommand + strict: true + + grantRolesToUser: + description: "Grant additional roles to a user" + namespace: type + type: string + cpp_name: GrantRolesToUserCommand + strict: true + fields: + roles: + description: "Roles to grant to the user" + type: array + + revokeRolesFromUser: + description: "Revoke previously assigned roles from a user" + namespace: type + type: string + cpp_name: RevokeRolesFromUserCommand + strict: true + fields: + roles: + description: "Roles to revoke from the user" + type: array + + createRole: + description: "Create a new role" + namespace: type + type: string + cpp_name: CreateRoleCommand + strict: true + fields: + privileges: + description: "Actions explicitly granted to this role" + type: array + roles: + description: "Roles to inherit additional privileges from" + type: array + authenticationRestrictions: + description: "Authentication restrictions to enforce on the user" + type: array + optional: true + + updateRole: + description: "Update an existing role" + namespace: type + type: string + cpp_name: UpdateRoleCommand + strict: true + fields: + privileges: + description: "Actions explicitly granted to this role" + type: array + optional: true + roles: + description: "Roles to inherit additional privileges from" + type: array + optional: true + authenticationRestrictions: + description: "Authentication restrictions to enforce on the user" + type: array + optional: true + + grantPrivilegesToRole: + description: "Grants privileges to a role" + namespace: type + type: string + cpp_name: GrantPrivilegesToRoleCommand + strict: true + fields: + privileges: + description: "Privileges to grant to this role" + type: array + + revokePrivilegesFromRole: + description: "Grants privileges to a role" + namespace: type + type: string + cpp_name: RevokePrivilegesFromRoleCommand + strict: true + fields: + privileges: + description: "Privileges to revoke from this role" + type: array + + grantRolesToRole: + description: "Grant roles to a role" + namespace: type + type: string + cpp_name: GrantRolesToRoleCommand + strict: true + fields: + roles: + description: "Roles to grant to this role" + type: array + + revokeRolesFromRole: + description: "Revoke roles from a role" + namespace: type + type: string + cpp_name: RevokeRolesFromRoleCommand + strict: true + fields: + roles: + description: "Roles to revoke from this role" + type: array + + dropRole: + description: >- + 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. + namespace: type + type: string + cpp_name: DropRoleCommand + strict: true + + dropAllRolesFromDatabase: + description: >- + 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. + namespace: ignored + cpp_name: DropAllRolesFromDatabaseCommand + strict: true diff --git a/src/mongo/db/commands/user_management_commands_common.cpp b/src/mongo/db/commands/user_management_commands_common.cpp index c2be6896ced..83df92fae4c 100644 --- a/src/mongo/db/commands/user_management_commands_common.cpp +++ b/src/mongo/db/commands/user_management_commands_common.cpp @@ -43,6 +43,7 @@ #include "mongo/db/auth/resource_pattern.h" #include "mongo/db/auth/user.h" #include "mongo/db/auth/user_management_commands_parser.h" +#include "mongo/db/commands/user_management_commands_gen.h" #include "mongo/db/jsobj.h" #include "mongo/util/sequence_util.h" #include "mongo/util/str.h" @@ -50,6 +51,16 @@ namespace mongo { namespace auth { +std::vector resolveRoleNames(const std::vector& possibleRoles, + StringData dbname) { + std::vector roles; + std::transform(possibleRoles.cbegin(), + possibleRoles.cend(), + std::back_inserter(roles), + [dbname](const auto& possibleRole) { return possibleRole.getRoleName(dbname); }); + return roles; +} + Status checkAuthorizedToGrantRoles(AuthorizationSession* authzSession, const std::vector& roles) { for (size_t i = 0; i < roles.size(); ++i) { @@ -113,289 +124,153 @@ Status checkAuthorizedToSetRestrictions(AuthorizationSession* authzSession, return Status::OK(); } -Status checkAuthForCreateUserCommand(Client* client, - const std::string& dbname, - const BSONObj& cmdObj) { - AuthorizationSession* authzSession = AuthorizationSession::get(client); - auth::CreateOrUpdateUserArgs args; - Status status = auth::parseCreateOrUpdateUserCommands(cmdObj, "createUser", dbname, &args); - if (!status.isOK()) { - return status; - } +void checkAuthForTypedCommand(Client* client, const CreateUserCommand& request) { + const auto& dbname = request.getDbName(); + auto* as = AuthorizationSession::get(client); - if (!authzSession->isAuthorizedForActionsOnResource( - ResourcePattern::forDatabaseName(args.userName.getDB()), ActionType::createUser)) { - return Status(ErrorCodes::Unauthorized, - str::stream() - << "Not authorized to create users on db: " << args.userName.getDB()); - } + uassert(ErrorCodes::Unauthorized, + str::stream() << "Not authorized to create users on db: " << dbname, + as->isAuthorizedForActionsOnResource(ResourcePattern::forDatabaseName(dbname), + ActionType::createUser)); - status = checkAuthorizedToGrantRoles(authzSession, args.roles); - if (!status.isOK()) { - return status; - } - - status = checkAuthorizedToSetRestrictions( - authzSession, static_cast(args.authenticationRestrictions), args.userName.getDB()); - if (!status.isOK()) { - return status; - } + auto resolvedRoles = resolveRoleNames(request.getRoles(), dbname); + uassertStatusOK(checkAuthorizedToGrantRoles(as, resolvedRoles)); - return Status::OK(); + uassertStatusOK(checkAuthorizedToSetRestrictions( + as, request.getAuthenticationRestrictions() != boost::none, dbname)); } -Status checkAuthForUpdateUserCommand(Client* client, - const std::string& dbname, - const BSONObj& cmdObj) { - AuthorizationSession* authzSession = AuthorizationSession::get(client); - auth::CreateOrUpdateUserArgs args; - Status status = auth::parseCreateOrUpdateUserCommands(cmdObj, "updateUser", dbname, &args); - if (!status.isOK()) { - return status; - } - - if (args.hasPassword) { - if (!authzSession->isAuthorizedToChangeOwnPasswordAsUser(args.userName) && - !authzSession->isAuthorizedForActionsOnResource( - ResourcePattern::forDatabaseName(args.userName.getDB()), - ActionType::changePassword)) { - return Status(ErrorCodes::Unauthorized, - str::stream() << "Not authorized to change password of user: " - << args.userName.getFullName()); - } - } - - if (args.hasCustomData) { - if (!authzSession->isAuthorizedToChangeOwnCustomDataAsUser(args.userName) && - !authzSession->isAuthorizedForActionsOnResource( - ResourcePattern::forDatabaseName(args.userName.getDB()), - ActionType::changeCustomData)) { - return Status(ErrorCodes::Unauthorized, - str::stream() << "Not authorized to change customData of user: " - << args.userName.getFullName()); - } - } - - if (args.hasRoles) { +void checkAuthForTypedCommand(Client* client, const UpdateUserCommand& request) { + const auto& dbname = request.getDbName(); + auto* as = AuthorizationSession::get(client); + + UserName userName(request.getCommandParameter(), dbname); + uassert( + ErrorCodes::Unauthorized, + str::stream() << "Not authorized to change password of user: " << userName.getFullName(), + (request.getPwd() == boost::none) || as->isAuthorizedToChangeOwnPasswordAsUser(userName) || + as->isAuthorizedForActionsOnResource(ResourcePattern::forDatabaseName(dbname), + ActionType::changePassword)); + + uassert(ErrorCodes::Unauthorized, + str::stream() << "Not authorized to change customData of user: " + << userName.getFullName(), + (request.getCustomData() == boost::none) || + as->isAuthorizedToChangeOwnCustomDataAsUser(userName) || + as->isAuthorizedForActionsOnResource(ResourcePattern::forDatabaseName(dbname), + ActionType::changeCustomData)); + + if (auto possibleRoles = request.getRoles()) { // You don't know what roles you might be revoking, so require the ability to // revoke any role in the system. - if (!authzSession->isAuthorizedForActionsOnResource(ResourcePattern::forAnyNormalResource(), - ActionType::revokeRole)) { - return Status(ErrorCodes::Unauthorized, - "In order to use updateUser to set roles array, must be " - "authorized to revoke any role in the system"); - } + uassert(ErrorCodes::Unauthorized, + "In order to use updateUser to set roles array, must be " + "authorized to revoke any role in the system", + as->isAuthorizedForActionsOnResource(ResourcePattern::forAnyNormalResource(), + ActionType::revokeRole)); - status = checkAuthorizedToGrantRoles(authzSession, args.roles); - if (!status.isOK()) { - return status; - } - } - - status = checkAuthorizedToSetRestrictions( - authzSession, static_cast(args.authenticationRestrictions), args.userName.getDB()); - if (!status.isOK()) { - return status; + auto resolvedRoles = resolveRoleNames(possibleRoles.get(), dbname); + uassertStatusOK(checkAuthorizedToGrantRoles(as, resolvedRoles)); } - return Status::OK(); + uassertStatusOK(checkAuthorizedToSetRestrictions( + as, request.getAuthenticationRestrictions() != boost::none, dbname)); } -Status checkAuthForGrantRolesToUserCommand(Client* client, - const std::string& dbname, - const BSONObj& cmdObj) { - AuthorizationSession* authzSession = AuthorizationSession::get(client); - std::vector roles; - std::string unusedUserNameString; - Status status = auth::parseRolePossessionManipulationCommands( - cmdObj, "grantRolesToUser", dbname, &unusedUserNameString, &roles); - if (!status.isOK()) { - return status; - } - - return checkAuthorizedToGrantRoles(authzSession, roles); +void checkAuthForTypedCommand(Client* client, const GrantRolesToUserCommand& request) { + auto roles = resolveRoleNames(request.getRoles(), request.getDbName()); + auto* as = AuthorizationSession::get(client); + uassertStatusOK(checkAuthorizedToGrantRoles(as, roles)); } -Status checkAuthForCreateRoleCommand(Client* client, - const std::string& dbname, - const BSONObj& cmdObj) { - AuthorizationSession* authzSession = AuthorizationSession::get(client); - auth::CreateOrUpdateRoleArgs args; - Status status = auth::parseCreateOrUpdateRoleCommands(cmdObj, "createRole", dbname, &args); - if (!status.isOK()) { - return status; - } +void checkAuthForTypedCommand(Client* client, const CreateRoleCommand& request) { + auto* as = AuthorizationSession::get(client); + const auto& dbname = request.getDbName(); + RoleName roleName(request.getCommandParameter(), dbname); - if (!authzSession->isAuthorizedToCreateRole(args)) { - return Status(ErrorCodes::Unauthorized, - str::stream() - << "Not authorized to create roles on db: " << args.roleName.getDB()); - } - - status = checkAuthorizedToGrantRoles(authzSession, args.roles); - if (!status.isOK()) { - return status; - } - - status = checkAuthorizedToGrantPrivileges(authzSession, args.privileges); - if (!status.isOK()) { - return status; - } - - status = checkAuthorizedToSetRestrictions( - authzSession, static_cast(args.authenticationRestrictions), args.roleName.getDB()); - if (!status.isOK()) { - return status; - } + uassert(ErrorCodes::Unauthorized, + str::stream() << "Not authorized to create roles on db: " << dbname, + as->isAuthorizedToCreateRole(roleName)); - return Status::OK(); + uassertStatusOK(checkAuthorizedToGrantRoles(as, resolveRoleNames(request.getRoles(), dbname))); + uassertStatusOK(checkAuthorizedToGrantPrivileges(as, request.getPrivileges())); + uassertStatusOK(checkAuthorizedToSetRestrictions( + as, request.getAuthenticationRestrictions() != boost::none, dbname)); } -Status checkAuthForUpdateRoleCommand(Client* client, - const std::string& dbname, - const BSONObj& cmdObj) { - AuthorizationSession* authzSession = AuthorizationSession::get(client); - auth::CreateOrUpdateRoleArgs args; - Status status = auth::parseCreateOrUpdateRoleCommands(cmdObj, "updateRole", dbname, &args); - if (!status.isOK()) { - return status; - } +void checkAuthForTypedCommand(Client* client, const UpdateRoleCommand& request) { + auto* as = AuthorizationSession::get(client); + const auto& dbname = request.getDbName(); // You don't know what roles or privileges you might be revoking, so require the ability // to revoke any role (or privilege) in the system. - if (!authzSession->isAuthorizedForActionsOnResource(ResourcePattern::forAnyNormalResource(), - ActionType::revokeRole)) { - return Status(ErrorCodes::Unauthorized, - "updateRole command required the ability to revoke any role in the " - "system"); - } + uassert(ErrorCodes::Unauthorized, + "updateRole command required the ability to revoke any role in the system", + as->isAuthorizedForActionsOnResource(ResourcePattern::forAnyNormalResource(), + ActionType::revokeRole)); - status = checkAuthorizedToGrantRoles(authzSession, args.roles); - if (!status.isOK()) { - return status; + if (auto roles = request.getRoles()) { + auto resolvedRoles = resolveRoleNames(roles.get(), dbname); + uassertStatusOK(checkAuthorizedToGrantRoles(as, resolvedRoles)); } - - status = checkAuthorizedToGrantPrivileges(authzSession, args.privileges); - if (!status.isOK()) { - return status; - } - - status = checkAuthorizedToSetRestrictions( - authzSession, static_cast(args.authenticationRestrictions), args.roleName.getDB()); - if (!status.isOK()) { - return status; + if (auto privs = request.getPrivileges()) { + uassertStatusOK(checkAuthorizedToGrantPrivileges(as, privs.get())); } - - return Status::OK(); + uassertStatusOK(checkAuthorizedToSetRestrictions( + as, request.getAuthenticationRestrictions() != boost::none, dbname)); } -Status checkAuthForGrantRolesToRoleCommand(Client* client, - const std::string& dbname, - const BSONObj& cmdObj) { - AuthorizationSession* authzSession = AuthorizationSession::get(client); - std::vector roles; - std::string unusedUserNameString; - Status status = auth::parseRolePossessionManipulationCommands( - cmdObj, "grantRolesToRole", dbname, &unusedUserNameString, &roles); - if (!status.isOK()) { - return status; - } - - return checkAuthorizedToGrantRoles(authzSession, roles); +void checkAuthForTypedCommand(Client* client, const GrantRolesToRoleCommand& request) { + auto rolesToRemove = resolveRoleNames(request.getRoles(), request.getDbName()); + auto* as = AuthorizationSession::get(client); + uassertStatusOK(checkAuthorizedToGrantRoles(as, rolesToRemove)); } -Status checkAuthForGrantPrivilegesToRoleCommand(Client* client, - const std::string& dbname, - const BSONObj& cmdObj) { - AuthorizationSession* authzSession = AuthorizationSession::get(client); - PrivilegeVector privileges; - RoleName unusedRoleName; - Status status = auth::parseAndValidateRolePrivilegeManipulationCommands( - cmdObj, "grantPrivilegesToRole", dbname, &unusedRoleName, &privileges); - if (!status.isOK()) { - return status; - } - - return checkAuthorizedToGrantPrivileges(authzSession, privileges); +void checkAuthForTypedCommand(Client* client, const GrantPrivilegesToRoleCommand& request) { + auto* as = AuthorizationSession::get(client); + uassertStatusOK(checkAuthorizedToGrantPrivileges(as, request.getPrivileges())); } -Status checkAuthForDropUserCommand(Client* client, - const std::string& dbname, - const BSONObj& cmdObj) { - AuthorizationSession* authzSession = AuthorizationSession::get(client); - UserName userName; - Status status = auth::parseAndValidateDropUserCommand(cmdObj, dbname, &userName); - if (!status.isOK()) { - return status; - } +void checkAuthForTypedCommand(Client* client, const DropUserCommand& request) { + auto* as = AuthorizationSession::get(client); + UserName userName(request.getCommandParameter(), request.getDbName()); - if (!authzSession->isAuthorizedForActionsOnResource( - ResourcePattern::forDatabaseName(userName.getDB()), ActionType::dropUser)) { - return Status(ErrorCodes::Unauthorized, - str::stream() << "Not authorized to drop users from the " << userName.getDB() - << " database"); - } - return Status::OK(); + uassert(ErrorCodes::Unauthorized, + str::stream() << "Not authorized to drop users from the " << userName.getDB() + << " database", + as->isAuthorizedForActionsOnResource(ResourcePattern::forDatabaseName(userName.getDB()), + ActionType::dropUser)); } -Status checkAuthForDropRoleCommand(Client* client, - const std::string& dbname, - const BSONObj& cmdObj) { - AuthorizationSession* authzSession = AuthorizationSession::get(client); - RoleName roleName; - Status status = auth::parseDropRoleCommand(cmdObj, dbname, &roleName); - if (!status.isOK()) { - return status; - } +void checkAuthForTypedCommand(Client* client, const DropRoleCommand& request) { + const auto& dbname = request.getDbName(); + auto* as = AuthorizationSession::get(client); - if (!authzSession->isAuthorizedForActionsOnResource( - ResourcePattern::forDatabaseName(roleName.getDB()), ActionType::dropRole)) { - return Status(ErrorCodes::Unauthorized, - str::stream() << "Not authorized to drop roles from the " << roleName.getDB() - << " database"); - } - return Status::OK(); + uassert(ErrorCodes::Unauthorized, + str::stream() << "Not authorized to drop roles from the " << dbname << " database", + as->isAuthorizedForActionsOnResource(ResourcePattern::forDatabaseName(dbname), + ActionType::dropRole)); } -Status checkAuthForDropAllUsersFromDatabaseCommand(Client* client, const std::string& dbname) { - AuthorizationSession* authzSession = AuthorizationSession::get(client); - if (!authzSession->isAuthorizedForActionsOnResource(ResourcePattern::forDatabaseName(dbname), - ActionType::dropUser)) { - return Status(ErrorCodes::Unauthorized, - str::stream() - << "Not authorized to drop users from the " << dbname << " database"); - } - return Status::OK(); +void checkAuthForTypedCommand(Client* client, const DropAllUsersFromDatabaseCommand& request) { + const auto& dbname = request.getDbName(); + auto* as = AuthorizationSession::get(client); + uassert(ErrorCodes::Unauthorized, + str::stream() << "Not authorized to drop users from the " << dbname << " database", + as->isAuthorizedForActionsOnResource(ResourcePattern::forDatabaseName(dbname), + ActionType::dropUser)); } -Status checkAuthForRevokeRolesFromUserCommand(Client* client, - const std::string& dbname, - const BSONObj& cmdObj) { - AuthorizationSession* authzSession = AuthorizationSession::get(client); - std::vector roles; - std::string unusedUserNameString; - Status status = auth::parseRolePossessionManipulationCommands( - cmdObj, "revokeRolesFromUser", dbname, &unusedUserNameString, &roles); - if (!status.isOK()) { - return status; - } - - return checkAuthorizedToRevokeRoles(authzSession, roles); +void checkAuthForTypedCommand(Client* client, const RevokeRolesFromUserCommand& request) { + auto roles = resolveRoleNames(request.getRoles(), request.getDbName()); + auto* as = AuthorizationSession::get(client); + uassertStatusOK(checkAuthorizedToRevokeRoles(as, roles)); } -Status checkAuthForRevokeRolesFromRoleCommand(Client* client, - const std::string& dbname, - const BSONObj& cmdObj) { - AuthorizationSession* authzSession = AuthorizationSession::get(client); - std::vector roles; - std::string unusedUserNameString; - Status status = auth::parseRolePossessionManipulationCommands( - cmdObj, "revokeRolesFromRole", dbname, &unusedUserNameString, &roles); - if (!status.isOK()) { - return status; - } - - return checkAuthorizedToRevokeRoles(authzSession, roles); +void checkAuthForTypedCommand(Client* client, const RevokeRolesFromRoleCommand& request) { + auto rolesToRemove = resolveRoleNames(request.getRoles(), request.getDbName()); + auto* as = AuthorizationSession::get(client); + uassertStatusOK(checkAuthorizedToRevokeRoles(as, rolesToRemove)); } Status checkAuthForUsersInfoCommand(Client* client, @@ -439,30 +314,18 @@ Status checkAuthForUsersInfoCommand(Client* client, return Status::OK(); } -Status checkAuthForRevokePrivilegesFromRoleCommand(Client* client, - const std::string& dbname, - const BSONObj& cmdObj) { - AuthorizationSession* authzSession = AuthorizationSession::get(client); - PrivilegeVector privileges; - RoleName unusedRoleName; - Status status = auth::parseAndValidateRolePrivilegeManipulationCommands( - cmdObj, "revokePrivilegesFromRole", dbname, &unusedRoleName, &privileges); - if (!status.isOK()) { - return status; - } - - return checkAuthorizedToRevokePrivileges(authzSession, privileges); +void checkAuthForTypedCommand(Client* client, const RevokePrivilegesFromRoleCommand& request) { + auto* as = AuthorizationSession::get(client); + uassertStatusOK(checkAuthorizedToRevokePrivileges(as, request.getPrivileges())); } -Status checkAuthForDropAllRolesFromDatabaseCommand(Client* client, const std::string& dbname) { - AuthorizationSession* authzSession = AuthorizationSession::get(client); - if (!authzSession->isAuthorizedForActionsOnResource(ResourcePattern::forDatabaseName(dbname), - ActionType::dropRole)) { - return Status(ErrorCodes::Unauthorized, - str::stream() - << "Not authorized to drop roles from the " << dbname << " database"); - } - return Status::OK(); +void checkAuthForTypedCommand(Client* client, const DropAllRolesFromDatabaseCommand& request) { + const auto& dbname = request.getDbName(); + auto* as = AuthorizationSession::get(client); + uassert(ErrorCodes::Unauthorized, + str::stream() << "Not authorized to drop roles from the " << dbname << " database", + as->isAuthorizedForActionsOnResource(ResourcePattern::forDatabaseName(dbname), + ActionType::dropRole)); } Status checkAuthForRolesInfoCommand(Client* client, diff --git a/src/mongo/db/commands/user_management_commands_common.h b/src/mongo/db/commands/user_management_commands_common.h index 4ddeec5be1c..7bc4caaffe3 100644 --- a/src/mongo/db/commands/user_management_commands_common.h +++ b/src/mongo/db/commands/user_management_commands_common.h @@ -32,10 +32,12 @@ #include #include +#include "mongo/base/string_data.h" #include "mongo/bson/mutable/element.h" #include "mongo/db/auth/privilege.h" #include "mongo/db/auth/role_name.h" #include "mongo/db/auth/user_name.h" +#include "mongo/db/commands/user_management_commands_gen.h" namespace mongo { @@ -47,6 +49,16 @@ class OperationContext; namespace auth { +/** + * User management commands accept rolenames as either `{ role: 'x', db: 'y' }` + * or as simply a string implying the role name and the dbname is inferred from the command. + * + * This method takes a vector of RoleNameOrString values parsed via IDL + * and normalizes them to a vector of RoleNames using a passed dbname fallback. + */ +std::vector resolveRoleNames(const std::vector& possibleRoles, + StringData dbname); + // // checkAuthorizedTo* methods // @@ -67,63 +79,25 @@ Status checkAuthorizedToRevokePrivileges(AuthorizationSession* authzSession, // checkAuthFor*Command methods // -Status checkAuthForCreateUserCommand(Client* client, - const std::string& dbname, - const BSONObj& cmdObj); - -Status checkAuthForUpdateUserCommand(Client* client, - const std::string& dbname, - const BSONObj& cmdObj); - -Status checkAuthForGrantRolesToUserCommand(Client* client, - const std::string& dbname, - const BSONObj& cmdObj); - -Status checkAuthForCreateRoleCommand(Client* client, - const std::string& dbname, - const BSONObj& cmdObj); - -Status checkAuthForUpdateRoleCommand(Client* client, - const std::string& dbname, - const BSONObj& cmdObj); - -Status checkAuthForGrantRolesToRoleCommand(Client* client, - const std::string& dbname, - const BSONObj& cmdObj); - -Status checkAuthForGrantPrivilegesToRoleCommand(Client* client, - const std::string& dbname, - const BSONObj& cmdObj); - -Status checkAuthForDropAllUsersFromDatabaseCommand(Client* client, const std::string& dbname); - -Status checkAuthForRevokeRolesFromUserCommand(Client* client, - const std::string& dbname, - const BSONObj& cmdObj); - -Status checkAuthForRevokeRolesFromRoleCommand(Client* client, - const std::string& dbname, - const BSONObj& cmdObj); - -Status checkAuthForDropUserCommand(Client* client, - const std::string& dbname, - const BSONObj& cmdObj); - -Status checkAuthForDropRoleCommand(Client* client, - const std::string& dbname, - const BSONObj& cmdObj); - +void checkAuthForTypedCommand(Client*, const CreateUserCommand&); +void checkAuthForTypedCommand(Client*, const UpdateUserCommand&); +void checkAuthForTypedCommand(Client*, const GrantRolesToUserCommand&); +void checkAuthForTypedCommand(Client*, const CreateRoleCommand&); +void checkAuthForTypedCommand(Client*, const UpdateRoleCommand&); +void checkAuthForTypedCommand(Client*, const GrantRolesToRoleCommand&); +void checkAuthForTypedCommand(Client*, const GrantPrivilegesToRoleCommand&); +void checkAuthForTypedCommand(Client*, const DropAllUsersFromDatabaseCommand&); +void checkAuthForTypedCommand(Client*, const RevokeRolesFromUserCommand&); +void checkAuthForTypedCommand(Client*, const RevokeRolesFromRoleCommand&); +void checkAuthForTypedCommand(Client*, const DropUserCommand&); +void checkAuthForTypedCommand(Client*, const DropRoleCommand&); +void checkAuthForTypedCommand(Client*, const RevokePrivilegesFromRoleCommand&); +void checkAuthForTypedCommand(Client*, const DropAllRolesFromDatabaseCommand&); Status checkAuthForUsersInfoCommand(Client* client, const std::string& dbname, const BSONObj& cmdObj); -Status checkAuthForRevokePrivilegesFromRoleCommand(Client* client, - const std::string& dbname, - const BSONObj& cmdObj); - -Status checkAuthForDropAllRolesFromDatabaseCommand(Client* client, const std::string& dbname); - Status checkAuthForRolesInfoCommand(Client* client, const std::string& dbname, const BSONObj& cmdObj); -- cgit v1.2.1