diff options
author | Mark Benvenuto <mark.benvenuto@mongodb.com> | 2021-02-26 12:17:39 -0500 |
---|---|---|
committer | Evergreen Agent <no-reply@evergreen.mongodb.com> | 2021-02-26 21:43:07 +0000 |
commit | 977f5f0b3305e87183a567144ef11481cec43872 (patch) | |
tree | a05c6f491f1c97b9456032280fc66e615e079637 /src/mongo/db/auth | |
parent | b769da11bca2c4e89d83efd6cd7bb4dbe4560245 (diff) | |
download | mongo-977f5f0b3305e87183a567144ef11481cec43872.tar.gz |
SERVER-54501 Write AuthorizationContract class
Diffstat (limited to 'src/mongo/db/auth')
-rw-r--r-- | src/mongo/db/auth/SConscript | 20 | ||||
-rw-r--r-- | src/mongo/db/auth/access_checks.idl | 1 | ||||
-rw-r--r-- | src/mongo/db/auth/action_set.cpp | 10 | ||||
-rw-r--r-- | src/mongo/db/auth/action_set.h | 9 | ||||
-rw-r--r-- | src/mongo/db/auth/authorization_contract.cpp | 70 | ||||
-rw-r--r-- | src/mongo/db/auth/authorization_contract.h | 103 | ||||
-rw-r--r-- | src/mongo/db/auth/authorization_contract_test.cpp | 169 | ||||
-rw-r--r-- | src/mongo/db/auth/authorization_session_test.cpp | 98 | ||||
-rw-r--r-- | src/mongo/db/auth/privilege.cpp | 4 | ||||
-rw-r--r-- | src/mongo/db/auth/privilege.h | 4 | ||||
-rw-r--r-- | src/mongo/db/auth/resource_pattern.h | 9 |
11 files changed, 437 insertions, 60 deletions
diff --git a/src/mongo/db/auth/SConscript b/src/mongo/db/auth/SConscript index 6a2bb098b93..bf18730cbc8 100644 --- a/src/mongo/db/auth/SConscript +++ b/src/mongo/db/auth/SConscript @@ -164,6 +164,7 @@ env.Library( '$BUILD_DIR/mongo/util/net/ssl_types', 'address_restriction', 'auth', + 'auth_umc', 'authorization_manager_global', 'authprivilege', 'builtin_roles', @@ -211,10 +212,25 @@ env.Library( 'action_set.cpp', 'action_type.cpp', 'action_type.idl', - 'impersonation_session.cpp', + 'authorization_contract.cpp', 'privilege.cpp', 'privilege_parser.cpp', 'resource_pattern.cpp', + ], + LIBDEPS=[ + '$BUILD_DIR/mongo/base', + '$BUILD_DIR/mongo/bson/mutable/mutable_bson', + '$BUILD_DIR/mongo/idl/idl_parser', + ], + LIBDEPS_PRIVATE=[ + '$BUILD_DIR/mongo/db/common', + ], +) + +env.Library( + target='auth_umc', + source=[ + 'impersonation_session.cpp', 'user_management_commands_parser.cpp', ], LIBDEPS=[ @@ -223,6 +239,7 @@ env.Library( '$BUILD_DIR/mongo/db/common', 'address_restriction', 'auth', + 'authprivilege', ], LIBDEPS_PRIVATE=[ '$BUILD_DIR/mongo/rpc/metadata_impersonated_user', @@ -439,6 +456,7 @@ env.CppUnitTest( source=[ 'action_set_test.cpp', 'address_restriction_test.cpp', + 'authorization_contract_test.cpp', 'auth_op_observer_test.cpp', 'authorization_manager_test.cpp', 'authorization_session_for_test.cpp', diff --git a/src/mongo/db/auth/access_checks.idl b/src/mongo/db/auth/access_checks.idl index 7eae9c8fc3d..cc5b6955cc2 100644 --- a/src/mongo/db/auth/access_checks.idl +++ b/src/mongo/db/auth/access_checks.idl @@ -35,3 +35,4 @@ enums: values: kIsAuthenticated : "is_authenticated" kIsCoAuthorized : "is_coauthorized" + kIsAuthorizedToParseNamespaceElement : "is_authorized_to_parse_namespace_element" diff --git a/src/mongo/db/auth/action_set.cpp b/src/mongo/db/auth/action_set.cpp index 4ebf0ac82e0..4744139df93 100644 --- a/src/mongo/db/auth/action_set.cpp +++ b/src/mongo/db/auth/action_set.cpp @@ -45,7 +45,7 @@ ActionSet::ActionSet(std::initializer_list<ActionType> actions) { } } -void ActionSet::addAction(const ActionType& action) { +void ActionSet::addAction(ActionType action) { if (action == ActionType::anyAction) { addAllActions(); return; @@ -65,7 +65,7 @@ void ActionSet::addAllActions() { _actions.set(); } -void ActionSet::removeAction(const ActionType& action) { +void ActionSet::removeAction(ActionType action) { _actions.set(static_cast<size_t>(action), false); _actions.set(static_cast<size_t>(ActionType::anyAction), false); } @@ -81,10 +81,14 @@ void ActionSet::removeAllActions() { _actions.reset(); } -bool ActionSet::contains(const ActionType& action) const { +bool ActionSet::contains(ActionType action) const { return _actions[static_cast<size_t>(action)]; } +bool ActionSet::contains(const ActionSet& other) const { + return (_actions | other._actions) == _actions; +} + bool ActionSet::isSupersetOf(const ActionSet& other) const { return (_actions & other._actions) == other._actions; } diff --git a/src/mongo/db/auth/action_set.h b/src/mongo/db/auth/action_set.h index 1616506dc35..be64e833cae 100644 --- a/src/mongo/db/auth/action_set.h +++ b/src/mongo/db/auth/action_set.h @@ -49,13 +49,13 @@ public: ActionSet() = default; ActionSet(std::initializer_list<ActionType> actions); - void addAction(const ActionType& action); + void addAction(ActionType action); void addAllActionsFromSet(const ActionSet& actionSet); void addAllActions(); // Removes action from the set. Also removes the "anyAction" action, if present. // Note: removing the "anyAction" action does *not* remove all other actions. - void removeAction(const ActionType& action); + void removeAction(ActionType action); void removeAllActionsFromSet(const ActionSet& actionSet); void removeAllActions(); @@ -67,7 +67,10 @@ public: return this->_actions == other._actions; } - bool contains(const ActionType& action) const; + bool contains(ActionType action) const; + + // Returns true if this action set contains the entire other action set + bool contains(const ActionSet& other) const; // Returns true only if this ActionSet contains all the actions present in the 'other' // ActionSet. diff --git a/src/mongo/db/auth/authorization_contract.cpp b/src/mongo/db/auth/authorization_contract.cpp new file mode 100644 index 00000000000..7a0d3985f4a --- /dev/null +++ b/src/mongo/db/auth/authorization_contract.cpp @@ -0,0 +1,70 @@ +/** + * Copyright (C) 2021-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/authorization_contract.h" +#include "mongo/db/auth/privilege.h" + +namespace mongo { + +void AuthorizationContract::addAccessCheck(AccessCheckEnum check) { + _checks.set(static_cast<size_t>(check), true); +} + +bool AuthorizationContract::hasAccessCheck(AccessCheckEnum check) const { + return _checks.test(static_cast<size_t>(check)); +} + +void AuthorizationContract::addPrivilege(const Privilege& p) { + auto matchType = p.getResourcePattern().matchType(); + + _privilegeChecks[static_cast<size_t>(matchType)].addAllActionsFromSet(p.getActions()); +} + +bool AuthorizationContract::hasPrivileges(const Privilege& p) const { + auto matchType = p.getResourcePattern().matchType(); + + return _privilegeChecks[static_cast<size_t>(matchType)].contains(p.getActions()); +} + +bool AuthorizationContract::contains(const AuthorizationContract& other) const { + + if ((_checks | other._checks) != _checks) { + return false; + } + + for (size_t i = 0; i < _privilegeChecks.size(); ++i) { + if (!_privilegeChecks[i].contains(other._privilegeChecks[i])) { + return false; + } + } + + return true; +} + +} // namespace mongo diff --git a/src/mongo/db/auth/authorization_contract.h b/src/mongo/db/auth/authorization_contract.h new file mode 100644 index 00000000000..b5aba515573 --- /dev/null +++ b/src/mongo/db/auth/authorization_contract.h @@ -0,0 +1,103 @@ +/** + * Copyright (C) 2021-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 <array> +#include <bitset> +#include <initializer_list> + +#include "mongo/db/auth/access_checks_gen.h" +#include "mongo/db/auth/action_set.h" +#include "mongo/db/auth/action_type.h" +#include "mongo/db/auth/privilege.h" + +namespace mongo { + +/** + * Contains an authorization contract which is the set of authorization checks a command is + * permitted to make against AuthorizationSession. + * + * There are two types of authorization checks: + * 1. Checks for privileges + * 2. Other forms of checks, simply called access check. These are generally boolean functions on + * AuthorizationSession that check criteria like isAuthorized(). + * + * This class is a lossy set. + * 1. It does not record a count of times a check has been performed. + * 2. It does not record which namespace a check is performed against. + */ +class AuthorizationContract { +public: + AuthorizationContract() = default; + + template <typename Checks, typename Privileges> + AuthorizationContract(const Checks& checks, const Privileges& privileges) { + for (const auto check : checks) { + addAccessCheck(check); + } + for (const auto& p : privileges) { + addPrivilege(p); + } + } + + /** + * Add a access check to the contract. + */ + void addAccessCheck(AccessCheckEnum check); + + /** + * Add a privilege and all actions contained in the privilege to the contract. + */ + void addPrivilege(const Privilege& p); + + /** + * Check if the contract contains a given access check. + */ + bool hasAccessCheck(AccessCheckEnum check) const; + + /** + * Check if the contract contains a privilege include all actions in the privilege. + */ + bool hasPrivileges(const Privilege& p) const; + + /** + * Check if this contract contains all the checks of the other contract. + */ + bool contains(const AuthorizationContract& other) const; + +private: + // Set of access checks performed + std::bitset<kNumAccessCheckEnum> _checks; + + // Set of privileges performed per resource pattern type + std::array<ActionSet, kNumMatchTypeEnum> _privilegeChecks; +}; + +} // namespace mongo diff --git a/src/mongo/db/auth/authorization_contract_test.cpp b/src/mongo/db/auth/authorization_contract_test.cpp new file mode 100644 index 00000000000..733fe27bb18 --- /dev/null +++ b/src/mongo/db/auth/authorization_contract_test.cpp @@ -0,0 +1,169 @@ +/** + * Copyright (C) 2021-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/access_checks_gen.h" +#include "mongo/db/auth/authorization_contract.h" + +#include "mongo/db/auth/privilege.h" +#include "mongo/unittest/unittest.h" +#include <initializer_list> + +namespace mongo { +namespace { + +TEST(AuthContractTest, Basic) { + + AuthorizationContract ac; + ac.addAccessCheck(AccessCheckEnum::kIsAuthenticated); + ac.addAccessCheck(AccessCheckEnum::kIsCoAuthorized); + + ActionSet enableShardingActions; + enableShardingActions.addAction(ActionType::enableSharding); + enableShardingActions.addAction(ActionType::refineCollectionShardKey); + enableShardingActions.addAction(ActionType::reshardCollection); + ac.addPrivilege(Privilege(ResourcePattern::forAnyNormalResource(), enableShardingActions)); + + ASSERT_TRUE(ac.hasAccessCheck(AccessCheckEnum::kIsAuthenticated)); + ASSERT_TRUE(ac.hasAccessCheck(AccessCheckEnum::kIsCoAuthorized)); + + ASSERT_FALSE(ac.hasAccessCheck(AccessCheckEnum::kIsAuthorizedToParseNamespaceElement)); + + + ASSERT_TRUE(ac.hasPrivileges( + Privilege(ResourcePattern::forAnyNormalResource(), ActionType::enableSharding))); + ASSERT_TRUE(ac.hasPrivileges( + Privilege(ResourcePattern::forAnyNormalResource(), ActionType::refineCollectionShardKey))); + ASSERT_TRUE(ac.hasPrivileges( + Privilege(ResourcePattern::forAnyNormalResource(), ActionType::reshardCollection))); + + ASSERT_FALSE( + ac.hasPrivileges(Privilege(ResourcePattern::forAnyNormalResource(), ActionType::shutdown))); + ASSERT_FALSE(ac.hasPrivileges( + Privilege(ResourcePattern::forClusterResource(), ActionType::enableSharding))); + + + ASSERT_TRUE(ac.hasPrivileges( + Privilege(ResourcePattern::forAnyNormalResource(), enableShardingActions))); + + ASSERT_TRUE(ac.contains(ac)); +} + +TEST(AuthContractTest, SimpleAccessCheck) { + + AuthorizationContract ac; + ac.addAccessCheck(AccessCheckEnum::kIsAuthenticated); + ac.addAccessCheck(AccessCheckEnum::kIsCoAuthorized); + + AuthorizationContract empty; + + ASSERT_TRUE(ac.contains(empty)); + ASSERT_FALSE(empty.contains(ac)); +} + +TEST(AuthContractTest, DifferentAccessCheck) { + + AuthorizationContract ac1; + ac1.addAccessCheck(AccessCheckEnum::kIsAuthenticated); + ac1.addAccessCheck(AccessCheckEnum::kIsCoAuthorized); + + AuthorizationContract ac2; + ac2.addAccessCheck(AccessCheckEnum::kIsAuthenticated); + ac2.addAccessCheck(AccessCheckEnum::kIsAuthorizedToParseNamespaceElement); + + ASSERT_FALSE(ac1.contains(ac2)); + ASSERT_FALSE(ac2.contains(ac1)); +} + +TEST(AuthContractTest, SimplePrivilege) { + + AuthorizationContract ac; + ac.addPrivilege(Privilege(ResourcePattern::forAnyNormalResource(), ActionType::enableSharding)); + + AuthorizationContract empty; + + ASSERT_TRUE(ac.contains(empty)); + ASSERT_FALSE(empty.contains(ac)); +} + + +TEST(AuthContractTest, DifferentResoucePattern) { + + AuthorizationContract ac1; + ac1.addPrivilege( + Privilege(ResourcePattern::forAnyNormalResource(), ActionType::enableSharding)); + + AuthorizationContract ac2; + ac2.addPrivilege(Privilege(ResourcePattern::forClusterResource(), ActionType::enableSharding)); + + ASSERT_FALSE(ac1.contains(ac2)); + ASSERT_FALSE(ac2.contains(ac1)); +} + + +TEST(AuthContractTest, DifferentActionType) { + + AuthorizationContract ac1; + ac1.addPrivilege( + Privilege(ResourcePattern::forAnyNormalResource(), ActionType::enableSharding)); + + AuthorizationContract ac2; + ac2.addPrivilege( + Privilege(ResourcePattern::forAnyNormalResource(), ActionType::grantPrivilegesToRole)); + + ASSERT_FALSE(ac1.contains(ac2)); + ASSERT_FALSE(ac2.contains(ac1)); +} + + +TEST(AuthContractTest, InitializerList) { + + AuthorizationContract ac1; + ac1.addAccessCheck(AccessCheckEnum::kIsAuthenticated); + ac1.addAccessCheck(AccessCheckEnum::kIsCoAuthorized); + + ActionSet enableShardingActions; + enableShardingActions.addAction(ActionType::enableSharding); + enableShardingActions.addAction(ActionType::refineCollectionShardKey); + enableShardingActions.addAction(ActionType::reshardCollection); + ac1.addPrivilege(Privilege(ResourcePattern::forAnyNormalResource(), enableShardingActions)); + + AuthorizationContract ac2( + std::initializer_list<AccessCheckEnum>{AccessCheckEnum::kIsAuthenticated, + AccessCheckEnum::kIsCoAuthorized}, + std::initializer_list<Privilege>{Privilege(ResourcePattern::forAnyNormalResource(), + {ActionType::enableSharding, + ActionType::refineCollectionShardKey, + ActionType::reshardCollection})}); + + ASSERT_TRUE(ac1.contains(ac2)); + ASSERT_TRUE(ac2.contains(ac1)); +} + +} // namespace +} // namespace mongo diff --git a/src/mongo/db/auth/authorization_session_test.cpp b/src/mongo/db/auth/authorization_session_test.cpp index 517117dd298..c983ba68313 100644 --- a/src/mongo/db/auth/authorization_session_test.cpp +++ b/src/mongo/db/auth/authorization_session_test.cpp @@ -606,7 +606,7 @@ TEST_F(AuthorizationSessionTest, CannotAggregateEmptyPipelineWithoutFindAction) } TEST_F(AuthorizationSessionTest, CanAggregateEmptyPipelineWithFindAction) { - authzSession->assumePrivilegesForDB(Privilege(testFooCollResource, {ActionType::find})); + authzSession->assumePrivilegesForDB(Privilege(testFooCollResource, ActionType::find)); auto aggReq = uassertStatusOK(aggregation_request_helper::parseFromBSONForTests( testFooNss, @@ -634,7 +634,7 @@ TEST_F(AuthorizationSessionTest, CannotAggregateWithoutFindActionIfFirstStageNot } TEST_F(AuthorizationSessionTest, CannotAggregateWithFindActionIfPipelineContainsIndexOrCollStats) { - authzSession->assumePrivilegesForDB(Privilege(testFooCollResource, {ActionType::find})); + authzSession->assumePrivilegesForDB(Privilege(testFooCollResource, ActionType::find)); BSONArray pipeline = BSON_ARRAY(BSON("$limit" << 1) << BSON("$collStats" << BSONObj()) << BSON("$indexStats" << BSONObj())); @@ -648,7 +648,7 @@ TEST_F(AuthorizationSessionTest, CannotAggregateWithFindActionIfPipelineContains } TEST_F(AuthorizationSessionTest, CannotAggregateCollStatsWithoutCollStatsAction) { - authzSession->assumePrivilegesForDB(Privilege(testFooCollResource, {ActionType::find})); + authzSession->assumePrivilegesForDB(Privilege(testFooCollResource, ActionType::find)); BSONArray pipeline = BSON_ARRAY(BSON("$collStats" << BSONObj())); auto aggReq = uassertStatusOK(aggregation_request_helper::parseFromBSONForTests( @@ -661,7 +661,7 @@ TEST_F(AuthorizationSessionTest, CannotAggregateCollStatsWithoutCollStatsAction) } TEST_F(AuthorizationSessionTest, CanAggregateCollStatsWithCollStatsAction) { - authzSession->assumePrivilegesForDB(Privilege(testFooCollResource, {ActionType::collStats})); + authzSession->assumePrivilegesForDB(Privilege(testFooCollResource, ActionType::collStats)); BSONArray pipeline = BSON_ARRAY(BSON("$collStats" << BSONObj())); auto aggReq = uassertStatusOK(aggregation_request_helper::parseFromBSONForTests( @@ -674,7 +674,7 @@ TEST_F(AuthorizationSessionTest, CanAggregateCollStatsWithCollStatsAction) { } TEST_F(AuthorizationSessionTest, CannotAggregateIndexStatsWithoutIndexStatsAction) { - authzSession->assumePrivilegesForDB(Privilege(testFooCollResource, {ActionType::find})); + authzSession->assumePrivilegesForDB(Privilege(testFooCollResource, ActionType::find)); BSONArray pipeline = BSON_ARRAY(BSON("$indexStats" << BSONObj())); auto aggReq = uassertStatusOK(aggregation_request_helper::parseFromBSONForTests( @@ -687,7 +687,7 @@ TEST_F(AuthorizationSessionTest, CannotAggregateIndexStatsWithoutIndexStatsActio } TEST_F(AuthorizationSessionTest, CanAggregateIndexStatsWithIndexStatsAction) { - authzSession->assumePrivilegesForDB(Privilege(testFooCollResource, {ActionType::indexStats})); + authzSession->assumePrivilegesForDB(Privilege(testFooCollResource, ActionType::indexStats)); BSONArray pipeline = BSON_ARRAY(BSON("$indexStats" << BSONObj())); auto aggReq = uassertStatusOK(aggregation_request_helper::parseFromBSONForTests( @@ -700,7 +700,7 @@ TEST_F(AuthorizationSessionTest, CanAggregateIndexStatsWithIndexStatsAction) { } TEST_F(AuthorizationSessionTest, CanAggregateCurrentOpAllUsersFalseWithoutInprogActionOnMongoD) { - authzSession->assumePrivilegesForDB(Privilege(testFooCollResource, {ActionType::find})); + authzSession->assumePrivilegesForDB(Privilege(testFooCollResource, ActionType::find)); BSONArray pipeline = BSON_ARRAY(BSON("$currentOp" << BSON("allUsers" << false))); auto aggReq = uassertStatusOK(aggregation_request_helper::parseFromBSONForTests( @@ -713,7 +713,7 @@ TEST_F(AuthorizationSessionTest, CanAggregateCurrentOpAllUsersFalseWithoutInprog } TEST_F(AuthorizationSessionTest, CannotAggregateCurrentOpAllUsersFalseWithoutInprogActionOnMongoS) { - authzSession->assumePrivilegesForDB(Privilege(testFooCollResource, {ActionType::find})); + authzSession->assumePrivilegesForDB(Privilege(testFooCollResource, ActionType::find)); BSONArray pipeline = BSON_ARRAY(BSON("$currentOp" << BSON("allUsers" << false))); auto aggReq = uassertStatusOK(aggregation_request_helper::parseFromBSONForTests( @@ -747,7 +747,7 @@ TEST_F(AuthorizationSessionTest, CannotAggregateCurrentOpAllUsersFalseIfNotAuthe } TEST_F(AuthorizationSessionTest, CannotAggregateCurrentOpAllUsersTrueWithoutInprogActionOnMongoD) { - authzSession->assumePrivilegesForDB(Privilege(testFooCollResource, {ActionType::find})); + authzSession->assumePrivilegesForDB(Privilege(testFooCollResource, ActionType::find)); BSONArray pipeline = BSON_ARRAY(BSON("$currentOp" << BSON("allUsers" << true))); auto aggReq = uassertStatusOK(aggregation_request_helper::parseFromBSONForTests( @@ -760,7 +760,7 @@ TEST_F(AuthorizationSessionTest, CannotAggregateCurrentOpAllUsersTrueWithoutInpr } TEST_F(AuthorizationSessionTest, CannotAggregateCurrentOpAllUsersTrueWithoutInprogActionOnMongoS) { - authzSession->assumePrivilegesForDB(Privilege(testFooCollResource, {ActionType::find})); + authzSession->assumePrivilegesForDB(Privilege(testFooCollResource, ActionType::find)); BSONArray pipeline = BSON_ARRAY(BSON("$currentOp" << BSON("allUsers" << true))); auto aggReq = uassertStatusOK(aggregation_request_helper::parseFromBSONForTests( @@ -774,7 +774,7 @@ TEST_F(AuthorizationSessionTest, CannotAggregateCurrentOpAllUsersTrueWithoutInpr TEST_F(AuthorizationSessionTest, CanAggregateCurrentOpAllUsersTrueWithInprogActionOnMongoD) { authzSession->assumePrivilegesForDB( - Privilege(ResourcePattern::forClusterResource(), {ActionType::inprog})); + Privilege(ResourcePattern::forClusterResource(), ActionType::inprog)); BSONArray pipeline = BSON_ARRAY(BSON("$currentOp" << BSON("allUsers" << true))); auto aggReq = uassertStatusOK(aggregation_request_helper::parseFromBSONForTests( @@ -788,7 +788,7 @@ TEST_F(AuthorizationSessionTest, CanAggregateCurrentOpAllUsersTrueWithInprogActi TEST_F(AuthorizationSessionTest, CanAggregateCurrentOpAllUsersTrueWithInprogActionOnMongoS) { authzSession->assumePrivilegesForDB( - Privilege(ResourcePattern::forClusterResource(), {ActionType::inprog})); + Privilege(ResourcePattern::forClusterResource(), ActionType::inprog)); BSONArray pipeline = BSON_ARRAY(BSON("$currentOp" << BSON("allUsers" << true))); auto aggReq = uassertStatusOK(aggregation_request_helper::parseFromBSONForTests( @@ -801,7 +801,7 @@ TEST_F(AuthorizationSessionTest, CanAggregateCurrentOpAllUsersTrueWithInprogActi } TEST_F(AuthorizationSessionTest, CannotSpoofAllUsersTrueWithoutInprogActionOnMongoD) { - authzSession->assumePrivilegesForDB(Privilege(testFooCollResource, {ActionType::find})); + authzSession->assumePrivilegesForDB(Privilege(testFooCollResource, ActionType::find)); BSONArray pipeline = BSON_ARRAY(BSON("$currentOp" << BSON("allUsers" << false << "allUsers" << true))); @@ -815,7 +815,7 @@ TEST_F(AuthorizationSessionTest, CannotSpoofAllUsersTrueWithoutInprogActionOnMon } TEST_F(AuthorizationSessionTest, CannotSpoofAllUsersTrueWithoutInprogActionOnMongoS) { - authzSession->assumePrivilegesForDB(Privilege(testFooCollResource, {ActionType::find})); + authzSession->assumePrivilegesForDB(Privilege(testFooCollResource, ActionType::find)); BSONArray pipeline = BSON_ARRAY(BSON("$currentOp" << BSON("allUsers" << false << "allUsers" << true))); @@ -829,7 +829,7 @@ TEST_F(AuthorizationSessionTest, CannotSpoofAllUsersTrueWithoutInprogActionOnMon } TEST_F(AuthorizationSessionTest, AddPrivilegesForStageFailsIfOutNamespaceIsNotValid) { - authzSession->assumePrivilegesForDB(Privilege(testFooCollResource, {ActionType::find})); + authzSession->assumePrivilegesForDB(Privilege(testFooCollResource, ActionType::find)); BSONArray pipeline = BSON_ARRAY(BSON("$out" << "")); @@ -845,7 +845,7 @@ TEST_F(AuthorizationSessionTest, AddPrivilegesForStageFailsIfOutNamespaceIsNotVa TEST_F(AuthorizationSessionTest, CannotAggregateOutWithoutInsertAndRemoveOnTargetNamespace) { // We only have find on the aggregation namespace. - authzSession->assumePrivilegesForDB(Privilege(testFooCollResource, {ActionType::find})); + authzSession->assumePrivilegesForDB(Privilege(testFooCollResource, ActionType::find)); BSONArray pipeline = BSON_ARRAY(BSON("$out" << testBarNss.coll())); auto aggReq = uassertStatusOK(aggregation_request_helper::parseFromBSONForTests( @@ -857,19 +857,19 @@ TEST_F(AuthorizationSessionTest, CannotAggregateOutWithoutInsertAndRemoveOnTarge ASSERT_FALSE(authzSession->isAuthorizedForPrivileges(privileges)); // We have insert but not remove on the $out namespace. - authzSession->assumePrivilegesForDB({Privilege(testFooCollResource, {ActionType::find}), - Privilege(testBarCollResource, {ActionType::insert})}); + authzSession->assumePrivilegesForDB({Privilege(testFooCollResource, ActionType::find), + Privilege(testBarCollResource, ActionType::insert)}); ASSERT_FALSE(authzSession->isAuthorizedForPrivileges(privileges)); // We have remove but not insert on the $out namespace. - authzSession->assumePrivilegesForDB({Privilege(testFooCollResource, {ActionType::find}), - Privilege(testBarCollResource, {ActionType::remove})}); + authzSession->assumePrivilegesForDB({Privilege(testFooCollResource, ActionType::find), + Privilege(testBarCollResource, ActionType::remove)}); ASSERT_FALSE(authzSession->isAuthorizedForPrivileges(privileges)); } TEST_F(AuthorizationSessionTest, CanAggregateOutWithInsertAndRemoveOnTargetNamespace) { authzSession->assumePrivilegesForDB( - {Privilege(testFooCollResource, {ActionType::find}), + {Privilege(testFooCollResource, ActionType::find), Privilege(testBarCollResource, {ActionType::insert, ActionType::remove})}); BSONArray pipeline = BSON_ARRAY(BSON("$out" << testBarNss.coll())); @@ -896,7 +896,7 @@ TEST_F(AuthorizationSessionTest, CanAggregateOutWithInsertAndRemoveOnTargetNames TEST_F(AuthorizationSessionTest, CannotAggregateOutBypassingValidationWithoutBypassDocumentValidationOnTargetNamespace) { authzSession->assumePrivilegesForDB( - {Privilege(testFooCollResource, {ActionType::find}), + {Privilege(testFooCollResource, ActionType::find), Privilege(testBarCollResource, {ActionType::insert, ActionType::remove})}); BSONArray pipeline = BSON_ARRAY(BSON("$out" << testBarNss.coll())); @@ -912,7 +912,7 @@ TEST_F(AuthorizationSessionTest, TEST_F(AuthorizationSessionTest, CanAggregateOutBypassingValidationWithBypassDocumentValidationOnTargetNamespace) { authzSession->assumePrivilegesForDB( - {Privilege(testFooCollResource, {ActionType::find}), + {Privilege(testFooCollResource, ActionType::find), Privilege( testBarCollResource, {ActionType::insert, ActionType::remove, ActionType::bypassDocumentValidation})}); @@ -928,7 +928,7 @@ TEST_F(AuthorizationSessionTest, } TEST_F(AuthorizationSessionTest, CannotAggregateLookupWithoutFindOnJoinedNamespace) { - authzSession->assumePrivilegesForDB(Privilege(testFooCollResource, {ActionType::find})); + authzSession->assumePrivilegesForDB(Privilege(testFooCollResource, ActionType::find)); BSONArray pipeline = BSON_ARRAY(BSON("$lookup" << BSON("from" << testBarNss.coll()))); auto aggReq = uassertStatusOK(aggregation_request_helper::parseFromBSONForTests( @@ -941,8 +941,8 @@ TEST_F(AuthorizationSessionTest, CannotAggregateLookupWithoutFindOnJoinedNamespa } TEST_F(AuthorizationSessionTest, CanAggregateLookupWithFindOnJoinedNamespace) { - authzSession->assumePrivilegesForDB({Privilege(testFooCollResource, {ActionType::find}), - Privilege(testBarCollResource, {ActionType::find})}); + authzSession->assumePrivilegesForDB({Privilege(testFooCollResource, ActionType::find), + Privilege(testBarCollResource, ActionType::find)}); BSONArray pipeline = BSON_ARRAY(BSON("$lookup" << BSON("from" << testBarNss.coll()))); auto aggReq = uassertStatusOK(aggregation_request_helper::parseFromBSONForTests( @@ -956,8 +956,8 @@ TEST_F(AuthorizationSessionTest, CanAggregateLookupWithFindOnJoinedNamespace) { TEST_F(AuthorizationSessionTest, CannotAggregateLookupWithoutFindOnNestedJoinedNamespace) { - authzSession->assumePrivilegesForDB({Privilege(testFooCollResource, {ActionType::find}), - Privilege(testBarCollResource, {ActionType::find})}); + authzSession->assumePrivilegesForDB({Privilege(testFooCollResource, ActionType::find), + Privilege(testBarCollResource, ActionType::find)}); BSONArray nestedPipeline = BSON_ARRAY(BSON("$lookup" << BSON("from" << testQuxNss.coll()))); BSONArray pipeline = BSON_ARRAY( @@ -972,9 +972,9 @@ TEST_F(AuthorizationSessionTest, CannotAggregateLookupWithoutFindOnNestedJoinedN } TEST_F(AuthorizationSessionTest, CanAggregateLookupWithFindOnNestedJoinedNamespace) { - authzSession->assumePrivilegesForDB({Privilege(testFooCollResource, {ActionType::find}), - Privilege(testBarCollResource, {ActionType::find}), - Privilege(testQuxCollResource, {ActionType::find})}); + authzSession->assumePrivilegesForDB({Privilege(testFooCollResource, ActionType::find), + Privilege(testBarCollResource, ActionType::find), + Privilege(testQuxCollResource, ActionType::find)}); BSONArray nestedPipeline = BSON_ARRAY(BSON("$lookup" << BSON("from" << testQuxNss.coll()))); BSONArray pipeline = BSON_ARRAY( @@ -989,7 +989,7 @@ TEST_F(AuthorizationSessionTest, CanAggregateLookupWithFindOnNestedJoinedNamespa } TEST_F(AuthorizationSessionTest, CheckAuthForAggregateWithDeeplyNestedLookup) { - authzSession->assumePrivilegesForDB(Privilege(testFooCollResource, {ActionType::find})); + authzSession->assumePrivilegesForDB(Privilege(testFooCollResource, ActionType::find)); // Recursively adds nested $lookup stages to 'pipelineBob', building a pipeline with // 'levelsToGo' deep $lookup stages. @@ -1035,7 +1035,7 @@ TEST_F(AuthorizationSessionTest, CheckAuthForAggregateWithDeeplyNestedLookup) { TEST_F(AuthorizationSessionTest, CannotAggregateGraphLookupWithoutFindOnJoinedNamespace) { - authzSession->assumePrivilegesForDB(Privilege(testFooCollResource, {ActionType::find})); + authzSession->assumePrivilegesForDB(Privilege(testFooCollResource, ActionType::find)); BSONArray pipeline = BSON_ARRAY(BSON("$graphLookup" << BSON("from" << testBarNss.coll()))); auto aggReq = uassertStatusOK(aggregation_request_helper::parseFromBSONForTests( @@ -1048,8 +1048,8 @@ TEST_F(AuthorizationSessionTest, CannotAggregateGraphLookupWithoutFindOnJoinedNa } TEST_F(AuthorizationSessionTest, CanAggregateGraphLookupWithFindOnJoinedNamespace) { - authzSession->assumePrivilegesForDB({Privilege(testFooCollResource, {ActionType::find}), - Privilege(testBarCollResource, {ActionType::find})}); + authzSession->assumePrivilegesForDB({Privilege(testFooCollResource, ActionType::find), + Privilege(testBarCollResource, ActionType::find)}); BSONArray pipeline = BSON_ARRAY(BSON("$graphLookup" << BSON("from" << testBarNss.coll()))); auto aggReq = uassertStatusOK(aggregation_request_helper::parseFromBSONForTests( @@ -1064,7 +1064,7 @@ TEST_F(AuthorizationSessionTest, CanAggregateGraphLookupWithFindOnJoinedNamespac TEST_F(AuthorizationSessionTest, CannotAggregateFacetWithLookupAndGraphLookupWithoutFindOnJoinedNamespaces) { // We only have find on the aggregation namespace. - authzSession->assumePrivilegesForDB(Privilege(testFooCollResource, {ActionType::find})); + authzSession->assumePrivilegesForDB(Privilege(testFooCollResource, ActionType::find)); BSONArray pipeline = BSON_ARRAY(fromjson("{$facet: {lookup: [{$lookup: {from: 'bar'}}], graphLookup: " @@ -1078,21 +1078,21 @@ TEST_F(AuthorizationSessionTest, ASSERT_FALSE(authzSession->isAuthorizedForPrivileges(privileges)); // We have find on the $lookup namespace but not on the $graphLookup namespace. - authzSession->assumePrivilegesForDB({Privilege(testFooCollResource, {ActionType::find}), - Privilege(testBarCollResource, {ActionType::find})}); + authzSession->assumePrivilegesForDB({Privilege(testFooCollResource, ActionType::find), + Privilege(testBarCollResource, ActionType::find)}); ASSERT_FALSE(authzSession->isAuthorizedForPrivileges(privileges)); // We have find on the $graphLookup namespace but not on the $lookup namespace. - authzSession->assumePrivilegesForDB({Privilege(testFooCollResource, {ActionType::find}), - Privilege(testQuxCollResource, {ActionType::find})}); + authzSession->assumePrivilegesForDB({Privilege(testFooCollResource, ActionType::find), + Privilege(testQuxCollResource, ActionType::find)}); ASSERT_FALSE(authzSession->isAuthorizedForPrivileges(privileges)); } TEST_F(AuthorizationSessionTest, CanAggregateFacetWithLookupAndGraphLookupWithFindOnJoinedNamespaces) { - authzSession->assumePrivilegesForDB({Privilege(testFooCollResource, {ActionType::find}), - Privilege(testBarCollResource, {ActionType::find}), - Privilege(testQuxCollResource, {ActionType::find})}); + authzSession->assumePrivilegesForDB({Privilege(testFooCollResource, ActionType::find), + Privilege(testBarCollResource, ActionType::find), + Privilege(testQuxCollResource, ActionType::find)}); BSONArray pipeline = BSON_ARRAY(fromjson("{$facet: {lookup: [{$lookup: {from: 'bar'}}], graphLookup: " @@ -1253,7 +1253,7 @@ TEST_F(AuthorizationSessionTest, CannotListCollectionsWithoutListCollectionsPriv TEST_F(AuthorizationSessionTest, CanListCollectionsWithListCollectionsPrivilege) { BSONObj cmd = BSON("listCollections" << 1); // The listCollections privilege authorizes the list collections command. - authzSession->assumePrivilegesForDB(Privilege(testDBResource, {ActionType::listCollections})); + authzSession->assumePrivilegesForDB(Privilege(testDBResource, ActionType::listCollections)); ASSERT_OK(authzSession->checkAuthorizedToListCollections(testFooNss.db(), cmd).getStatus()); ASSERT_OK(authzSession->checkAuthorizedToListCollections(testBarNss.db(), cmd).getStatus()); @@ -1264,7 +1264,7 @@ TEST_F(AuthorizationSessionTest, CanListOwnCollectionsWithPrivilege) { BSONObj cmd = BSON("listCollections" << 1 << "nameOnly" << true << "authorizedCollections" << true); // The listCollections privilege authorizes the list collections command. - authzSession->assumePrivilegesForDB(Privilege(testFooCollResource, {ActionType::find})); + authzSession->assumePrivilegesForDB(Privilege(testFooCollResource, ActionType::find)); ASSERT_OK(authzSession->checkAuthorizedToListCollections(testFooNss.db(), cmd).getStatus()); ASSERT_OK(authzSession->checkAuthorizedToListCollections(testBarNss.db(), cmd).getStatus()); @@ -1278,7 +1278,7 @@ TEST_F(AuthorizationSessionTest, CanCheckIfHasAnyPrivilegeOnResource) { ASSERT_FALSE(authzSession->isAuthorizedForAnyActionOnResource(testFooCollResource)); // If we have a collection privilege, we have actions on that collection - authzSession->assumePrivilegesForDB(Privilege(testFooCollResource, {ActionType::find})); + authzSession->assumePrivilegesForDB(Privilege(testFooCollResource, ActionType::find)); ASSERT_TRUE(authzSession->isAuthorizedForAnyActionOnResource(testFooCollResource)); ASSERT_FALSE(authzSession->isAuthorizedForAnyActionOnResource( ResourcePattern::forDatabaseName(testFooNss.db()))); @@ -1290,7 +1290,7 @@ TEST_F(AuthorizationSessionTest, CanCheckIfHasAnyPrivilegeOnResource) { // If we have a database privilege, we have actions on that database and all collections it // contains authzSession->assumePrivilegesForDB( - Privilege(ResourcePattern::forDatabaseName(testFooNss.db()), {ActionType::find})); + Privilege(ResourcePattern::forDatabaseName(testFooNss.db()), ActionType::find)); ASSERT_TRUE(authzSession->isAuthorizedForAnyActionOnResource(testFooCollResource)); ASSERT_TRUE(authzSession->isAuthorizedForAnyActionOnResource( ResourcePattern::forDatabaseName(testFooNss.db()))); @@ -1302,7 +1302,7 @@ TEST_F(AuthorizationSessionTest, CanCheckIfHasAnyPrivilegeOnResource) { // If we have a privilege on anyNormalResource, we have actions on all databases and all // collections they contain authzSession->assumePrivilegesForDB( - Privilege(ResourcePattern::forAnyNormalResource(), {ActionType::find})); + Privilege(ResourcePattern::forAnyNormalResource(), ActionType::find)); ASSERT_TRUE(authzSession->isAuthorizedForAnyActionOnResource(testFooCollResource)); ASSERT_TRUE(authzSession->isAuthorizedForAnyActionOnResource( ResourcePattern::forDatabaseName(testFooNss.db()))); @@ -1331,7 +1331,7 @@ TEST_F(AuthorizationSessionTest, CanUseUUIDNamespacesWithPrivilege) { // The useUUID privilege allows UUIDs to be parsed authzSession->assumePrivilegesForDB( - Privilege(ResourcePattern::forClusterResource(), {ActionType::useUUID})); + Privilege(ResourcePattern::forClusterResource(), ActionType::useUUID)); ASSERT_TRUE(authzSession->isAuthorizedToParseNamespaceElement(stringObj.firstElement())); ASSERT_TRUE(authzSession->isAuthorizedToParseNamespaceElement(uuidObj.firstElement())); diff --git a/src/mongo/db/auth/privilege.cpp b/src/mongo/db/auth/privilege.cpp index 032124b1a52..dc3cd1d75a1 100644 --- a/src/mongo/db/auth/privilege.cpp +++ b/src/mongo/db/auth/privilege.cpp @@ -54,7 +54,7 @@ void Privilege::addPrivilegesToPrivilegeVector(PrivilegeVector* privileges, } } -Privilege::Privilege(const ResourcePattern& resource, const ActionType& action) +Privilege::Privilege(const ResourcePattern& resource, const ActionType action) : _resource(resource) { _actions.addAction(action); } @@ -69,7 +69,7 @@ void Privilege::removeActions(const ActionSet& actionsToRemove) { _actions.removeAllActionsFromSet(actionsToRemove); } -bool Privilege::includesAction(const ActionType& action) const { +bool Privilege::includesAction(const ActionType action) const { return _actions.contains(action); } diff --git a/src/mongo/db/auth/privilege.h b/src/mongo/db/auth/privilege.h index 643ad18e984..48c0c238091 100644 --- a/src/mongo/db/auth/privilege.h +++ b/src/mongo/db/auth/privilege.h @@ -75,7 +75,7 @@ public: Privilege(Privilege&&) = default; Privilege& operator=(Privilege&&) = default; - Privilege(const ResourcePattern& resource, const ActionType& action); + Privilege(const ResourcePattern& resource, const ActionType action); Privilege(const ResourcePattern& resource, const ActionSet& actions); const ResourcePattern& getResourcePattern() const { @@ -90,7 +90,7 @@ public: void removeActions(const ActionSet& actionsToRemove); // Checks if the given action is present in the Privilege. - bool includesAction(const ActionType& action) const; + bool includesAction(const ActionType action) const; // Checks if the given actions are present in the Privilege. bool includesActions(const ActionSet& actions) const; diff --git a/src/mongo/db/auth/resource_pattern.h b/src/mongo/db/auth/resource_pattern.h index df85f83a73a..6f51a768120 100644 --- a/src/mongo/db/auth/resource_pattern.h +++ b/src/mongo/db/auth/resource_pattern.h @@ -49,6 +49,8 @@ namespace mongo { * authorization_session.cpp for details. */ class ResourcePattern { + friend class AuthorizationContract; + public: /** * Returns a pattern that matches absolutely any resource. @@ -188,6 +190,13 @@ public: } private: + // AuthorizationContract works directly with MatchTypeEnum. Users should not be concerned with + // how a ResourcePattern was constructed. + MatchTypeEnum matchType() const { + return _matchType; + } + +private: explicit ResourcePattern(MatchTypeEnum type) : _matchType(type) {} ResourcePattern(MatchTypeEnum type, const NamespaceString& ns) : _matchType(type), _ns(ns) {} |