summaryrefslogtreecommitdiff
path: root/src/mongo/db/auth
diff options
context:
space:
mode:
authorSara Golemon <sara.golemon@mongodb.com>2022-06-08 22:22:08 -0500
committerEvergreen Agent <no-reply@evergreen.mongodb.com>2022-06-15 17:51:25 +0000
commitd762bb7bc5e99c387fe16468c562132de24c5a45 (patch)
tree7c84a777cf15c3d90a6e2ed5c4010496a222efa7 /src/mongo/db/auth
parent631ca9943ca90732a0bd7ff8454a13fc78948c10 (diff)
downloadmongo-d762bb7bc5e99c387fe16468c562132de24c5a45.tar.gz
SERVER-67148 Refactor ValidatedTenantId into ValidatedSecurityToken
Diffstat (limited to 'src/mongo/db/auth')
-rw-r--r--src/mongo/db/auth/SConscript4
-rw-r--r--src/mongo/db/auth/authorization_session_impl.cpp7
-rw-r--r--src/mongo/db/auth/security_token_authentication_guard.cpp66
-rw-r--r--src/mongo/db/auth/security_token_authentication_guard.h (renamed from src/mongo/db/auth/security_token.h)38
-rw-r--r--src/mongo/db/auth/validated_tenancy_scope.cpp (renamed from src/mongo/db/auth/security_token.cpp)169
-rw-r--r--src/mongo/db/auth/validated_tenancy_scope.h116
-rw-r--r--src/mongo/db/auth/validated_tenancy_scope_test.cpp177
7 files changed, 476 insertions, 101 deletions
diff --git a/src/mongo/db/auth/SConscript b/src/mongo/db/auth/SConscript
index 3df9d6922f2..0f4dcbc61bb 100644
--- a/src/mongo/db/auth/SConscript
+++ b/src/mongo/db/auth/SConscript
@@ -7,8 +7,9 @@ env = env.Clone()
env.Library(
target='security_token',
source=[
- 'security_token.cpp',
+ 'security_token_authentication_guard.cpp',
'security_token.idl',
+ 'validated_tenancy_scope.cpp',
],
LIBDEPS=[
'$BUILD_DIR/mongo/base',
@@ -540,6 +541,7 @@ env.CppUnitTest(
'sasl_scram_test.cpp',
'security_key_test.cpp',
'user_document_parser_test.cpp',
+ 'validated_tenancy_scope_test.cpp',
],
LIBDEPS=[
'$BUILD_DIR/mongo/base',
diff --git a/src/mongo/db/auth/authorization_session_impl.cpp b/src/mongo/db/auth/authorization_session_impl.cpp
index 173a19cfd58..5ebc878a07c 100644
--- a/src/mongo/db/auth/authorization_session_impl.cpp
+++ b/src/mongo/db/auth/authorization_session_impl.cpp
@@ -43,7 +43,7 @@
#include "mongo/db/auth/action_type.h"
#include "mongo/db/auth/authz_session_external_state.h"
#include "mongo/db/auth/privilege.h"
-#include "mongo/db/auth/security_token.h"
+#include "mongo/db/auth/validated_tenancy_scope.h"
#include "mongo/db/bson/dotted_path_support.h"
#include "mongo/db/client.h"
#include "mongo/db/namespace_string.h"
@@ -245,14 +245,15 @@ Status AuthorizationSessionImpl::addAndAuthorizeUser(OperationContext* opCtx,
stdx::lock_guard<Client> lk(*opCtx->getClient());
- if (auto token = auth::getSecurityToken(opCtx)) {
+ auto validatedTenancyScope = auth::ValidatedTenancyScope::get(opCtx);
+ if (validatedTenancyScope && validatedTenancyScope->hasAuthenticatedUser()) {
uassert(
6161501,
"Attempt to authorize via security token on connection with established authentication",
_authenticationMode != AuthenticationMode::kConnection);
uassert(6161502,
"Attempt to authorize a user other than that present in the security token",
- token->getAuthenticatedUser() == userName);
+ validatedTenancyScope->authenticatedUser() == userName);
validateSecurityTokenUserPrivileges(user->getPrivileges());
_authenticationMode = AuthenticationMode::kSecurityToken;
} else {
diff --git a/src/mongo/db/auth/security_token_authentication_guard.cpp b/src/mongo/db/auth/security_token_authentication_guard.cpp
new file mode 100644
index 00000000000..5be6de3dc75
--- /dev/null
+++ b/src/mongo/db/auth/security_token_authentication_guard.cpp
@@ -0,0 +1,66 @@
+/**
+ * Copyright (C) 2022-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/security_token_authentication_guard.h"
+
+#include "mongo/db/auth/authorization_session.h"
+#include "mongo/logv2/log.h"
+
+#define MONGO_LOGV2_DEFAULT_COMPONENT ::mongo::logv2::LogComponent::kAccessControl
+
+namespace mongo {
+namespace auth {
+
+SecurityTokenAuthenticationGuard::SecurityTokenAuthenticationGuard(
+ OperationContext* opCtx, const ValidatedTenancyScope& token) {
+ if (token.hasAuthenticatedUser()) {
+ const auto& userName = token.authenticatedUser();
+ auto* client = opCtx->getClient();
+ uassertStatusOK(AuthorizationSession::get(client)->addAndAuthorizeUser(opCtx, userName));
+ _client = client;
+
+ LOGV2_DEBUG(5838100,
+ 4,
+ "Authenticated with security token",
+ "token"_attr = token.getOriginalToken());
+ } else {
+ _client = nullptr;
+ }
+}
+
+SecurityTokenAuthenticationGuard::~SecurityTokenAuthenticationGuard() {
+ if (_client) {
+ // SecurityToken based users are "logged out" at the end of their request.
+ AuthorizationSession::get(_client)->logoutSecurityTokenUser(_client);
+ }
+}
+
+} // namespace auth
+} // namespace mongo
diff --git a/src/mongo/db/auth/security_token.h b/src/mongo/db/auth/security_token_authentication_guard.h
index 315e6efcbf4..c73e0324e5f 100644
--- a/src/mongo/db/auth/security_token.h
+++ b/src/mongo/db/auth/security_token_authentication_guard.h
@@ -1,5 +1,5 @@
/**
- * Copyright (C) 2021-present MongoDB, Inc.
+ * Copyright (C) 2022-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,
@@ -29,49 +29,27 @@
#pragma once
-#include <boost/optional.hpp>
-
-#include "mongo/bson/bsonobj.h"
-#include "mongo/db/auth/security_token_gen.h"
+#include "mongo/db/auth/validated_tenancy_scope.h"
#include "mongo/db/client.h"
#include "mongo/db/operation_context.h"
namespace mongo {
namespace auth {
+/**
+ * If ValidatedTenancyScope represents an AuthenticatedUser,
+ * that user will be authenticated against the client until this guard dies.
+ * This is used in ServiceEntryPoint to scope authentication to a single operation.
+ */
class SecurityTokenAuthenticationGuard {
public:
SecurityTokenAuthenticationGuard() = delete;
- SecurityTokenAuthenticationGuard(OperationContext* opCtx);
+ SecurityTokenAuthenticationGuard(OperationContext*, const ValidatedTenancyScope&);
~SecurityTokenAuthenticationGuard();
private:
Client* _client;
};
-/**
- * Takes an unsigned security token as input and applies
- * the temporary signature algorithm to extend it into a full SecurityToken.
- */
-BSONObj signSecurityToken(BSONObj obj);
-
-/**
- * Verify the contents of the provided security token
- * using the temporary signing algorithm,
- */
-SecurityToken verifySecurityToken(BSONObj obj);
-
-/**
- * Parse the validated SecurityToken from the OpMsg and place it as a decoration
- * on OperationContext.
- */
-void setSecurityToken(OperationContext* opCtx, const OpMsg& opMsg);
-
-/**
- * Retrieve the Security Token associated with this operation context
- */
-using MaybeSecurityToken = boost::optional<SecurityToken>;
-MaybeSecurityToken getSecurityToken(OperationContext* opCtx);
-
} // namespace auth
} // namespace mongo
diff --git a/src/mongo/db/auth/security_token.cpp b/src/mongo/db/auth/validated_tenancy_scope.cpp
index f20cffe04c2..2ab66b3abd3 100644
--- a/src/mongo/db/auth/security_token.cpp
+++ b/src/mongo/db/auth/validated_tenancy_scope.cpp
@@ -1,5 +1,5 @@
/**
- * Copyright (C) 2021-present MongoDB, Inc.
+ * Copyright (C) 2022-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,
@@ -27,26 +27,23 @@
* it in the license file.
*/
-
-#include "mongo/db/auth/security_token.h"
-
-#include <boost/optional.hpp>
+#include "mongo/db/auth/validated_tenancy_scope.h"
#include "mongo/base/init.h"
#include "mongo/db/auth/authorization_session.h"
+#include "mongo/db/auth/security_token_gen.h"
+#include "mongo/db/multitenancy.h"
#include "mongo/db/multitenancy_gen.h"
#include "mongo/db/server_feature_flags_gen.h"
-#include "mongo/db/tenant_id.h"
#include "mongo/logv2/log.h"
#include "mongo/logv2/log_detail.h"
#define MONGO_LOGV2_DEFAULT_COMPONENT ::mongo::logv2::LogComponent::kAccessControl
-
-namespace mongo {
-namespace auth {
+namespace mongo::auth {
namespace {
-const auto securityTokenDecoration = OperationContext::declareDecoration<MaybeSecurityToken>();
+const auto validatedTenancyScopeDecoration =
+ OperationContext::declareDecoration<boost::optional<ValidatedTenancyScope>>();
MONGO_INITIALIZER(SecurityTokenOptionValidate)(InitializerContext*) {
uassert(ErrorCodes::BadValue,
"multitenancySupport may not be specified if featureFlagMongoStore is not enabled",
@@ -54,15 +51,13 @@ MONGO_INITIALIZER(SecurityTokenOptionValidate)(InitializerContext*) {
if (gMultitenancySupport) {
logv2::detail::setGetTenantIDCallback([]() -> boost::optional<TenantId> {
auto* client = Client::getCurrent();
- if (!client)
+ if (!client) {
return boost::none;
+ }
if (auto* opCtx = client->getOperationContext()) {
- auto token = getSecurityToken(opCtx);
- if (token) {
- return token->getAuthenticatedUser().getTenant();
- } else {
- return boost::none;
+ if (auto token = ValidatedTenancyScope::get(opCtx)) {
+ return token->tenantId();
}
}
@@ -72,45 +67,10 @@ MONGO_INITIALIZER(SecurityTokenOptionValidate)(InitializerContext*) {
}
} // namespace
-SecurityTokenAuthenticationGuard::SecurityTokenAuthenticationGuard(OperationContext* opCtx) {
- auto token = getSecurityToken(opCtx);
- if (token == boost::none) {
- _client = nullptr;
- return;
- }
-
- auto client = opCtx->getClient();
- uassertStatusOK(AuthorizationSession::get(client)->addAndAuthorizeUser(
- opCtx, token->getAuthenticatedUser()));
- _client = client;
-}
-
-SecurityTokenAuthenticationGuard::~SecurityTokenAuthenticationGuard() {
- if (_client) {
- // SecurityToken based users are "logged out" at the end of their request.
- AuthorizationSession::get(_client)->logoutSecurityTokenUser(_client);
- }
-}
-
-BSONObj signSecurityToken(BSONObj obj) {
- auto authUserElem = obj[SecurityToken::kAuthenticatedUserFieldName];
- uassert(ErrorCodes::BadValue,
- "Invalid field(s) in token being signed",
- (authUserElem.type() == Object) && (obj.nFields() == 1));
-
- auto authUserObj = authUserElem.Obj();
- ConstDataRange authUserCDR(authUserObj.objdata(), authUserObj.objsize());
-
- // Placeholder algorithm.
- auto sig = SHA256Block::computeHash({authUserCDR});
-
- BSONObjBuilder signedToken(obj);
- signedToken.appendBinData(SecurityToken::kSigFieldName, sig.size(), BinDataGeneral, sig.data());
- return signedToken.obj();
-}
-
-SecurityToken verifySecurityToken(BSONObj obj) {
- uassert(ErrorCodes::BadValue, "Multitenancy not enabled", gMultitenancySupport);
+ValidatedTenancyScope::ValidatedTenancyScope(BSONObj obj, InitTag tag) : _originalToken(obj) {
+ uassert(ErrorCodes::InvalidOptions,
+ "Multitenancy not enabled, refusing to accept securityToken",
+ gMultitenancySupport || (tag == InitTag::kInitForShell));
auto token = SecurityToken::parse({"Security Token"}, obj);
auto authenticatedUser = token.getAuthenticatedUser();
@@ -126,22 +86,97 @@ SecurityToken verifySecurityToken(BSONObj obj) {
auto computed = SHA256Block::computeHash({authUserCDR});
uassert(ErrorCodes::Unauthorized, "Token signature invalid", computed == token.getSig());
- return token;
+
+ _tenantOrUser = std::move(authenticatedUser);
}
-void setSecurityToken(OperationContext* opCtx, const OpMsg& opMsg) {
- if (opMsg.validatedTenant && opMsg.securityToken.nFields() > 0) {
- // Use the security token directly as it has been validated by ValdiatedTenantId
- // constructor.
- securityTokenDecoration(opCtx) =
- SecurityToken::parse({"Security Token"}, opMsg.securityToken);
- LOGV2_DEBUG(5838100, 4, "Accepted security token", "token"_attr = opMsg.securityToken);
+ValidatedTenancyScope::ValidatedTenancyScope(Client* client, TenantId tenant)
+ : _tenantOrUser(std::move(tenant)) {
+ uassert(ErrorCodes::InvalidOptions,
+ "Multitenancy not enabled, refusing to accept $tenant parameter",
+ gMultitenancySupport);
+
+ uassert(ErrorCodes::Unauthorized,
+ "'$tenant' may only be specified with the useTenant action type",
+ client &&
+ AuthorizationSession::get(client)->isAuthorizedForActionsOnResource(
+ ResourcePattern::forClusterResource(), ActionType::useTenant));
+}
+
+boost::optional<ValidatedTenancyScope> ValidatedTenancyScope::create(Client* client,
+ BSONObj body,
+ BSONObj securityToken) {
+ if (!gMultitenancySupport) {
+ return boost::none;
+ }
+
+ auto dollarTenantElem = body["$tenant"_sd];
+ const bool hasToken = securityToken.nFields() > 0;
+
+ uassert(6545800,
+ "Cannot pass $tenant id if also passing securityToken",
+ dollarTenantElem.eoo() || !hasToken);
+ uassert(ErrorCodes::OperationFailed,
+ "Cannot process $tenant id when no client is available",
+ dollarTenantElem.eoo() || client);
+
+ // TODO SERVER-66822: Re-enable this uassert.
+ // uassert(ErrorCodes::Unauthorized,
+ // "Multitenancy is enabled, $tenant id or securityToken is required.",
+ // dollarTenantElem || opMsg.securityToken.nFields() > 0);
+
+ if (dollarTenantElem) {
+ return ValidatedTenancyScope(client, TenantId::parseFromBSON(dollarTenantElem));
+ } else if (hasToken) {
+ return ValidatedTenancyScope(securityToken);
+ } else {
+ return boost::none;
+ }
+}
+
+bool ValidatedTenancyScope::hasAuthenticatedUser() const {
+ return stdx::holds_alternative<UserName>(_tenantOrUser);
+}
+
+const UserName& ValidatedTenancyScope::authenticatedUser() const {
+ invariant(hasAuthenticatedUser());
+ return stdx::get<UserName>(_tenantOrUser);
+}
+
+const TenantId& ValidatedTenancyScope::tenantId() const {
+ if (hasAuthenticatedUser()) {
+ return stdx::get<UserName>(_tenantOrUser).getTenant().get();
+ } else {
+ invariant(stdx::holds_alternative<TenantId>(_tenantOrUser));
+ return stdx::get<TenantId>(_tenantOrUser);
}
}
-MaybeSecurityToken getSecurityToken(OperationContext* opCtx) {
- return securityTokenDecoration(opCtx);
+const boost::optional<ValidatedTenancyScope>& ValidatedTenancyScope::get(OperationContext* opCtx) {
+ return validatedTenancyScopeDecoration(opCtx);
+}
+
+void ValidatedTenancyScope::set(OperationContext* opCtx,
+ boost::optional<ValidatedTenancyScope> token) {
+ validatedTenancyScopeDecoration(opCtx) = std::move(token);
+}
+
+ValidatedTenancyScope::ValidatedTenancyScope(BSONObj obj, TokenForTestingTag) {
+ auto authUserElem = obj[SecurityToken::kAuthenticatedUserFieldName];
+ uassert(ErrorCodes::BadValue,
+ "Invalid field(s) in token being signed",
+ (authUserElem.type() == Object) && (obj.nFields() == 1));
+
+ auto authUserObj = authUserElem.Obj();
+ ConstDataRange authUserCDR(authUserObj.objdata(), authUserObj.objsize());
+
+ // Placeholder algorithm.
+ auto sig = SHA256Block::computeHash({authUserCDR});
+
+ BSONObjBuilder signedToken(obj);
+ signedToken.appendBinData(SecurityToken::kSigFieldName, sig.size(), BinDataGeneral, sig.data());
+ _originalToken = signedToken.obj();
+ _tenantOrUser = UserName::parseFromBSONObj(authUserObj);
}
-} // namespace auth
-} // namespace mongo
+} // namespace mongo::auth
diff --git a/src/mongo/db/auth/validated_tenancy_scope.h b/src/mongo/db/auth/validated_tenancy_scope.h
new file mode 100644
index 00000000000..302b3fdac5a
--- /dev/null
+++ b/src/mongo/db/auth/validated_tenancy_scope.h
@@ -0,0 +1,116 @@
+/**
+ * Copyright (C) 2022-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 <boost/optional.hpp>
+
+#include "mongo/bson/bsonobj.h"
+#include "mongo/db/auth/user_name.h"
+#include "mongo/db/tenant_id.h"
+#include "mongo/stdx/variant.h"
+
+namespace mongo {
+
+class Client;
+class OperationContext;
+
+namespace auth {
+
+class ValidatedTenancyScope {
+public:
+ ValidatedTenancyScope() = delete;
+ ValidatedTenancyScope(const ValidatedTenancyScope&) = default;
+
+ // kInitForShell allows parsing a securityToken without multitenancy enabled.
+ // This is required in the shell since we do not enable this setting in non-servers.
+ enum class InitTag {
+ kNormal,
+ kInitForShell,
+ };
+
+ /**
+ * Constructs a ValidatedTenancyScope by parsing a SecurityToken from a BSON object
+ * and verifying its cryptographic signature.
+ */
+ explicit ValidatedTenancyScope(BSONObj securityToken, InitTag tag = InitTag::kNormal);
+
+ /**
+ * Constructs a ValidatedTenancyScope for tenant only by validating that the
+ * current client is permitted to specify a tenant via the $tenant field.
+ */
+ ValidatedTenancyScope(Client* client, TenantId tenant);
+
+ /**
+ * Parses the client provided command body and securityToken for tenantId,
+ * and for securityToken respectively, the authenticatedUser as well.
+ *
+ * Returns boost::none when multitenancy support is not enabled.
+ */
+ static boost::optional<ValidatedTenancyScope> create(Client* client,
+ BSONObj body,
+ BSONObj securityToken);
+
+ bool hasAuthenticatedUser() const;
+ const UserName& authenticatedUser() const;
+ const TenantId& tenantId() const;
+
+ BSONObj getOriginalToken() const {
+ return _originalToken;
+ }
+
+ /**
+ * Get/Set a ValidatedTenancyScope as a decoration on the OperationContext
+ */
+ static const boost::optional<ValidatedTenancyScope>& get(OperationContext* opCtx);
+ static void set(OperationContext* opCtx, boost::optional<ValidatedTenancyScope> token);
+
+ /**
+ * Transitional token generator, do not use outside of test code.
+ */
+ struct TokenForTestingTag {};
+ explicit ValidatedTenancyScope(BSONObj token, TokenForTestingTag);
+
+ /**
+ * Backdoor API for use by FLE Query Analysis to setup a validated tenant without a security
+ * context.
+ */
+ struct TrustedFLEQueryAnalysisTag {};
+ explicit ValidatedTenancyScope(TenantId tenant, TrustedFLEQueryAnalysisTag)
+ : _tenantOrUser(std::move(tenant)) {}
+
+private:
+ // Preserve original token for serializing from MongoQ.
+ BSONObj _originalToken;
+
+ stdx::variant<UserName, TenantId> _tenantOrUser;
+};
+
+} // namespace auth
+} // namespace mongo
diff --git a/src/mongo/db/auth/validated_tenancy_scope_test.cpp b/src/mongo/db/auth/validated_tenancy_scope_test.cpp
new file mode 100644
index 00000000000..f1942f757a6
--- /dev/null
+++ b/src/mongo/db/auth/validated_tenancy_scope_test.cpp
@@ -0,0 +1,177 @@
+/**
+ * Copyright (C) 2022-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/platform/basic.h"
+
+#include "mongo/db/auth/authorization_manager_impl.h"
+#include "mongo/db/auth/authorization_session.h"
+#include "mongo/db/auth/authorization_session_impl.h"
+#include "mongo/db/auth/authz_manager_external_state_mock.h"
+#include "mongo/db/auth/security_token_gen.h"
+#include "mongo/db/auth/validated_tenancy_scope.h"
+#include "mongo/db/multitenancy_gen.h"
+#include "mongo/db/service_context_test_fixture.h"
+#include "mongo/unittest/unittest.h"
+
+namespace mongo {
+
+class AuthorizationSessionImplTestHelper {
+public:
+ /**
+ * Synthesize a user with the useTenant privilege and add them to the authorization session.
+ */
+ static void grantUseTenant(Client& client) {
+ User user(UserName("useTenant", "admin"));
+ user.setPrivileges(
+ {Privilege(ResourcePattern::forClusterResource(), ActionType::useTenant)});
+ auto* as = dynamic_cast<AuthorizationSessionImpl*>(AuthorizationSession::get(client));
+ if (as->_authenticatedUser != boost::none) {
+ as->logoutAllDatabases(&client, "AuthorizationSessionImplTestHelper"_sd);
+ }
+ as->_authenticatedUser = std::move(user);
+ as->_authenticationMode = AuthorizationSession::AuthenticationMode::kConnection;
+ as->_updateInternalAuthorizationState();
+ }
+};
+
+namespace auth {
+namespace {
+
+class ValidatedTenancyScopeTestFixture : public mongo::ScopedGlobalServiceContextForTest,
+ public unittest::Test {
+protected:
+ void setUp() final {
+ auto authzManagerState = std::make_unique<AuthzManagerExternalStateMock>();
+ auto authzManager = std::make_unique<AuthorizationManagerImpl>(
+ getServiceContext(), std::move(authzManagerState));
+ authzManager->setAuthEnabled(true);
+ AuthorizationManager::set(getServiceContext(), std::move(authzManager));
+
+ client = getServiceContext()->makeClient("test");
+ }
+
+ BSONObj makeSecurityToken(const UserName& userName) {
+ constexpr auto authUserFieldName = auth::SecurityToken::kAuthenticatedUserFieldName;
+ auto authUser = userName.toBSON(true /* serialize token */);
+ ASSERT_EQ(authUser["tenant"_sd].type(), jstOID);
+ using VTS = auth::ValidatedTenancyScope;
+ return VTS(BSON(authUserFieldName << authUser), VTS::TokenForTestingTag{})
+ .getOriginalToken();
+ }
+
+ ServiceContext::UniqueClient client;
+};
+
+TEST_F(ValidatedTenancyScopeTestFixture, MultitenancySupportOffWithoutTenantOK) {
+ gMultitenancySupport = false;
+ auto body = BSON("$db"
+ << "foo");
+
+ auto validated = ValidatedTenancyScope::create(client.get(), body, {});
+ ASSERT_TRUE(validated == boost::none);
+}
+
+TEST_F(ValidatedTenancyScopeTestFixture, MultitenancySupportWithTenantOK) {
+ gMultitenancySupport = true;
+
+ auto kOid = OID::gen();
+ auto body = BSON("ping" << 1 << "$tenant" << kOid);
+
+ AuthorizationSessionImplTestHelper::grantUseTenant(*(client.get()));
+ auto validated = ValidatedTenancyScope::create(client.get(), body, {});
+ ASSERT_TRUE(validated != boost::none);
+ ASSERT_TRUE(validated->tenantId() == TenantId(kOid));
+}
+
+TEST_F(ValidatedTenancyScopeTestFixture, MultitenancySupportWithSecurityTokenOK) {
+ gMultitenancySupport = true;
+
+ const TenantId kTenantId(OID::gen());
+ auto body = BSON("ping" << 1);
+ UserName user("user", "admin", kTenantId);
+ auto token = makeSecurityToken(user);
+
+ auto validated = ValidatedTenancyScope::create(client.get(), body, token);
+ ASSERT_TRUE(validated != boost::none);
+ ASSERT_TRUE(validated->tenantId() == kTenantId);
+ ASSERT_TRUE(validated->hasAuthenticatedUser());
+ ASSERT_TRUE(validated->authenticatedUser() == user);
+}
+
+TEST_F(ValidatedTenancyScopeTestFixture, MultitenancySupportOffWithTenantNOK) {
+ gMultitenancySupport = false;
+
+ auto kOid = OID::gen();
+ auto body = BSON("ping" << 1 << "$tenant" << kOid);
+
+ AuthorizationSessionImplTestHelper::grantUseTenant(*(client.get()));
+ ASSERT_THROWS_CODE(ValidatedTenancyScope(client.get(), TenantId(kOid)),
+ DBException,
+ ErrorCodes::InvalidOptions);
+ ASSERT_TRUE(ValidatedTenancyScope::create(client.get(), body, {}) == boost::none);
+}
+
+TEST_F(ValidatedTenancyScopeTestFixture, MultitenancySupportWithTenantNOK) {
+ gMultitenancySupport = true;
+
+ auto kOid = OID::gen();
+ auto body = BSON("ping" << 1 << "$tenant" << kOid);
+
+ ASSERT_THROWS_CODE(
+ ValidatedTenancyScope(client.get(), TenantId(kOid)), DBException, ErrorCodes::Unauthorized);
+ ASSERT_THROWS_CODE(ValidatedTenancyScope::create(client.get(), body, {}),
+ DBException,
+ ErrorCodes::Unauthorized);
+}
+
+// TODO SERVER-66822: Re-enable this test case.
+// TEST_F(ValidatedTenancyScopeTestFixture, MultitenancySupportWithoutTenantAndSecurityTokenNOK) {
+// gMultitenancySupport = true;
+// auto body = BSON("ping" << 1);
+// AuthorizationSessionImplTestHelper::grantUseTenant(*(client.get()));
+// ASSERT_THROWS_CODE(ValidatedTenancyScope::create(client.get(), body, {}), DBException,
+// ErrorCodes::Unauthorized);
+// }
+
+TEST_F(ValidatedTenancyScopeTestFixture, MultitenancySupportWithTenantAndSecurityTokenNOK) {
+ gMultitenancySupport = true;
+
+ auto kOid = OID::gen();
+ auto body = BSON("ping" << 1 << "$tenant" << kOid);
+ UserName user("user", "admin", TenantId(kOid));
+ auto token = makeSecurityToken(user);
+
+ AuthorizationSessionImplTestHelper::grantUseTenant(*(client.get()));
+ ASSERT_THROWS_CODE(
+ ValidatedTenancyScope::create(client.get(), body, token), DBException, 6545800);
+}
+
+} // namespace
+} // namespace auth
+} // namespace mongo