diff options
author | Sara Golemon <sara.golemon@mongodb.com> | 2022-06-08 22:22:08 -0500 |
---|---|---|
committer | Evergreen Agent <no-reply@evergreen.mongodb.com> | 2022-06-15 17:51:25 +0000 |
commit | d762bb7bc5e99c387fe16468c562132de24c5a45 (patch) | |
tree | 7c84a777cf15c3d90a6e2ed5c4010496a222efa7 /src/mongo/db/auth | |
parent | 631ca9943ca90732a0bd7ff8454a13fc78948c10 (diff) | |
download | mongo-d762bb7bc5e99c387fe16468c562132de24c5a45.tar.gz |
SERVER-67148 Refactor ValidatedTenantId into ValidatedSecurityToken
Diffstat (limited to 'src/mongo/db/auth')
-rw-r--r-- | src/mongo/db/auth/SConscript | 4 | ||||
-rw-r--r-- | src/mongo/db/auth/authorization_session_impl.cpp | 7 | ||||
-rw-r--r-- | src/mongo/db/auth/security_token_authentication_guard.cpp | 66 | ||||
-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.h | 116 | ||||
-rw-r--r-- | src/mongo/db/auth/validated_tenancy_scope_test.cpp | 177 |
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 |