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