summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSara Golemon <sara.golemon@mongodb.com>2022-06-16 14:15:24 -0500
committerEvergreen Agent <no-reply@evergreen.mongodb.com>2022-07-07 16:06:08 +0000
commit51e8972dca292dbc6f80d13252b8555c8ce633c9 (patch)
tree9c48e9a142ba48faf17dc0fd9e8ddd8e444a0b69
parenta7ce4c0c9b3abf5bc27675a4b5edde401371a2fd (diff)
downloadmongo-51e8972dca292dbc6f80d13252b8555c8ce633c9.tar.gz
SERVER-66586 Add multitenancy support to UMC commands
-rw-r--r--jstests/auth/multitenancy_test_authzn.js194
-rw-r--r--src/mongo/db/auth/authorization_manager.h12
-rw-r--r--src/mongo/db/auth/authorization_manager_impl.cpp15
-rw-r--r--src/mongo/db/auth/authorization_manager_impl.h5
-rw-r--r--src/mongo/db/auth/authz_manager_external_state_local.cpp18
-rw-r--r--src/mongo/db/auth/role_name_or_string.cpp5
-rw-r--r--src/mongo/db/auth/role_name_or_string.h3
-rw-r--r--src/mongo/db/auth/umc_info_command_arg.h5
-rw-r--r--src/mongo/db/commands/user_management_commands.cpp521
-rw-r--r--src/mongo/db/commands/user_management_commands_common.cpp2
-rw-r--r--src/mongo/db/commands/user_management_commands_common.h3
-rw-r--r--src/mongo/db/namespace_string.cpp22
-rw-r--r--src/mongo/embedded/embedded_auth_manager.cpp4
13 files changed, 562 insertions, 247 deletions
diff --git a/jstests/auth/multitenancy_test_authzn.js b/jstests/auth/multitenancy_test_authzn.js
new file mode 100644
index 00000000000..496e4af4c12
--- /dev/null
+++ b/jstests/auth/multitenancy_test_authzn.js
@@ -0,0 +1,194 @@
+// Test creation of local users and roles for multitenancy.
+
+(function() {
+'use strict';
+
+if (!TestData.setParameters.featureFlagMongoStore) {
+ assert.throws(() => MongoRunner.runMongod({
+ setParameter: "multitenancySupport=true",
+ }));
+ return;
+}
+
+function setup(conn) {
+ const admin = conn.getDB('admin');
+ assert.commandWorked(
+ admin.runCommand({createUser: 'admin', pwd: 'admin', roles: ['__system']}));
+ assert(admin.auth('admin', 'admin'));
+}
+
+function runTests(conn, tenant, multitenancySupport) {
+ const expectSuccess = (tenant === null) || (multitenancySupport && TestData.enableTestCommands);
+ jsTest.log("Runing test: " + tojson({tenant: tenant, multi: multitenancySupport}));
+
+ function checkSuccess(result) {
+ if (expectSuccess) {
+ return assert.commandWorked(result);
+ } else {
+ assert.commandFailedWithCode(result, ErrorCodes.InvalidOptions);
+ return false;
+ }
+ }
+
+ // TODO (SERVER-67657) Use $tenant with {find:...} operation
+ const tenantAdmin = conn.getDB((tenant ? tenant.str + '_' : '') + 'admin');
+ const test = conn.getDB('test');
+ const cmdSuffix = (tenant === null) ? {} : {"$tenant": tenant};
+ function runCmd(cmd) {
+ const cmdToRun = Object.assign({}, cmd, cmdSuffix);
+ return test.runCommand(cmdToRun);
+ }
+
+ function validateCounts(expectUsers, expectRoles) {
+ const filter = {db: 'test'};
+ const admin = conn.getDB('admin');
+
+ if (!expectSuccess) {
+ expectUsers = expectRoles = 0;
+ }
+
+ // usersInfo/rolesInfo commands return expected data.
+ const usersInfo = assert.commandWorked(runCmd({usersInfo: 1})).users;
+ const rolesInfo = assert.commandWorked(runCmd({rolesInfo: 1, showPrivileges: true})).roles;
+ assert.eq(usersInfo.length, expectUsers, tojson(usersInfo));
+ assert.eq(rolesInfo.length, expectRoles, tojson(rolesInfo));
+
+ if (tenant) {
+ // Look for users/roles in tenant specific collections directly.
+ const tenantUsers = tenantAdmin.system.users.find(filter).toArray();
+ const tenantRoles = tenantAdmin.system.roles.find(filter).toArray();
+
+ assert.eq(tenantUsers.length, expectUsers, tojson(tenantUsers));
+ assert.eq(tenantRoles.length, expectRoles, tojson(tenantRoles));
+
+ // Found users/roles in tenant, don't look for them in base collections.
+ expectUsers = expectRoles = 0;
+ }
+
+ // Check base system collections, generally should be empty, unless we're in no-tenant mode.
+ const systemUsers = admin.system.users.find(filter).toArray();
+ const systemRoles = admin.system.roles.find(filter).toArray();
+ assert.eq(systemUsers.length, expectUsers, tojson(systemUsers));
+ assert.eq(systemRoles.length, expectRoles, tojson(systemRoles));
+ }
+
+ // createUser/createRole
+ checkSuccess(runCmd({createUser: 'user1', 'pwd': 'pwd', roles: []}));
+ checkSuccess(runCmd({createRole: 'role1', roles: [], privileges: []}));
+ checkSuccess(runCmd({createRole: 'role2', roles: ['role1'], privileges: []}));
+ checkSuccess(
+ runCmd({createRole: 'role3', roles: [{db: 'test', role: 'role1'}], privileges: []}));
+ checkSuccess(runCmd({createUser: 'user2', 'pwd': 'pwd', roles: ['role2', 'role3']}));
+
+ const rwMyColl_privs = [{
+ resource: {db: 'test', collection: 'myColl'},
+ actions: ['find', 'insert', 'remove', 'update']
+ }];
+ const myCollUser_roles = [{role: 'rwMyColl', db: 'test'}];
+ checkSuccess(runCmd({createRole: 'rwMyColl', roles: [], privileges: rwMyColl_privs}));
+ checkSuccess(runCmd({createUser: 'myCollUser', pwd: 'pwd', roles: myCollUser_roles}));
+ validateCounts(3, 4);
+
+ if (tenant && expectSuccess) {
+ const myCollUser = tenantAdmin.system.users.find({_id: 'test.myCollUser'}).toArray()[0];
+ assert.eq(tojson(myCollUser.roles), tojson(myCollUser_roles), tojson(myCollUser));
+ const rwMyColl = tenantAdmin.system.roles.find({_id: 'test.rwMyColl'}).toArray()[0];
+ assert.eq(tojson(rwMyColl.privileges), tojson(rwMyColl_privs), tojson(rwMyColl));
+ const role2 = tenantAdmin.system.roles.find({_id: 'test.role2'}).toArray()[0];
+ assert.eq(tojson(role2.roles), tojson([{role: 'role1', db: 'test'}]), tojson(role2));
+ const role3 = tenantAdmin.system.roles.find({_id: 'test.role3'}).toArray()[0];
+ assert.eq(tojson(role3.roles), tojson([{role: 'role1', db: 'test'}]), tojson(role3));
+ }
+
+ // grant/revoke privileges
+ const rwMyColl_addPrivs =
+ [{resource: {db: 'test', collection: 'otherColl'}, actions: ['find']}];
+ checkSuccess(runCmd({grantPrivilegesToRole: 'rwMyColl', privileges: rwMyColl_addPrivs}));
+ checkSuccess(runCmd({
+ revokePrivilegesFromRole: 'rwMyColl',
+ privileges: [{resource: {db: 'test', collection: 'myColl'}, actions: ['find']}]
+ }));
+ validateCounts(3, 4);
+
+ if (tenant && expectSuccess) {
+ const rwMyColl_expectPrivs = [
+ {resource: {db: 'test', collection: 'myColl'}, actions: ['insert', 'remove', 'update']},
+ {resource: {db: 'test', collection: 'otherColl'}, actions: ['find']}
+ ];
+ const rwMyColl = tenantAdmin.system.roles.find({_id: 'test.rwMyColl'}).toArray()[0];
+ assert.eq(tojson(rwMyColl.privileges), tojson(rwMyColl_expectPrivs), tojson(rwMyColl));
+ }
+
+ // Grant/Revoke Roles to/fromfrom User/Role
+ checkSuccess(runCmd({grantRolesToUser: 'user1', roles: ['role1']}));
+ checkSuccess(runCmd({revokeRolesFromUser: 'user2', roles: ['role2']}));
+ checkSuccess(runCmd({grantRolesToRole: 'role1', roles: ['rwMyColl']}));
+ checkSuccess(runCmd({revokeRolesFromRole: 'role3', roles: ['role1']}));
+ validateCounts(3, 4);
+
+ if (tenant && expectSuccess) {
+ const user1 = tenantAdmin.system.users.find({_id: 'test.user1'}).toArray()[0];
+ assert.eq(tojson(user1.roles), tojson([{role: 'role1', db: 'test'}]), tojson(user1));
+ const user2 = tenantAdmin.system.users.find({_id: 'test.user2'}).toArray()[0];
+ assert.eq(tojson(user2.roles), tojson([{role: 'role3', db: 'test'}]), tojson(user2));
+
+ const role1 = tenantAdmin.system.roles.find({_id: 'test.role1'}).toArray()[0];
+ assert.eq(tojson(role1.roles), tojson([{role: 'rwMyColl', db: 'test'}]), tojson(role1));
+ const role3 = tenantAdmin.system.roles.find({_id: 'test.role3'}).toArray()[0];
+ assert.eq(tojson(role3.roles), tojson([]), tojson(role3));
+ }
+
+ // updateUser/updateRole
+ checkSuccess(runCmd({updateUser: 'user1', roles: ['role2']}));
+ checkSuccess(runCmd({updateRole: 'role2', roles: ['rwMyColl']}));
+ validateCounts(3, 4);
+
+ if (tenant && expectSuccess) {
+ const user1 = tenantAdmin.system.users.find({_id: 'test.user1'}).toArray()[0];
+ assert.eq(tojson(user1.roles), tojson([{role: 'role2', db: 'test'}]), tojson(user1));
+ const role2 = tenantAdmin.system.roles.find({_id: 'test.role2'}).toArray()[0];
+ assert.eq(tojson(role2.roles), tojson([{role: 'rwMyColl', db: 'test'}]), tojson(role2));
+ }
+
+ // dropUser/dropRole
+ checkSuccess(runCmd({dropRole: 'role2'}));
+ checkSuccess(runCmd({dropUser: 'myCollUser'}));
+ validateCounts(2, 3);
+
+ if (tenant && expectSuccess) {
+ // role2 should have been revoked from user1 during drop,.
+ const user1 = tenantAdmin.system.users.find({_id: 'test.user1'}).toArray()[0];
+ assert.eq(tojson(user1.roles), tojson([]), tojson(user1));
+ assert.eq(0, tenantAdmin.system.users.find({_id: 'test.myCollUser'}).toArray().length);
+ assert.eq(0, tenantAdmin.system.roles.find({_id: 'test.role2'}).toArray().length);
+ }
+
+ // Cleanup
+ checkSuccess(runCmd({dropAllUsersFromDatabase: 1}));
+ checkSuccess(runCmd({dropAllRolesFromDatabase: 1}));
+ validateCounts(0, 0);
+}
+
+// This isn't relevant to this test, but requires enableTestCommands, which we want to frob.
+TestData.roleGraphInvalidationIsFatal = false;
+
+function spanOptions(cb) {
+ [true].forEach(function(enableTestCommands) {
+ TestData.enableTestCommands = enableTestCommands;
+ [true].forEach(function(multitenancySupport) {
+ jsTest.log(
+ {enableTestCommands: enableTestCommands, multitenancySupport: multitenancySupport});
+ cb({multitenancySupport: multitenancySupport});
+ });
+ });
+}
+
+spanOptions(function(setParams) {
+ const standalone = MongoRunner.runMongod({auth: "", setParameter: setParams});
+ jsTest.log('Standalone started');
+ setup(standalone);
+ runTests(standalone, null, setParams.multitenancySupport);
+ runTests(standalone, ObjectId(), setParams.multitenancySupport);
+ MongoRunner.stopMongod(standalone);
+});
+})();
diff --git a/src/mongo/db/auth/authorization_manager.h b/src/mongo/db/auth/authorization_manager.h
index fca637f77d3..69077aebe6a 100644
--- a/src/mongo/db/auth/authorization_manager.h
+++ b/src/mongo/db/auth/authorization_manager.h
@@ -39,7 +39,7 @@
#include "mongo/db/auth/privilege_format.h"
#include "mongo/db/auth/resource_pattern.h"
#include "mongo/db/auth/user.h"
-#include "mongo/db/jsobj.h"
+#include "mongo/db/database_name.h"
#include "mongo/db/namespace_string.h"
#include "mongo/db/tenant_id.h"
@@ -326,14 +326,16 @@ public:
virtual void invalidateUserByName(OperationContext* opCtx, const UserName& user) = 0;
/**
- * Invalidates all users who's source is "dbname" and removes them from the user cache.
+ * Invalidates all users whose source is "dbname" and removes them from the user cache.
*/
- virtual void invalidateUsersFromDB(OperationContext* opCtx, StringData dbname) = 0;
+ virtual void invalidateUsersFromDB(OperationContext* opCtx, const DatabaseName& dbname) = 0;
/**
- * Invalidate all users associated with a given tenant.
+ * Invalidate all users associated with a given tenant,
+ * or entire cache if tenant == boost::none.
*/
- virtual void invalidateUsersByTenant(OperationContext* opCtx, const TenantId& tenant) = 0;
+ virtual void invalidateUsersByTenant(OperationContext* opCtx,
+ const boost::optional<TenantId>& tenant) = 0;
/**
* Retrieves all users whose source is "$external" and checks if the corresponding user in the
diff --git a/src/mongo/db/auth/authorization_manager_impl.cpp b/src/mongo/db/auth/authorization_manager_impl.cpp
index 4ecfb5af2df..2d1d6117736 100644
--- a/src/mongo/db/auth/authorization_manager_impl.cpp
+++ b/src/mongo/db/auth/authorization_manager_impl.cpp
@@ -684,16 +684,23 @@ void AuthorizationManagerImpl::invalidateUserByName(OperationContext* opCtx,
_userCache.invalidateKey(UserRequest(userName, boost::none));
}
-void AuthorizationManagerImpl::invalidateUsersFromDB(OperationContext* opCtx, StringData dbname) {
+void AuthorizationManagerImpl::invalidateUsersFromDB(OperationContext* opCtx,
+ const DatabaseName& dbname) {
LOGV2_DEBUG(20236, 2, "Invalidating all users from database", "database"_attr = dbname);
_updateCacheGeneration();
_authSchemaVersionCache.invalidateAll();
- _userCache.invalidateKeyIf(
- [&](const UserRequest& userRequest) { return userRequest.name.getDB() == dbname; });
+ _userCache.invalidateKeyIf([&](const UserRequest& userRequest) {
+ return userRequest.name.getDatabaseName() == dbname;
+ });
}
void AuthorizationManagerImpl::invalidateUsersByTenant(OperationContext* opCtx,
- const TenantId& tenant) {
+ const boost::optional<TenantId>& tenant) {
+ if (!tenant) {
+ invalidateUserCache(opCtx);
+ return;
+ }
+
LOGV2_DEBUG(6323600, 2, "Invalidating tenant users", "tenant"_attr = tenant);
_updateCacheGeneration();
_authSchemaVersionCache.invalidateAll();
diff --git a/src/mongo/db/auth/authorization_manager_impl.h b/src/mongo/db/auth/authorization_manager_impl.h
index 81f247b2b1d..fcec7b97e32 100644
--- a/src/mongo/db/auth/authorization_manager_impl.h
+++ b/src/mongo/db/auth/authorization_manager_impl.h
@@ -105,9 +105,10 @@ public:
*/
void invalidateUserByName(OperationContext* opCtx, const UserName& user) override;
- void invalidateUsersFromDB(OperationContext* opCtx, StringData dbname) override;
+ void invalidateUsersFromDB(OperationContext* opCtx, const DatabaseName& dbname) override;
- void invalidateUsersByTenant(OperationContext* opCtx, const TenantId& tenant) override;
+ void invalidateUsersByTenant(OperationContext* opCtx,
+ const boost::optional<TenantId>& tenant) override;
/**
* Verify role information for users in the $external database and insert updated information
diff --git a/src/mongo/db/auth/authz_manager_external_state_local.cpp b/src/mongo/db/auth/authz_manager_external_state_local.cpp
index 7020dfd5ef6..717361a9b83 100644
--- a/src/mongo/db/auth/authz_manager_external_state_local.cpp
+++ b/src/mongo/db/auth/authz_manager_external_state_local.cpp
@@ -164,7 +164,8 @@ constexpr auto kAuthenticationRestrictionFieldName = "authenticationRestrictions
std::vector<RoleName> filterAndMapRole(BSONObjBuilder* builder,
BSONObj role,
ResolveRoleOption option,
- bool liftAuthenticationRestrictions) {
+ bool liftAuthenticationRestrictions,
+ const boost::optional<TenantId>& tenant) {
std::vector<RoleName> subRoles;
bool sawRestrictions = false;
@@ -173,7 +174,7 @@ std::vector<RoleName> filterAndMapRole(BSONObjBuilder* builder,
uassert(
ErrorCodes::BadValue, "Invalid roles field, expected array", elem.type() == Array);
for (const auto& roleName : elem.Obj()) {
- subRoles.push_back(RoleName::parseFromBSON(roleName));
+ subRoles.push_back(RoleName::parseFromBSON(roleName, tenant));
}
if ((option & ResolveRoleOption::kRoles) == 0) {
continue;
@@ -379,7 +380,8 @@ Status AuthzManagerExternalStateLocal::getUserDescription(OperationContext* opCt
return status;
}
- directRoles = filterAndMapRole(&resultBuilder, userDoc, ResolveRoleOption::kAll, false);
+ directRoles = filterAndMapRole(
+ &resultBuilder, userDoc, ResolveRoleOption::kAll, false, userName.getTenant());
} else {
uassert(ErrorCodes::BadValue,
"Illegal combination of pre-defined roles with tenant identifier",
@@ -439,7 +441,6 @@ StatusWith<ResolvedRoleData> AuthzManagerExternalStateLocal::resolveRoles(
const bool processRests = option & ResolveRoleOption::kRestrictions;
const bool walkIndirect = (option & ResolveRoleOption::kDirectOnly) == 0;
- auto optTenant = getActiveTenant(opCtx);
RoleNameSet inheritedRoles;
PrivilegeVector inheritedPrivileges;
RestrictionDocuments::sequence_type inheritedRestrictions;
@@ -478,7 +479,7 @@ StatusWith<ResolvedRoleData> AuthzManagerExternalStateLocal::resolveRoles(
<< "', expected an array but found " << typeName(elem.type())};
}
for (const auto& subroleElem : elem.Obj()) {
- auto subrole = RoleName::parseFromBSON(subroleElem, optTenant);
+ auto subrole = RoleName::parseFromBSON(subroleElem, role.getTenant());
if (visited.count(subrole) || nextFrontier.count(subrole)) {
continue;
}
@@ -613,7 +614,7 @@ Status AuthzManagerExternalStateLocal::getRolesDescription(
}
BSONObjBuilder roleBuilder;
- auto subRoles = filterAndMapRole(&roleBuilder, roleDoc, option, true);
+ auto subRoles = filterAndMapRole(&roleBuilder, roleDoc, option, true, role.getTenant());
auto data = uassertStatusOK(resolveRoles(opCtx, subRoles, option));
data.roles->insert(subRoles.cbegin(), subRoles.cend());
serializeResolvedRoles(&roleBuilder, data, roleDoc);
@@ -681,14 +682,15 @@ Status AuthzManagerExternalStateLocal::getRoleDescriptionsForDB(
}
return query(opCtx,
- getRolesCollection(getActiveTenant(opCtx)),
+ getRolesCollection(dbname.tenantId()),
BSON(AuthorizationManager::ROLE_DB_FIELD_NAME << dbname.db()),
BSONObj(),
[&](const BSONObj& roleDoc) {
try {
BSONObjBuilder roleBuilder;
- auto subRoles = filterAndMapRole(&roleBuilder, roleDoc, option, true);
+ auto subRoles = filterAndMapRole(
+ &roleBuilder, roleDoc, option, true, dbname.tenantId());
roleBuilder.append("isBuiltin", false);
auto data = uassertStatusOK(resolveRoles(opCtx, subRoles, option));
data.roles->insert(subRoles.cbegin(), subRoles.cend());
diff --git a/src/mongo/db/auth/role_name_or_string.cpp b/src/mongo/db/auth/role_name_or_string.cpp
index f9091dfbaae..72d7b4773ca 100644
--- a/src/mongo/db/auth/role_name_or_string.cpp
+++ b/src/mongo/db/auth/role_name_or_string.cpp
@@ -45,9 +45,10 @@ const T& variant_get(const U* variant) {
return *value;
}
-RoleName RoleNameOrString::getRoleName(StringData dbname) const {
+RoleName RoleNameOrString::getRoleName(const DatabaseName& dbname) const {
if (std::holds_alternative<RoleName>(_roleName)) {
- return variant_get<RoleName>(&_roleName);
+ auto role = variant_get<RoleName>(&_roleName);
+ return RoleName(role.getName(), role.getDB(), dbname.tenantId());
} else {
dassert(std::holds_alternative<std::string>(_roleName));
return RoleName(variant_get<std::string>(&_roleName), dbname);
diff --git a/src/mongo/db/auth/role_name_or_string.h b/src/mongo/db/auth/role_name_or_string.h
index d75e1f332a9..ccc933cce0c 100644
--- a/src/mongo/db/auth/role_name_or_string.h
+++ b/src/mongo/db/auth/role_name_or_string.h
@@ -37,6 +37,7 @@
#include "mongo/base/string_data.h"
#include "mongo/bson/bsonelement.h"
#include "mongo/bson/bsonobjbuilder.h"
+#include "mongo/db/database_name.h"
namespace mongo {
@@ -66,7 +67,7 @@ public:
* Returns the fully qualified RoleName if present,
* or constructs a RoleName using the parsed role and provided dbname.
*/
- RoleName getRoleName(StringData dbname) const;
+ RoleName getRoleName(const DatabaseName& dbname) const;
private:
std::variant<RoleName, std::string> _roleName;
diff --git a/src/mongo/db/auth/umc_info_command_arg.h b/src/mongo/db/auth/umc_info_command_arg.h
index dbb49fe8d0a..13a7ef586f2 100644
--- a/src/mongo/db/auth/umc_info_command_arg.h
+++ b/src/mongo/db/auth/umc_info_command_arg.h
@@ -36,6 +36,7 @@
#include "mongo/bson/bsonobjbuilder.h"
#include "mongo/db/auth/role_name.h"
#include "mongo/db/auth/user_name.h"
+#include "mongo/db/database_name.h"
#include "mongo/stdx/variant.h"
namespace mongo {
@@ -132,7 +133,7 @@ public:
/**
* For isExact() commands, returns a set of T with unspecified DB names resolved with $dbname.
*/
- std::vector<T> getElements(StringData dbname) const {
+ std::vector<T> getElements(const DatabaseName& dbname) const {
if (!isExact()) {
dassert(false);
uasserted(ErrorCodes::InternalError, "Unable to get exact match for wildcard query");
@@ -190,7 +191,7 @@ private:
}
}
- static T getElement(Single elem, StringData dbname) {
+ static T getElement(Single elem, const DatabaseName& dbname) {
if (stdx::holds_alternative<T>(elem)) {
return stdx::get<T>(elem);
} else {
diff --git a/src/mongo/db/commands/user_management_commands.cpp b/src/mongo/db/commands/user_management_commands.cpp
index 63bfeb73a03..70b868a5cda 100644
--- a/src/mongo/db/commands/user_management_commands.cpp
+++ b/src/mongo/db/commands/user_management_commands.cpp
@@ -65,6 +65,7 @@
#include "mongo/db/dbdirectclient.h"
#include "mongo/db/jsobj.h"
#include "mongo/db/multitenancy.h"
+#include "mongo/db/multitenancy_gen.h"
#include "mongo/db/operation_context.h"
#include "mongo/db/ops/write_ops.h"
#include "mongo/db/pipeline/aggregation_request_helper.h"
@@ -96,9 +97,20 @@ namespace {
constexpr auto kOne = "1"_sd;
Status useDefaultCode(const Status& status, ErrorCodes::Error defaultCode) {
- if (status.code() != ErrorCodes::UnknownError)
- return status;
- return Status(defaultCode, status.reason());
+ if (status.code() == ErrorCodes::UnknownError) {
+ return Status(defaultCode, status.reason());
+ }
+
+ return status;
+}
+
+template <typename T>
+StatusWith<T> useDefaultCode(StatusWith<T>&& status, ErrorCodes::Error defaultCode) {
+ if (!status.isOK()) {
+ return useDefaultCode(status.getStatus(), defaultCode);
+ }
+
+ return std::move(status);
}
template <typename Container>
@@ -210,36 +222,37 @@ Status checkOkayToGrantPrivilegesToRole(const RoleName& role, const PrivilegeVec
return Status::OK();
}
-// Temporary placeholder pending availability of NamespaceWithTenant.
-NamespaceString getNamespaceWithTenant(const NamespaceString& nss,
- const boost::optional<TenantId>& tenant) {
- if (tenant) {
- return NamespaceString(str::stream() << tenant.get() << '_' << nss.db(), nss.coll());
+// TODO (SERVER-67423) Convert DBClient to accept DatabaseName type.
+// Currently tenant is lost on the way from DBDirectClient to runCommand.
+// For now, just mangle the NamespaceName into (`tenant_db`, `coll`) format.
+NamespaceString patchTenantNSS(const NamespaceString& nss) {
+ if (auto tenant = nss.tenantId()) {
+ return NamespaceString(
+ boost::none, str::stream() << *tenant << '_' << nss.dbName().db(), nss.coll());
} else {
return nss;
}
}
-/**
- * Finds all documents matching "query" in "collectionName". For each document returned,
- * calls the function resultProcessor on it.
- * Should only be called on collections with authorization documents in them
- * (ie admin.system.users and admin.system.roles).
- */
-Status queryAuthzDocument(OperationContext* opCtx,
- const NamespaceString& collectionName,
- const BSONObj& query,
- const BSONObj& projection,
- const std::function<void(const BSONObj&)>& resultProcessor) {
- try {
- DBDirectClient client(opCtx);
- FindCommandRequest findRequest{collectionName};
- findRequest.setFilter(query);
- findRequest.setProjection(projection);
- client.find(std::move(findRequest), resultProcessor);
- return Status::OK();
- } catch (const DBException& e) {
- return e.toStatus();
+// TODO (SERVER-67423) Convert DBClient to accept DatabaseName type.
+NamespaceString usersNSS(const boost::optional<TenantId>& tenant) {
+ if (tenant) {
+ return NamespaceString(boost::none,
+ str::stream() << *tenant << '_' << NamespaceString::kAdminDb,
+ NamespaceString::kSystemUsers);
+ } else {
+ return AuthorizationManager::usersCollectionNamespace;
+ }
+}
+
+// TODO (SERVER-67423) Convert DBClient to accept DatabaseName type.
+NamespaceString rolesNSS(const boost::optional<TenantId>& tenant) {
+ if (tenant) {
+ return NamespaceString(boost::none,
+ str::stream() << *tenant << '_' << NamespaceString::kAdminDb,
+ NamespaceString::kSystemRoles);
+ } else {
+ return AuthorizationManager::rolesCollectionNamespace;
}
}
@@ -251,16 +264,14 @@ Status queryAuthzDocument(OperationContext* opCtx,
* (ie admin.system.users and admin.system.roles).
*/
Status insertAuthzDocument(OperationContext* opCtx,
- const NamespaceString& collectionName,
- const BSONObj& document) {
- try {
- DBDirectClient client(opCtx);
- write_ops::checkWriteErrors(
- client.insert(write_ops::InsertCommandRequest(collectionName, {document})));
- return Status::OK();
- } catch (const DBException& e) {
- return e.toStatus();
- }
+ const NamespaceString& nss,
+ const BSONObj& document) try {
+ DBDirectClient client(opCtx);
+ write_ops::checkWriteErrors(
+ client.insert(write_ops::InsertCommandRequest(patchTenantNSS(nss), {document})));
+ return Status::OK();
+} catch (const DBException& e) {
+ return e.toStatus();
}
/**
@@ -269,34 +280,30 @@ Status insertAuthzDocument(OperationContext* opCtx,
* Should only be called on collections with authorization documents in them
* (ie admin.system.users and admin.system.roles).
*/
-Status updateAuthzDocuments(OperationContext* opCtx,
- const NamespaceString& collectionName,
- const BSONObj& query,
- const BSONObj& updatePattern,
- bool upsert,
- bool multi,
- std::int64_t* numMatched) {
- try {
- DBDirectClient client(opCtx);
- auto result = client.update([&] {
- write_ops::UpdateCommandRequest updateOp(collectionName);
- updateOp.setUpdates({[&] {
- write_ops::UpdateOpEntry entry;
- entry.setQ(query);
- entry.setU(write_ops::UpdateModification::parseFromClassicUpdate(updatePattern));
- entry.setMulti(multi);
- entry.setUpsert(upsert);
- return entry;
- }()});
- return updateOp;
- }());
-
- *numMatched = result.getN();
- write_ops::checkWriteErrors(result);
- return Status::OK();
- } catch (const DBException& e) {
- return e.toStatus();
- }
+StatusWith<std::int64_t> updateAuthzDocuments(OperationContext* opCtx,
+ const NamespaceString& nss,
+ const BSONObj& query,
+ const BSONObj& updatePattern,
+ bool upsert,
+ bool multi) try {
+ DBDirectClient client(opCtx);
+ auto result = client.update([&] {
+ write_ops::UpdateCommandRequest updateOp(patchTenantNSS(nss));
+ updateOp.setUpdates({[&] {
+ write_ops::UpdateOpEntry entry;
+ entry.setQ(query);
+ entry.setU(write_ops::UpdateModification::parseFromClassicUpdate(updatePattern));
+ entry.setMulti(multi);
+ entry.setUpsert(upsert);
+ return entry;
+ }()});
+ return updateOp;
+ }());
+
+ write_ops::checkWriteErrors(result);
+ return result.getN();
+} catch (const DBException& e) {
+ return e.toStatus();
}
/**
@@ -316,16 +323,18 @@ Status updateOneAuthzDocument(OperationContext* opCtx,
const BSONObj& query,
const BSONObj& updatePattern,
bool upsert) {
- std::int64_t numMatched;
- Status status = updateAuthzDocuments(
- opCtx, collectionName, query, updatePattern, upsert, false, &numMatched);
- if (!status.isOK()) {
- return status;
+ auto swNumMatched =
+ updateAuthzDocuments(opCtx, collectionName, query, updatePattern, upsert, false);
+ if (!swNumMatched.isOK()) {
+ return swNumMatched.getStatus();
}
+
+ auto numMatched = swNumMatched.getValue();
dassert(numMatched == 1 || numMatched == 0);
if (numMatched == 0) {
- return Status(ErrorCodes::NoMatchingDocument, "No document found");
+ return {ErrorCodes::NoMatchingDocument, "No document found"};
}
+
return Status::OK();
}
@@ -335,50 +344,46 @@ Status updateOneAuthzDocument(OperationContext* opCtx,
* Should only be called on collections with authorization documents in them
* (ie admin.system.users and admin.system.roles).
*/
-Status removeAuthzDocuments(OperationContext* opCtx,
- const NamespaceString& collectionName,
- const BSONObj& query,
- std::int64_t* numRemoved) {
- try {
- DBDirectClient client(opCtx);
- auto result = client.remove([&] {
- write_ops::DeleteCommandRequest deleteOp(collectionName);
- deleteOp.setDeletes({[&] {
- write_ops::DeleteOpEntry entry;
- entry.setQ(query);
- entry.setMulti(true);
- return entry;
- }()});
- return deleteOp;
- }());
-
- *numRemoved = result.getN();
- write_ops::checkWriteErrors(result);
- return Status::OK();
- } catch (const DBException& e) {
- return e.toStatus();
- }
+StatusWith<std::int64_t> removeAuthzDocuments(OperationContext* opCtx,
+ const NamespaceString& nss,
+ const BSONObj& query) try {
+ DBDirectClient client(opCtx);
+ auto result = client.remove([&] {
+ write_ops::DeleteCommandRequest deleteOp(patchTenantNSS(nss));
+ deleteOp.setDeletes({[&] {
+ write_ops::DeleteOpEntry entry;
+ entry.setQ(query);
+ entry.setMulti(true);
+ return entry;
+ }()});
+ return deleteOp;
+ }());
+
+ write_ops::checkWriteErrors(result);
+ return result.getN();
+} catch (const DBException& e) {
+ return e.toStatus();
}
/**
* Creates the given role object in the given database.
*/
-Status insertRoleDocument(OperationContext* opCtx, const BSONObj& roleObj) {
- Status status =
- insertAuthzDocument(opCtx, AuthorizationManager::rolesCollectionNamespace, roleObj);
+Status insertRoleDocument(OperationContext* opCtx,
+ const BSONObj& roleObj,
+ const boost::optional<TenantId>& tenant) {
+ auto status = insertAuthzDocument(opCtx, rolesNSS(tenant), roleObj);
if (status.isOK()) {
return status;
}
+
if (status.code() == ErrorCodes::DuplicateKey) {
std::string name = roleObj[AuthorizationManager::ROLE_NAME_FIELD_NAME].String();
std::string source = roleObj[AuthorizationManager::ROLE_DB_FIELD_NAME].String();
return Status(ErrorCodes::Error(51002),
str::stream() << "Role \"" << name << "@" << source << "\" already exists");
}
- if (status.code() == ErrorCodes::UnknownError) {
- return Status(ErrorCodes::RoleModificationFailed, status.reason());
- }
- return status;
+
+ return useDefaultCode(status, ErrorCodes::RoleModificationFailed);
}
/**
@@ -387,7 +392,7 @@ Status insertRoleDocument(OperationContext* opCtx, const BSONObj& roleObj) {
Status updateRoleDocument(OperationContext* opCtx, const RoleName& role, const BSONObj& updateObj) {
Status status = updateOneAuthzDocument(
opCtx,
- AuthorizationManager::rolesCollectionNamespace,
+ rolesNSS(role.getTenant()),
BSON(AuthorizationManager::ROLE_NAME_FIELD_NAME
<< role.getRole() << AuthorizationManager::ROLE_DB_FIELD_NAME << role.getDB()),
updateObj,
@@ -395,28 +400,23 @@ Status updateRoleDocument(OperationContext* opCtx, const RoleName& role, const B
if (status.isOK()) {
return status;
}
+
if (status.code() == ErrorCodes::NoMatchingDocument) {
return Status(ErrorCodes::RoleNotFound, str::stream() << "Role " << role << " not found");
}
- if (status.code() == ErrorCodes::UnknownError) {
- return Status(ErrorCodes::RoleModificationFailed, status.reason());
- }
- return status;
+
+ return useDefaultCode(status, ErrorCodes::RoleModificationFailed);
}
/**
* Removes roles matching the given query.
* Writes into *numRemoved the number of role documents that were modified.
*/
-Status removeRoleDocuments(OperationContext* opCtx,
- const BSONObj& query,
- std::int64_t* numRemoved) {
- Status status = removeAuthzDocuments(
- opCtx, AuthorizationManager::rolesCollectionNamespace, query, numRemoved);
- if (status.code() == ErrorCodes::UnknownError) {
- return Status(ErrorCodes::RoleModificationFailed, status.reason());
- }
- return status;
+StatusWith<std::int64_t> removeRoleDocuments(OperationContext* opCtx,
+ const BSONObj& query,
+ const boost::optional<TenantId>& tenant) {
+ return useDefaultCode(removeAuthzDocuments(opCtx, rolesNSS(tenant), query),
+ ErrorCodes::RoleModificationFailed);
}
/**
@@ -425,7 +425,7 @@ Status removeRoleDocuments(OperationContext* opCtx,
Status insertPrivilegeDocument(OperationContext* opCtx,
const BSONObj& userObj,
const boost::optional<TenantId>& tenant = boost::none) {
- auto nss = getNamespaceWithTenant(AuthorizationManager::usersCollectionNamespace, tenant);
+ auto nss = usersNSS(tenant);
Status status = insertAuthzDocument(opCtx, nss, userObj);
if (status.isOK()) {
return status;
@@ -453,15 +453,14 @@ Status updatePrivilegeDocument(OperationContext* opCtx,
dassert(queryObj.hasField(AuthorizationManager::USER_NAME_FIELD_NAME));
dassert(queryObj.hasField(AuthorizationManager::USER_DB_FIELD_NAME));
- const auto status = updateOneAuthzDocument(
- opCtx, AuthorizationManager::usersCollectionNamespace, queryObj, updateObj, false);
- if (status.code() == ErrorCodes::UnknownError) {
- return {ErrorCodes::UserModificationFailed, status.reason()};
- }
+ const auto status =
+ updateOneAuthzDocument(opCtx, usersNSS(user.getTenant()), queryObj, updateObj, false);
+
if (status.code() == ErrorCodes::NoMatchingDocument) {
return {ErrorCodes::UserNotFound, str::stream() << "User " << user << " not found"};
}
- return status;
+
+ return useDefaultCode(status, ErrorCodes::UserModificationFailed);
}
/**
@@ -471,28 +470,23 @@ Status updatePrivilegeDocument(OperationContext* opCtx,
Status updatePrivilegeDocument(OperationContext* opCtx,
const UserName& user,
const BSONObj& updateObj) {
- const auto status = updatePrivilegeDocument(
+ return updatePrivilegeDocument(
opCtx,
user,
BSON(AuthorizationManager::USER_NAME_FIELD_NAME
<< user.getUser() << AuthorizationManager::USER_DB_FIELD_NAME << user.getDB()),
updateObj);
- return status;
}
/**
* Removes users for the given database matching the given query.
* Writes into *numRemoved the number of user documents that were modified.
*/
-Status removePrivilegeDocuments(OperationContext* opCtx,
- const BSONObj& query,
- std::int64_t* numRemoved) {
- Status status = removeAuthzDocuments(
- opCtx, AuthorizationManager::usersCollectionNamespace, query, numRemoved);
- if (status.code() == ErrorCodes::UnknownError) {
- return Status(ErrorCodes::UserModificationFailed, status.reason());
- }
- return status;
+StatusWith<std::int64_t> removePrivilegeDocuments(OperationContext* opCtx,
+ const BSONObj& query,
+ const boost::optional<TenantId>& tenant) {
+ return useDefaultCode(removeAuthzDocuments(opCtx, usersNSS(tenant), query),
+ ErrorCodes::UserModificationFailed);
}
/**
@@ -746,7 +740,9 @@ public:
static constexpr StringData kCommitTransaction = "commitTransaction"_sd;
static constexpr StringData kAbortTransaction = "abortTransaction"_sd;
- UMCTransaction(OperationContext* opCtx, StringData forCommand) {
+ UMCTransaction(OperationContext* opCtx,
+ StringData forCommand,
+ const boost::optional<TenantId>& tenant) {
// Don't transactionalize on standalone.
_isReplSet = repl::ReplicationCoordinator::get(opCtx)->getReplicationMode() ==
repl::ReplicationCoordinator::modeReplSet;
@@ -758,6 +754,12 @@ public:
as->grantInternalAuthorization(_client.get());
}
+ if (tenant) {
+ _dbName = str::stream() << *tenant << '_' << kAdminDB;
+ } else {
+ _dbName = kAdminDB.toString();
+ }
+
AlternativeClientRegion clientRegion(_client);
_sessionInfo.setStartTransaction(true);
_sessionInfo.setTxnNumber(0);
@@ -771,14 +773,14 @@ public:
}
StatusWith<std::uint32_t> insert(const NamespaceString& nss, const std::vector<BSONObj>& docs) {
- dassert(nss.db() == kAdminDB);
+ dassert(validNamespace(nss));
write_ops::InsertCommandRequest op(nss);
op.setDocuments(docs);
return doCrudOp(op.toBSON({}));
}
StatusWith<std::uint32_t> update(const NamespaceString& nss, BSONObj query, BSONObj update) {
- dassert(nss.db() == kAdminDB);
+ dassert(validNamespace(nss));
write_ops::UpdateOpEntry entry;
entry.setQ(query);
entry.setU(write_ops::UpdateModification::parseFromClassicUpdate(update));
@@ -789,7 +791,7 @@ public:
}
StatusWith<std::uint32_t> remove(const NamespaceString& nss, BSONObj query) {
- dassert(nss.db() == kAdminDB);
+ dassert(validNamespace(nss));
write_ops::DeleteOpEntry entry;
entry.setQ(query);
entry.setMulti(true);
@@ -816,6 +818,23 @@ public:
}
private:
+ static bool validNamespace(const NamespaceString& nss) {
+ if (nss.dbName().db() == kAdminDB) {
+ return true;
+ }
+ if (gMultitenancySupport && !nss.tenantId()) {
+ // TODO (SERVER-67423) Convert DBClient to accept DatabaseName type.
+ try {
+ auto parsed =
+ NamespaceString::parseFromStringExpectTenantIdInMultitenancyMode(nss.ns());
+ return parsed.dbName().db() == kAdminDB;
+ } catch (const DBException&) {
+ }
+ }
+
+ return false;
+ }
+
StatusWith<std::uint32_t> doCrudOp(BSONObj op) try {
invariant(_state != TransactionState::kDone);
@@ -872,7 +891,7 @@ private:
auto svcCtx = _client->getServiceContext();
auto sep = svcCtx->getServiceEntryPoint();
- auto opMsgRequest = OpMsgRequest::fromDBAndBody(kAdminDB, cmdBuilder->obj());
+ auto opMsgRequest = OpMsgRequest::fromDBAndBody(_dbName, cmdBuilder->obj());
auto requestMessage = opMsgRequest.serialize();
// Switch to our local client and create a short-lived opCtx for this transaction op.
@@ -891,16 +910,24 @@ private:
bool _isReplSet;
ServiceContext::UniqueClient _client;
+ std::string _dbName;
OperationSessionInfoFromClient _sessionInfo;
TransactionState _state = TransactionState::kInit;
};
+enum class SupportTenantOption {
+ kNever,
+ kTestOnly,
+ kAlways,
+};
+
// Used by most UMC commands.
struct UMCStdParams {
static constexpr bool adminOnly = false;
static constexpr bool supportsWriteConcern = true;
static constexpr auto allowedOnSecondary = BasicCommand::AllowedOnSecondary::kNever;
static constexpr bool skipApiVersionCheck = false;
+ static constexpr auto supportTenant = SupportTenantOption::kTestOnly;
};
// Used by {usersInfo:...} and {rolesInfo:...}
@@ -909,6 +936,7 @@ struct UMCInfoParams {
static constexpr bool supportsWriteConcern = false;
static constexpr auto allowedOnSecondary = BasicCommand::AllowedOnSecondary::kOptIn;
static constexpr bool skipApiVersionCheck = false;
+ static constexpr auto supportTenant = SupportTenantOption::kAlways;
};
// Used by {invalidateUserCache:...}
@@ -917,6 +945,7 @@ struct UMCInvalidateUserCacheParams {
static constexpr bool supportsWriteConcern = false;
static constexpr auto allowedOnSecondary = BasicCommand::AllowedOnSecondary::kAlways;
static constexpr bool skipApiVersionCheck = false;
+ static constexpr auto supportTenant = SupportTenantOption::kAlways;
};
// Used by {_getUserCacheGeneration:...}
@@ -925,6 +954,7 @@ struct UMCGetUserCacheGenParams {
static constexpr bool supportsWriteConcern = false;
static constexpr auto allowedOnSecondary = BasicCommand::AllowedOnSecondary::kAlways;
static constexpr bool skipApiVersionCheck = true;
+ static constexpr auto supportTenant = SupportTenantOption::kNever;
};
template <typename T>
@@ -976,6 +1006,20 @@ public:
typename TC::AllowedOnSecondary secondaryAllowed(ServiceContext*) const final {
return Params::allowedOnSecondary;
}
+
+ bool allowedWithSecurityToken() const final {
+ switch (Params::supportTenant) {
+ case SupportTenantOption::kAlways:
+ return true;
+ case SupportTenantOption::kTestOnly:
+ return getTestCommandsEnabled();
+ case SupportTenantOption::kNever:
+ return false;
+ }
+
+ MONGO_UNREACHABLE;
+ return false;
+ }
};
@@ -991,24 +1035,28 @@ public:
template <>
void CmdUMCTyped<CreateUserCommand>::Invocation::typedRun(OperationContext* opCtx) {
const auto& cmd = request();
- const auto& dbname = cmd.getDbName();
+ // TODO (SERVER-67516) cmd.getDatabaseName()
+ DatabaseName dbname(getActiveTenant(opCtx), cmd.getDbName());
// Validate input
- uassert(ErrorCodes::BadValue, "Cannot create users in the local database", dbname != "local");
+ uassert(ErrorCodes::BadValue,
+ "Cannot create users in the local database",
+ dbname.db() != NamespaceString::kLocalDb);
uassert(ErrorCodes::BadValue,
"Username cannot contain NULL characters",
cmd.getCommandParameter().find('\0') == std::string::npos);
- UserName userName(cmd.getCommandParameter(), dbname, getActiveTenant(opCtx));
+ UserName userName(cmd.getCommandParameter(), dbname);
+ const bool isExternal = dbname.db() == NamespaceString::kExternalDb;
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"));
+ (cmd.getPwd() != boost::none) || isExternal);
uassert(ErrorCodes::BadValue,
"Cannot set the password for users defined on the '$external' database",
- (cmd.getPwd() == boost::none) || (dbname != "$external"));
+ (cmd.getPwd() == boost::none) || !isExternal);
uassert(ErrorCodes::BadValue,
"mechanisms field must not be empty",
@@ -1017,8 +1065,7 @@ void CmdUMCTyped<CreateUserCommand>::Invocation::typedRun(OperationContext* opCt
#ifdef MONGO_CONFIG_SSL
auto configuration = opCtx->getClient()->session()->getSSLConfiguration();
- if ((dbname == "$external") && configuration &&
- configuration->isClusterMember(userName.getUser())) {
+ if (isExternal && configuration && configuration->isClusterMember(userName.getUser())) {
if (gEnforceUserClusterSeparation) {
uasserted(ErrorCodes::BadValue,
"Cannot create an x.509 user with a subjectname that would be "
@@ -1099,7 +1146,8 @@ public:
template <>
void CmdUMCTyped<UpdateUserCommand>::Invocation::typedRun(OperationContext* opCtx) {
const auto& cmd = request();
- const auto& dbname = cmd.getDbName();
+ // TODO (SERVER-67516) cmd.getDatabaseName()
+ DatabaseName dbname(getActiveTenant(opCtx), cmd.getDbName());
UserName userName(cmd.getCommandParameter(), dbname);
uassert(ErrorCodes::BadValue,
@@ -1197,7 +1245,8 @@ CmdUMCTyped<DropUserCommand> cmdDropUser;
template <>
void CmdUMCTyped<DropUserCommand>::Invocation::typedRun(OperationContext* opCtx) {
const auto& cmd = request();
- const auto& dbname = cmd.getDbName();
+ // TODO (SERVER-67516) cmd.getDatabaseName()
+ DatabaseName dbname(getActiveTenant(opCtx), cmd.getDbName());
UserName userName(cmd.getCommandParameter(), dbname);
auto* serviceContext = opCtx->getClient()->getServiceContext();
@@ -1206,16 +1255,15 @@ void CmdUMCTyped<DropUserCommand>::Invocation::typedRun(OperationContext* opCtx)
audit::logDropUser(Client::getCurrent(), userName);
- std::int64_t numMatched;
- auto status = removePrivilegeDocuments(
+ auto swNumMatched = removePrivilegeDocuments(
opCtx,
BSON(AuthorizationManager::USER_NAME_FIELD_NAME
<< userName.getUser() << AuthorizationManager::USER_DB_FIELD_NAME << userName.getDB()),
- &numMatched);
+ userName.getTenant());
// Must invalidate even on bad status - what if the write succeeded but the GLE failed?
authzManager->invalidateUserByName(opCtx, userName);
- uassertStatusOK(status);
+ auto numMatched = uassertStatusOK(swNumMatched);
uassert(ErrorCodes::UserNotFound,
str::stream() << "User '" << userName << "' not found",
@@ -1227,25 +1275,24 @@ template <>
DropAllUsersFromDatabaseReply CmdUMCTyped<DropAllUsersFromDatabaseCommand>::Invocation::typedRun(
OperationContext* opCtx) {
const auto& cmd = request();
- const auto& dbname = cmd.getDbName();
+ // TODO (SERVER-67516) cmd.getDatabaseName()
+ DatabaseName dbname(getActiveTenant(opCtx), 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);
+ audit::logDropAllUsersFromDatabase(client, dbname.db());
- std::int64_t numRemoved;
- auto status = removePrivilegeDocuments(
- opCtx, BSON(AuthorizationManager::USER_DB_FIELD_NAME << dbname), &numRemoved);
+ auto swNumRemoved = removePrivilegeDocuments(
+ opCtx, BSON(AuthorizationManager::USER_DB_FIELD_NAME << dbname.db()), dbname.tenantId());
// 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);
+ reply.setCount(uassertStatusOK(swNumRemoved));
return reply;
}
@@ -1253,7 +1300,8 @@ CmdUMCTyped<GrantRolesToUserCommand> cmdGrantRolesToUser;
template <>
void CmdUMCTyped<GrantRolesToUserCommand>::Invocation::typedRun(OperationContext* opCtx) {
const auto& cmd = request();
- const auto& dbname = cmd.getDbName();
+ // TODO (SERVER-67516) cmd.getDatabaseName()
+ DatabaseName dbname(getActiveTenant(opCtx), cmd.getDbName());
UserName userName(cmd.getCommandParameter(), dbname);
uassert(ErrorCodes::BadValue,
@@ -1288,7 +1336,8 @@ CmdUMCTyped<RevokeRolesFromUserCommand> cmdRevokeRolesFromUser;
template <>
void CmdUMCTyped<RevokeRolesFromUserCommand>::Invocation::typedRun(OperationContext* opCtx) {
const auto& cmd = request();
- const auto& dbname = cmd.getDbName();
+ // TODO (SERVER-67516) cmd.getDatabaseName()
+ DatabaseName dbname(getActiveTenant(opCtx), cmd.getDbName());
UserName userName(cmd.getCommandParameter(), dbname);
uassert(ErrorCodes::BadValue,
@@ -1325,7 +1374,8 @@ UsersInfoReply CmdUMCTyped<UsersInfoCommand, UMCInfoParams>::Invocation::typedRu
OperationContext* opCtx) {
const auto& cmd = request();
const auto& arg = cmd.getCommandParameter();
- const auto& dbname = cmd.getDbName();
+ // TODO (SERVER-67516) cmd.getDatabaseName()
+ DatabaseName dbname(getActiveTenant(opCtx), cmd.getDbName());
auto* authzManager = AuthorizationManager::get(opCtx->getServiceContext());
auto lk = uassertStatusOK(requireReadableAuthSchema26Upgrade(opCtx, authzManager));
@@ -1404,7 +1454,7 @@ UsersInfoReply CmdUMCTyped<UsersInfoCommand, UMCInfoParams>::Invocation::typedRu
// Leave the pipeline unconstrained, we want to return every user.
} else if (arg.isAllOnCurrentDB()) {
pipeline.push_back(
- BSON("$match" << BSON(AuthorizationManager::USER_DB_FIELD_NAME << dbname)));
+ BSON("$match" << BSON(AuthorizationManager::USER_DB_FIELD_NAME << dbname.db())));
} else {
invariant(arg.isExact());
BSONArrayBuilder usersMatchArray;
@@ -1448,11 +1498,10 @@ UsersInfoReply CmdUMCTyped<UsersInfoCommand, UMCInfoParams>::Invocation::typedRu
DBDirectClient client(opCtx);
rpc::OpMsgReplyBuilder replyBuilder;
- AggregateCommandRequest aggRequest(AuthorizationManager::usersCollectionNamespace,
- std::move(pipeline));
+ AggregateCommandRequest aggRequest(usersNSS(dbname.tenantId()), std::move(pipeline));
// Impose no cursor privilege requirements, as cursor is drained internally
uassertStatusOK(runAggregate(opCtx,
- AuthorizationManager::usersCollectionNamespace,
+ usersNSS(dbname.tenantId()),
aggRequest,
aggregation_request_helper::serializeToCommandObj(aggRequest),
PrivilegeVector(),
@@ -1481,24 +1530,27 @@ CmdUMCTyped<CreateRoleCommand> cmdCreateRole;
template <>
void CmdUMCTyped<CreateRoleCommand>::Invocation::typedRun(OperationContext* opCtx) {
const auto& cmd = request();
- const auto& dbname = cmd.getDbName();
+ // TODO (SERVER-67516) cmd.getDatabaseName()
+ DatabaseName dbname(getActiveTenant(opCtx), cmd.getDbName());
uassert(
ErrorCodes::BadValue, "Role name must be non-empty", !cmd.getCommandParameter().empty());
RoleName roleName(cmd.getCommandParameter(), dbname);
- uassert(ErrorCodes::BadValue, "Cannot create roles in the local database", dbname != "local");
+ uassert(ErrorCodes::BadValue,
+ "Cannot create roles in the local database",
+ dbname.db() != NamespaceString::kLocalDb);
uassert(ErrorCodes::BadValue,
"Cannot create roles in the $external database",
- dbname != "$external");
+ dbname.db() != NamespaceString::kExternalDb);
uassert(ErrorCodes::BadValue,
"Cannot create roles with the same name as a built-in role",
!auth::isBuiltinRole(roleName));
BSONObjBuilder roleObjBuilder;
- roleObjBuilder.append("_id", str::stream() << roleName.getDB() << "." << roleName.getRole());
+ roleObjBuilder.append("_id", roleName.getUnambiguousName());
roleObjBuilder.append(AuthorizationManager::ROLE_NAME_FIELD_NAME, roleName.getRole());
roleObjBuilder.append(AuthorizationManager::ROLE_DB_FIELD_NAME, roleName.getDB());
@@ -1527,14 +1579,15 @@ void CmdUMCTyped<CreateRoleCommand>::Invocation::typedRun(OperationContext* opCt
audit::logCreateRole(
client, roleName, resolvedRoleNames, cmd.getPrivileges(), bsonAuthRestrictions);
- uassertStatusOK(insertRoleDocument(opCtx, roleObjBuilder.done()));
+ uassertStatusOK(insertRoleDocument(opCtx, roleObjBuilder.done(), roleName.getTenant()));
}
CmdUMCTyped<UpdateRoleCommand> cmdUpdateRole;
template <>
void CmdUMCTyped<UpdateRoleCommand>::Invocation::typedRun(OperationContext* opCtx) {
const auto& cmd = request();
- const auto& dbname = cmd.getDbName();
+ // TODO (SERVER-67516) cmd.getDatabaseName()
+ DatabaseName dbname(getActiveTenant(opCtx), cmd.getDbName());
RoleName roleName(cmd.getCommandParameter(), dbname);
const bool hasRoles = cmd.getRoles() != boost::none;
@@ -1601,7 +1654,7 @@ void CmdUMCTyped<UpdateRoleCommand>::Invocation::typedRun(OperationContext* opCt
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);
+ authzManager->invalidateUsersByTenant(opCtx, dbname.tenantId());
uassertStatusOK(status);
}
@@ -1609,7 +1662,8 @@ CmdUMCTyped<GrantPrivilegesToRoleCommand> cmdGrantPrivilegesToRole;
template <>
void CmdUMCTyped<GrantPrivilegesToRoleCommand>::Invocation::typedRun(OperationContext* opCtx) {
const auto& cmd = request();
- const auto& dbname = cmd.getDbName();
+ // TODO (SERVER-67516) cmd.getDatabaseName()
+ DatabaseName dbname(getActiveTenant(opCtx), cmd.getDbName());
RoleName roleName(cmd.getCommandParameter(), dbname);
uassert(ErrorCodes::BadValue,
@@ -1650,7 +1704,7 @@ void CmdUMCTyped<GrantPrivilegesToRoleCommand>::Invocation::typedRun(OperationCo
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);
+ authzManager->invalidateUsersByTenant(opCtx, dbname.tenantId());
uassertStatusOK(status);
}
@@ -1658,7 +1712,8 @@ CmdUMCTyped<RevokePrivilegesFromRoleCommand> cmdRevokePrivilegesFromRole;
template <>
void CmdUMCTyped<RevokePrivilegesFromRoleCommand>::Invocation::typedRun(OperationContext* opCtx) {
const auto& cmd = request();
- const auto& dbname = cmd.getDbName();
+ // TODO (SERVER-67516) cmd.getDatabaseName()
+ DatabaseName dbname(getActiveTenant(opCtx), cmd.getDbName());
RoleName roleName(cmd.getCommandParameter(), dbname);
uassert(ErrorCodes::BadValue,
@@ -1704,7 +1759,7 @@ void CmdUMCTyped<RevokePrivilegesFromRoleCommand>::Invocation::typedRun(Operatio
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);
+ authzManager->invalidateUsersByTenant(opCtx, dbname.tenantId());
uassertStatusOK(status);
}
@@ -1712,7 +1767,8 @@ CmdUMCTyped<GrantRolesToRoleCommand> cmdGrantRolesToRole;
template <>
void CmdUMCTyped<GrantRolesToRoleCommand>::Invocation::typedRun(OperationContext* opCtx) {
const auto& cmd = request();
- const auto& dbname = cmd.getDbName();
+ // TODO (SERVER-67516) cmd.getDatabaseName()
+ DatabaseName dbname(getActiveTenant(opCtx), cmd.getDbName());
RoleName roleName(cmd.getCommandParameter(), dbname);
uassert(ErrorCodes::BadValue,
@@ -1744,7 +1800,7 @@ void CmdUMCTyped<GrantRolesToRoleCommand>::Invocation::typedRun(OperationContext
auto status = updateRoleDocument(
opCtx, roleName, BSON("$set" << BSON("roles" << containerToBSONArray(directRoles))));
// Must invalidate even on bad status - what if the write succeeded but the GLE failed?
- authzManager->invalidateUserCache(opCtx);
+ authzManager->invalidateUsersByTenant(opCtx, dbname.tenantId());
uassertStatusOK(status);
}
@@ -1752,7 +1808,8 @@ CmdUMCTyped<RevokeRolesFromRoleCommand> cmdRevokeRolesFromRole;
template <>
void CmdUMCTyped<RevokeRolesFromRoleCommand>::Invocation::typedRun(OperationContext* opCtx) {
const auto& cmd = request();
- const auto& dbname = cmd.getDbName();
+ // TODO (SERVER-67516) cmd.getDatabaseName()
+ DatabaseName dbname(getActiveTenant(opCtx), cmd.getDbName());
RoleName roleName(cmd.getCommandParameter(), dbname);
uassert(ErrorCodes::BadValue,
@@ -1783,7 +1840,7 @@ void CmdUMCTyped<RevokeRolesFromRoleCommand>::Invocation::typedRun(OperationCont
auto status = updateRoleDocument(
opCtx, roleName, BSON("$set" << BSON("roles" << containerToBSONArray(roles))));
// Must invalidate even on bad status - what if the write succeeded but the GLE failed?
- authzManager->invalidateUserCache(opCtx);
+ authzManager->invalidateUsersByTenant(opCtx, dbname.tenantId());
uassertStatusOK(status);
}
@@ -1800,6 +1857,7 @@ bool shouldRetryTransaction(const Status& status) {
}
Status retryTransactionOps(OperationContext* opCtx,
+ const boost::optional<TenantId>& tenant,
StringData forCommand,
TxnOpsCallback ops,
TxnAuditCallback audit) {
@@ -1822,7 +1880,7 @@ Status retryTransactionOps(OperationContext* opCtx,
"reason"_attr = status);
}
- UMCTransaction txn(opCtx, forCommand);
+ UMCTransaction txn(opCtx, forCommand, tenant);
status = ops(txn);
if (!status.isOK()) {
if (!shouldRetryTransaction(status)) {
@@ -1857,7 +1915,8 @@ CmdUMCTyped<DropRoleCommand> cmdDropRole;
template <>
void CmdUMCTyped<DropRoleCommand>::Invocation::typedRun(OperationContext* opCtx) {
const auto& cmd = request();
- const auto& dbname = cmd.getDbName();
+ // TODO (SERVER-67516) cmd.getDatabaseName()
+ DatabaseName dbname(getActiveTenant(opCtx), cmd.getDbName());
RoleName roleName(cmd.getCommandParameter(), dbname);
uassert(ErrorCodes::BadValue,
@@ -1874,7 +1933,7 @@ void CmdUMCTyped<DropRoleCommand>::Invocation::typedRun(OperationContext* opCtx)
// From here on, we always want to invalidate the user cache before returning.
ScopeGuard invalidateGuard([&] {
try {
- authzManager->invalidateUserCache(opCtx);
+ authzManager->invalidateUsersByTenant(opCtx, dbname.tenantId());
} catch (const AssertionException& ex) {
LOGV2_WARNING(4907701, "Failed invalidating user cache", "exception"_attr = ex);
}
@@ -1882,7 +1941,7 @@ void CmdUMCTyped<DropRoleCommand>::Invocation::typedRun(OperationContext* opCtx)
const auto dropRoleOps = [&](UMCTransaction& txn) -> Status {
// Remove this role from all users
- auto swCount = txn.update(AuthorizationManager::usersCollectionNamespace,
+ auto swCount = txn.update(usersNSS(dbname.tenantId()),
BSON("roles" << BSON("$elemMatch" << roleName.toBSON())),
BSON("$pull" << BSON("roles" << roleName.toBSON())));
if (!swCount.isOK()) {
@@ -1892,7 +1951,7 @@ void CmdUMCTyped<DropRoleCommand>::Invocation::typedRun(OperationContext* opCtx)
}
// Remove this role from all other roles
- swCount = txn.update(AuthorizationManager::rolesCollectionNamespace,
+ swCount = txn.update(rolesNSS(dbname.tenantId()),
BSON("roles" << BSON("$elemMatch" << roleName.toBSON())),
BSON("$pull" << BSON("roles" << roleName.toBSON())));
if (!swCount.isOK()) {
@@ -1902,7 +1961,7 @@ void CmdUMCTyped<DropRoleCommand>::Invocation::typedRun(OperationContext* opCtx)
}
// Finally, remove the actual role document
- swCount = txn.remove(AuthorizationManager::rolesCollectionNamespace, roleName.toBSON());
+ swCount = txn.remove(rolesNSS(dbname.tenantId()), roleName.toBSON());
if (!swCount.isOK()) {
return swCount.getStatus().withContext(str::stream()
<< "Failed to remove role " << roleName);
@@ -1911,9 +1970,10 @@ void CmdUMCTyped<DropRoleCommand>::Invocation::typedRun(OperationContext* opCtx)
return Status::OK();
};
- auto status = retryTransactionOps(opCtx, DropRoleCommand::kCommandName, dropRoleOps, [&] {
- audit::logDropRole(client, roleName);
- });
+ auto status = retryTransactionOps(
+ opCtx, roleName.getTenant(), DropRoleCommand::kCommandName, dropRoleOps, [&] {
+ audit::logDropRole(client, roleName);
+ });
if (!status.isOK()) {
uassertStatusOK(status.withContext("Failed applying dropRole transaction"));
}
@@ -1924,7 +1984,8 @@ template <>
DropAllRolesFromDatabaseReply CmdUMCTyped<DropAllRolesFromDatabaseCommand>::Invocation::typedRun(
OperationContext* opCtx) {
const auto& cmd = request();
- const auto& dbname = cmd.getDbName();
+ // TODO (SERVER-67516) cmd.getDatabaseName()
+ DatabaseName dbname(getActiveTenant(opCtx), cmd.getDbName());
auto* client = opCtx->getClient();
auto* serviceContext = client->getServiceContext();
@@ -1932,9 +1993,9 @@ DropAllRolesFromDatabaseReply CmdUMCTyped<DropAllRolesFromDatabaseCommand>::Invo
auto lk = uassertStatusOK(requireWritableAuthSchema28SCRAM(opCtx, authzManager));
// From here on, we always want to invalidate the user cache before returning.
- ScopeGuard invalidateGuard([opCtx, authzManager] {
+ ScopeGuard invalidateGuard([opCtx, authzManager, &dbname] {
try {
- authzManager->invalidateUserCache(opCtx);
+ authzManager->invalidateUsersByTenant(opCtx, dbname.tenantId());
} catch (const AssertionException& ex) {
LOGV2_WARNING(4907700, "Failed invalidating user cache", "exception"_attr = ex);
}
@@ -1942,34 +2003,33 @@ DropAllRolesFromDatabaseReply CmdUMCTyped<DropAllRolesFromDatabaseCommand>::Invo
DropAllRolesFromDatabaseReply reply;
const auto dropRoleOps = [&](UMCTransaction& txn) -> Status {
- auto roleMatch = BSON(AuthorizationManager::ROLE_DB_FIELD_NAME << dbname);
+ auto roleMatch = BSON(AuthorizationManager::ROLE_DB_FIELD_NAME << dbname.db());
auto rolesMatch = BSON("roles" << roleMatch);
// Remove these roles from all users
- auto swCount = txn.update(AuthorizationManager::usersCollectionNamespace,
- rolesMatch,
- BSON("$pull" << rolesMatch));
+ auto swCount =
+ txn.update(usersNSS(dbname.tenantId()), rolesMatch, BSON("$pull" << rolesMatch));
if (!swCount.isOK()) {
return useDefaultCode(swCount.getStatus(), ErrorCodes::UserModificationFailed)
- .withContext(str::stream() << "Failed to remove roles from \"" << dbname
+ .withContext(str::stream() << "Failed to remove roles from \"" << dbname.db()
<< "\" db from all users");
}
// Remove these roles from all other roles
- swCount = txn.update(AuthorizationManager::rolesCollectionNamespace,
- BSON("roles.db" << dbname),
+ swCount = txn.update(rolesNSS(dbname.tenantId()),
+ BSON("roles.db" << dbname.db()),
BSON("$pull" << rolesMatch));
if (!swCount.isOK()) {
return useDefaultCode(swCount.getStatus(), ErrorCodes::RoleModificationFailed)
- .withContext(str::stream() << "Failed to remove roles from \"" << dbname
+ .withContext(str::stream() << "Failed to remove roles from \"" << dbname.db()
<< "\" db from all roles");
}
// Finally, remove the actual role documents
- swCount = txn.remove(AuthorizationManager::rolesCollectionNamespace, roleMatch);
+ swCount = txn.remove(rolesNSS(dbname.tenantId()), roleMatch);
if (!swCount.isOK()) {
return swCount.getStatus().withContext(
- str::stream() << "Removed roles from \"" << dbname
+ str::stream() << "Removed roles from \"" << dbname.db()
<< "\" db "
" from all users and roles but failed to actually delete"
" those roles themselves");
@@ -1979,9 +2039,9 @@ DropAllRolesFromDatabaseReply CmdUMCTyped<DropAllRolesFromDatabaseCommand>::Invo
return Status::OK();
};
- auto status =
- retryTransactionOps(opCtx, DropAllRolesFromDatabaseCommand::kCommandName, dropRoleOps, [&] {
- audit::logDropAllRolesFromDatabase(Client::getCurrent(), dbname);
+ auto status = retryTransactionOps(
+ opCtx, dbname.tenantId(), DropAllRolesFromDatabaseCommand::kCommandName, dropRoleOps, [&] {
+ audit::logDropAllRolesFromDatabase(opCtx->getClient(), dbname.db());
});
if (!status.isOK()) {
uassertStatusOK(
@@ -2019,7 +2079,8 @@ RolesInfoReply CmdUMCTyped<RolesInfoCommand, UMCInfoParams>::Invocation::typedRu
OperationContext* opCtx) {
const auto& cmd = request();
const auto& arg = cmd.getCommandParameter();
- const auto& dbname = cmd.getDbName();
+ // TODO (SERVER-67516) cmd.getDatabaseName()
+ DatabaseName dbname(getActiveTenant(opCtx), cmd.getDbName());
auto* authzManager = AuthorizationManager::get(opCtx->getServiceContext());
auto lk = uassertStatusOK(requireReadableAuthSchema26Upgrade(opCtx, authzManager));
@@ -2069,7 +2130,8 @@ void CmdUMCTyped<InvalidateUserCacheCommand, UMCInvalidateUserCacheParams>::Invo
OperationContext* opCtx) {
auto* authzManager = AuthorizationManager::get(opCtx->getServiceContext());
auto lk = requireReadableAuthSchema26Upgrade(opCtx, authzManager);
- authzManager->invalidateUserCache(opCtx);
+ // TODO (SERVER-67516) cmd.getDatabaseName().tenantId()
+ authzManager->invalidateUsersByTenant(opCtx, getActiveTenant(opCtx));
}
CmdUMCTyped<GetUserCacheGenerationCommand, UMCGetUserCacheGenParams> cmdGetUserCacheGeneration;
@@ -2117,7 +2179,7 @@ public:
auth::checkAuthForTypedCommand(opCtx, request());
}
- NamespaceString ns() const override {
+ NamespaceString ns() const final {
return NamespaceString(request().getDbName(), "");
}
};
@@ -2126,9 +2188,14 @@ public:
return AllowedOnSecondary::kNever;
}
- bool adminOnly() const {
+ bool adminOnly() const final {
return true;
}
+
+ bool allowedWithSecurityToken() const final {
+ // TODO (SERVER-TBD) Support mergeAuthzCollections in multitenancy
+ return false;
+ }
} cmdMergeAuthzCollections;
UserName _extractUserNameFromBSON(const BSONObj& userObj) {
@@ -2235,6 +2302,28 @@ void _addUser(OperationContext* opCtx,
/**
+ * Finds all documents matching "query" in "collectionName". For each document returned,
+ * calls the function resultProcessor on it.
+ * Should only be called on collections with authorization documents in them
+ * (ie admin.system.users and admin.system.roles).
+ */
+Status queryAuthzDocument(OperationContext* opCtx,
+ const NamespaceString& nss,
+ const BSONObj& query,
+ const BSONObj& projection,
+ const std::function<void(const BSONObj&)>& resultProcessor) try {
+ DBDirectClient client(opCtx);
+ FindCommandRequest findRequest{patchTenantNSS(nss)};
+ findRequest.setFilter(query);
+ findRequest.setProjection(projection);
+ client.find(std::move(findRequest), resultProcessor);
+ return Status::OK();
+} catch (const DBException& e) {
+ return e.toStatus();
+}
+
+
+/**
* Moves all user objects from usersCollName into admin.system.users. If drop is true,
* removes any users that were in admin.system.users but not in usersCollName.
*/
@@ -2282,9 +2371,9 @@ void _processUsers(OperationContext* opCtx,
if (drop) {
for (const auto& userName : usersToDrop) {
- audit::logDropUser(Client::getCurrent(), userName);
- std::int64_t numRemoved;
- uassertStatusOK(removePrivilegeDocuments(opCtx, userName.toBSON(), &numRemoved));
+ audit::logDropUser(opCtx->getClient(), userName);
+ auto numRemoved = uassertStatusOK(
+ removePrivilegeDocuments(opCtx, userName.toBSON(), userName.getTenant()));
dassert(numRemoved == 1);
}
}
@@ -2350,7 +2439,7 @@ void _addRole(OperationContext* opCtx,
}
} else {
_auditCreateOrUpdateRole(roleObj, true);
- Status status = insertRoleDocument(opCtx, roleObj);
+ Status status = insertRoleDocument(opCtx, roleObj, roleName.getTenant());
if (!status.isOK()) {
// Match the behavior of mongorestore to continue on failure
LOGV2_WARNING(20513,
@@ -2411,8 +2500,8 @@ void _processRoles(OperationContext* opCtx,
if (drop) {
for (const auto& roleName : rolesToDrop) {
audit::logDropRole(Client::getCurrent(), roleName);
- std::int64_t numRemoved;
- uassertStatusOK(removeRoleDocuments(opCtx, roleName.toBSON(), &numRemoved));
+ auto numRemoved = uassertStatusOK(
+ removeRoleDocuments(opCtx, roleName.toBSON(), roleName.getTenant()));
dassert(numRemoved == 1);
}
}
diff --git a/src/mongo/db/commands/user_management_commands_common.cpp b/src/mongo/db/commands/user_management_commands_common.cpp
index 4645c22d506..f798b291324 100644
--- a/src/mongo/db/commands/user_management_commands_common.cpp
+++ b/src/mongo/db/commands/user_management_commands_common.cpp
@@ -79,7 +79,7 @@ Status checkAuthorizedToGrantPrivilege(AuthorizationSession* authzSession,
} // namespace
std::vector<RoleName> resolveRoleNames(const std::vector<RoleNameOrString>& possibleRoles,
- StringData dbname) {
+ const DatabaseName& dbname) {
// De-duplicate as we resolve names by using a set.
stdx::unordered_set<RoleName> roles;
for (const auto& possibleRole : possibleRoles) {
diff --git a/src/mongo/db/commands/user_management_commands_common.h b/src/mongo/db/commands/user_management_commands_common.h
index 16ce67035af..0c2af216fb3 100644
--- a/src/mongo/db/commands/user_management_commands_common.h
+++ b/src/mongo/db/commands/user_management_commands_common.h
@@ -38,6 +38,7 @@
#include "mongo/db/auth/role_name.h"
#include "mongo/db/auth/user_name.h"
#include "mongo/db/commands/user_management_commands_gen.h"
+#include "mongo/db/database_name.h"
namespace mongo {
@@ -57,7 +58,7 @@ namespace auth {
* and normalizes them to a vector of RoleNames using a passed dbname fallback.
*/
std::vector<RoleName> resolveRoleNames(const std::vector<RoleNameOrString>& possibleRoles,
- StringData dbname);
+ const DatabaseName& dbname);
//
// checkAuthorizedTo* methods
diff --git a/src/mongo/db/namespace_string.cpp b/src/mongo/db/namespace_string.cpp
index 399bebab90d..6ad3b027165 100644
--- a/src/mongo/db/namespace_string.cpp
+++ b/src/mongo/db/namespace_string.cpp
@@ -203,7 +203,23 @@ bool NamespaceString::isCollectionlessAggregateNS() const {
bool NamespaceString::isLegalClientSystemNS(
const ServerGlobalParams::FeatureCompatibility& currentFCV) const {
- if (db() == kAdminDb) {
+ auto dbname = dbName().db();
+
+ NamespaceString parsedNSS;
+ if (gMultitenancySupport && !tenantId()) {
+ // TODO (SERVER-67423) Remove support for mangled dbname in isLegalClientSystemNS check
+ // Transitional support for accepting tenantId as a mangled database name.
+ try {
+ parsedNSS = parseFromStringExpectTenantIdInMultitenancyMode(ns());
+ if (parsedNSS.tenantId()) {
+ dbname = parsedNSS.dbName().db();
+ }
+ } catch (const DBException&) {
+ // Swallow exception.
+ }
+ }
+
+ if (dbname == kAdminDb) {
if (coll() == "system.roles")
return true;
if (coll() == kServerConfigurationNamespace.coll())
@@ -212,7 +228,7 @@ bool NamespaceString::isLegalClientSystemNS(
return true;
if (coll() == "system.backup_users")
return true;
- } else if (db() == kConfigDb) {
+ } else if (dbname == kConfigDb) {
if (coll() == "system.sessions")
return true;
if (coll() == kIndexBuildEntryNamespace.coll())
@@ -223,7 +239,7 @@ bool NamespaceString::isLegalClientSystemNS(
return true;
if (coll() == kConfigsvrCoordinatorsNamespace.coll())
return true;
- } else if (db() == kLocalDb) {
+ } else if (dbname == kLocalDb) {
if (coll() == kSystemReplSetNamespace.coll())
return true;
if (coll() == "system.healthlog")
diff --git a/src/mongo/embedded/embedded_auth_manager.cpp b/src/mongo/embedded/embedded_auth_manager.cpp
index 5473cbee59a..ec2c7dc9184 100644
--- a/src/mongo/embedded/embedded_auth_manager.cpp
+++ b/src/mongo/embedded/embedded_auth_manager.cpp
@@ -120,11 +120,11 @@ public:
UASSERT_NOT_IMPLEMENTED;
}
- void invalidateUsersFromDB(OperationContext*, const StringData dbname) override {
+ void invalidateUsersFromDB(OperationContext*, const DatabaseName& dbname) override {
UASSERT_NOT_IMPLEMENTED;
}
- void invalidateUsersByTenant(OperationContext*, const TenantId&) override {
+ void invalidateUsersByTenant(OperationContext*, const boost::optional<TenantId>&) override {
UASSERT_NOT_IMPLEMENTED;
}