/* Copyright 2012 10gen Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* 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
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
*
* 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 GNU Affero General 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"
/**
* Unit tests of the AuthorizationSession type.
*/
#include "mongo/base/status.h"
#include "mongo/db/auth/action_type.h"
#include "mongo/db/auth/authorization_manager.h"
#include "mongo/db/auth/authorization_session_for_test.h"
#include "mongo/db/auth/authz_manager_external_state_mock.h"
#include "mongo/db/auth/authz_session_external_state_mock.h"
#include "mongo/db/jsobj.h"
#include "mongo/db/json.h"
#include "mongo/db/namespace_string.h"
#include "mongo/db/operation_context_noop.h"
#include "mongo/stdx/memory.h"
#include "mongo/unittest/unittest.h"
#include "mongo/util/map_util.h"
#define ASSERT_NULL(EXPR) ASSERT_FALSE(EXPR)
#define ASSERT_NON_NULL(EXPR) ASSERT_TRUE(EXPR)
namespace mongo {
namespace {
class FailureCapableAuthzManagerExternalStateMock : public AuthzManagerExternalStateMock {
public:
FailureCapableAuthzManagerExternalStateMock() : _findsShouldFail(false) {}
virtual ~FailureCapableAuthzManagerExternalStateMock() {}
void setFindsShouldFail(bool enable) {
_findsShouldFail = enable;
}
virtual Status findOne(OperationContext* txn,
const NamespaceString& collectionName,
const BSONObj& query,
BSONObj* result) {
if (_findsShouldFail && collectionName == AuthorizationManager::usersCollectionNamespace) {
return Status(ErrorCodes::UnknownError,
"findOne on admin.system.users set to fail in mock.");
}
return AuthzManagerExternalStateMock::findOne(txn, collectionName, query, result);
}
private:
bool _findsShouldFail;
};
class AuthorizationSessionTest : public ::mongo::unittest::Test {
public:
FailureCapableAuthzManagerExternalStateMock* managerState;
OperationContextNoop _txn;
AuthzSessionExternalStateMock* sessionState;
std::unique_ptr authzManager;
std::unique_ptr authzSession;
void setUp() {
auto localManagerState = stdx::make_unique();
managerState = localManagerState.get();
managerState->setAuthzVersion(AuthorizationManager::schemaVersion26Final);
authzManager = stdx::make_unique(std::move(localManagerState));
auto localSessionState =
stdx::make_unique(authzManager.get());
sessionState = localSessionState.get();
authzSession = stdx::make_unique(std::move(localSessionState));
authzManager->setAuthEnabled(true);
}
};
const NamespaceString testFooNss("test.foo");
const NamespaceString testBarNss("test.bar");
const NamespaceString testQuxNss("test.qux");
const ResourcePattern testDBResource(ResourcePattern::forDatabaseName("test"));
const ResourcePattern otherDBResource(ResourcePattern::forDatabaseName("other"));
const ResourcePattern adminDBResource(ResourcePattern::forDatabaseName("admin"));
const ResourcePattern testFooCollResource(ResourcePattern::forExactNamespace(testFooNss));
const ResourcePattern testBarCollResource(ResourcePattern::forExactNamespace(testBarNss));
const ResourcePattern testQuxCollResource(ResourcePattern::forExactNamespace(testQuxNss));
const ResourcePattern otherFooCollResource(
ResourcePattern::forExactNamespace(NamespaceString("other.foo")));
const ResourcePattern thirdFooCollResource(
ResourcePattern::forExactNamespace(NamespaceString("third.foo")));
const ResourcePattern adminFooCollResource(
ResourcePattern::forExactNamespace(NamespaceString("admin.foo")));
const ResourcePattern testUsersCollResource(
ResourcePattern::forExactNamespace(NamespaceString("test.system.users")));
const ResourcePattern otherUsersCollResource(
ResourcePattern::forExactNamespace(NamespaceString("other.system.users")));
const ResourcePattern thirdUsersCollResource(
ResourcePattern::forExactNamespace(NamespaceString("third.system.users")));
const ResourcePattern testIndexesCollResource(
ResourcePattern::forExactNamespace(NamespaceString("test.system.indexes")));
const ResourcePattern otherIndexesCollResource(
ResourcePattern::forExactNamespace(NamespaceString("other.system.indexes")));
const ResourcePattern thirdIndexesCollResource(
ResourcePattern::forExactNamespace(NamespaceString("third.system.indexes")));
const ResourcePattern testProfileCollResource(
ResourcePattern::forExactNamespace(NamespaceString("test.system.profile")));
const ResourcePattern otherProfileCollResource(
ResourcePattern::forExactNamespace(NamespaceString("other.system.profile")));
const ResourcePattern thirdProfileCollResource(
ResourcePattern::forExactNamespace(NamespaceString("third.system.profile")));
TEST_F(AuthorizationSessionTest, AddUserAndCheckAuthorization) {
// Check that disabling auth checks works
ASSERT_FALSE(
authzSession->isAuthorizedForActionsOnResource(testFooCollResource, ActionType::insert));
sessionState->setReturnValueForShouldIgnoreAuthChecks(true);
ASSERT_TRUE(
authzSession->isAuthorizedForActionsOnResource(testFooCollResource, ActionType::insert));
sessionState->setReturnValueForShouldIgnoreAuthChecks(false);
ASSERT_FALSE(
authzSession->isAuthorizedForActionsOnResource(testFooCollResource, ActionType::insert));
// Check that you can't authorize a user that doesn't exist.
ASSERT_EQUALS(ErrorCodes::UserNotFound,
authzSession->addAndAuthorizeUser(&_txn, UserName("spencer", "test")));
// Add a user with readWrite and dbAdmin on the test DB
ASSERT_OK(managerState->insertPrivilegeDocument(&_txn,
BSON("user"
<< "spencer"
<< "db"
<< "test"
<< "credentials"
<< BSON("MONGODB-CR"
<< "a")
<< "roles"
<< BSON_ARRAY(BSON("role"
<< "readWrite"
<< "db"
<< "test")
<< BSON("role"
<< "dbAdmin"
<< "db"
<< "test"))),
BSONObj()));
ASSERT_OK(authzSession->addAndAuthorizeUser(&_txn, UserName("spencer", "test")));
ASSERT_TRUE(
authzSession->isAuthorizedForActionsOnResource(testFooCollResource, ActionType::insert));
ASSERT_TRUE(
authzSession->isAuthorizedForActionsOnResource(testDBResource, ActionType::dbStats));
ASSERT_FALSE(
authzSession->isAuthorizedForActionsOnResource(otherFooCollResource, ActionType::insert));
// Add an admin user with readWriteAnyDatabase
ASSERT_OK(
managerState->insertPrivilegeDocument(&_txn,
BSON("user"
<< "admin"
<< "db"
<< "admin"
<< "credentials"
<< BSON("MONGODB-CR"
<< "a")
<< "roles"
<< BSON_ARRAY(BSON("role"
<< "readWriteAnyDatabase"
<< "db"
<< "admin"))),
BSONObj()));
ASSERT_OK(authzSession->addAndAuthorizeUser(&_txn, UserName("admin", "admin")));
ASSERT_TRUE(authzSession->isAuthorizedForActionsOnResource(
ResourcePattern::forExactNamespace(NamespaceString("anydb.somecollection")),
ActionType::insert));
ASSERT_TRUE(
authzSession->isAuthorizedForActionsOnResource(otherDBResource, ActionType::insert));
ASSERT_TRUE(
authzSession->isAuthorizedForActionsOnResource(otherFooCollResource, ActionType::insert));
ASSERT_FALSE(
authzSession->isAuthorizedForActionsOnResource(otherFooCollResource, ActionType::collMod));
ASSERT_TRUE(
authzSession->isAuthorizedForActionsOnResource(testFooCollResource, ActionType::insert));
authzSession->logoutDatabase("test");
ASSERT_TRUE(
authzSession->isAuthorizedForActionsOnResource(otherFooCollResource, ActionType::insert));
ASSERT_TRUE(
authzSession->isAuthorizedForActionsOnResource(testFooCollResource, ActionType::insert));
ASSERT_FALSE(
authzSession->isAuthorizedForActionsOnResource(testFooCollResource, ActionType::collMod));
authzSession->logoutDatabase("admin");
ASSERT_FALSE(
authzSession->isAuthorizedForActionsOnResource(otherFooCollResource, ActionType::insert));
ASSERT_FALSE(
authzSession->isAuthorizedForActionsOnResource(testFooCollResource, ActionType::insert));
ASSERT_FALSE(
authzSession->isAuthorizedForActionsOnResource(testFooCollResource, ActionType::collMod));
}
TEST_F(AuthorizationSessionTest, DuplicateRolesOK) {
// Add a user with doubled-up readWrite and single dbAdmin on the test DB
ASSERT_OK(managerState->insertPrivilegeDocument(&_txn,
BSON("user"
<< "spencer"
<< "db"
<< "test"
<< "credentials"
<< BSON("MONGODB-CR"
<< "a")
<< "roles"
<< BSON_ARRAY(BSON("role"
<< "readWrite"
<< "db"
<< "test")
<< BSON("role"
<< "dbAdmin"
<< "db"
<< "test")
<< BSON("role"
<< "readWrite"
<< "db"
<< "test"))),
BSONObj()));
ASSERT_OK(authzSession->addAndAuthorizeUser(&_txn, UserName("spencer", "test")));
ASSERT_TRUE(
authzSession->isAuthorizedForActionsOnResource(testFooCollResource, ActionType::insert));
ASSERT_TRUE(
authzSession->isAuthorizedForActionsOnResource(testDBResource, ActionType::dbStats));
ASSERT_FALSE(
authzSession->isAuthorizedForActionsOnResource(otherFooCollResource, ActionType::insert));
}
TEST_F(AuthorizationSessionTest, SystemCollectionsAccessControl) {
ASSERT_OK(managerState->insertPrivilegeDocument(&_txn,
BSON("user"
<< "rw"
<< "db"
<< "test"
<< "credentials"
<< BSON("MONGODB-CR"
<< "a")
<< "roles"
<< BSON_ARRAY(BSON("role"
<< "readWrite"
<< "db"
<< "test")
<< BSON("role"
<< "dbAdmin"
<< "db"
<< "test"))),
BSONObj()));
ASSERT_OK(managerState->insertPrivilegeDocument(&_txn,
BSON("user"
<< "useradmin"
<< "db"
<< "test"
<< "credentials"
<< BSON("MONGODB-CR"
<< "a")
<< "roles"
<< BSON_ARRAY(BSON("role"
<< "userAdmin"
<< "db"
<< "test"))),
BSONObj()));
ASSERT_OK(
managerState->insertPrivilegeDocument(&_txn,
BSON("user"
<< "rwany"
<< "db"
<< "test"
<< "credentials"
<< BSON("MONGODB-CR"
<< "a")
<< "roles"
<< BSON_ARRAY(BSON("role"
<< "readWriteAnyDatabase"
<< "db"
<< "admin")
<< BSON("role"
<< "dbAdminAnyDatabase"
<< "db"
<< "admin"))),
BSONObj()));
ASSERT_OK(
managerState->insertPrivilegeDocument(&_txn,
BSON("user"
<< "useradminany"
<< "db"
<< "test"
<< "credentials"
<< BSON("MONGODB-CR"
<< "a")
<< "roles"
<< BSON_ARRAY(BSON("role"
<< "userAdminAnyDatabase"
<< "db"
<< "admin"))),
BSONObj()));
ASSERT_OK(authzSession->addAndAuthorizeUser(&_txn, UserName("rwany", "test")));
ASSERT_FALSE(
authzSession->isAuthorizedForActionsOnResource(testUsersCollResource, ActionType::insert));
ASSERT_FALSE(
authzSession->isAuthorizedForActionsOnResource(testUsersCollResource, ActionType::find));
ASSERT_FALSE(
authzSession->isAuthorizedForActionsOnResource(otherUsersCollResource, ActionType::insert));
ASSERT_FALSE(
authzSession->isAuthorizedForActionsOnResource(otherUsersCollResource, ActionType::find));
ASSERT_TRUE(
authzSession->isAuthorizedForActionsOnResource(testIndexesCollResource, ActionType::find));
ASSERT_TRUE(
authzSession->isAuthorizedForActionsOnResource(testProfileCollResource, ActionType::find));
ASSERT_TRUE(
authzSession->isAuthorizedForActionsOnResource(otherIndexesCollResource, ActionType::find));
ASSERT_TRUE(
authzSession->isAuthorizedForActionsOnResource(otherProfileCollResource, ActionType::find));
// Logging in as useradminany@test implicitly logs out rwany@test.
ASSERT_OK(authzSession->addAndAuthorizeUser(&_txn, UserName("useradminany", "test")));
ASSERT_FALSE(
authzSession->isAuthorizedForActionsOnResource(testUsersCollResource, ActionType::insert));
ASSERT_TRUE(
authzSession->isAuthorizedForActionsOnResource(testUsersCollResource, ActionType::find));
ASSERT_FALSE(
authzSession->isAuthorizedForActionsOnResource(otherUsersCollResource, ActionType::insert));
ASSERT_TRUE(
authzSession->isAuthorizedForActionsOnResource(otherUsersCollResource, ActionType::find));
ASSERT_FALSE(
authzSession->isAuthorizedForActionsOnResource(testIndexesCollResource, ActionType::find));
ASSERT_FALSE(
authzSession->isAuthorizedForActionsOnResource(testProfileCollResource, ActionType::find));
ASSERT_FALSE(
authzSession->isAuthorizedForActionsOnResource(otherIndexesCollResource, ActionType::find));
ASSERT_FALSE(
authzSession->isAuthorizedForActionsOnResource(otherProfileCollResource, ActionType::find));
// Logging in as rw@test implicitly logs out useradminany@test.
ASSERT_OK(authzSession->addAndAuthorizeUser(&_txn, UserName("rw", "test")));
ASSERT_FALSE(
authzSession->isAuthorizedForActionsOnResource(testUsersCollResource, ActionType::insert));
ASSERT_FALSE(
authzSession->isAuthorizedForActionsOnResource(testUsersCollResource, ActionType::find));
ASSERT_FALSE(
authzSession->isAuthorizedForActionsOnResource(otherUsersCollResource, ActionType::insert));
ASSERT_FALSE(
authzSession->isAuthorizedForActionsOnResource(otherUsersCollResource, ActionType::find));
ASSERT_TRUE(
authzSession->isAuthorizedForActionsOnResource(testIndexesCollResource, ActionType::find));
ASSERT_TRUE(
authzSession->isAuthorizedForActionsOnResource(testProfileCollResource, ActionType::find));
ASSERT_FALSE(
authzSession->isAuthorizedForActionsOnResource(otherIndexesCollResource, ActionType::find));
ASSERT_FALSE(
authzSession->isAuthorizedForActionsOnResource(otherProfileCollResource, ActionType::find));
// Logging in as useradmin@test implicitly logs out rw@test.
ASSERT_OK(authzSession->addAndAuthorizeUser(&_txn, UserName("useradmin", "test")));
ASSERT_FALSE(
authzSession->isAuthorizedForActionsOnResource(testUsersCollResource, ActionType::insert));
ASSERT_FALSE(
authzSession->isAuthorizedForActionsOnResource(testUsersCollResource, ActionType::find));
ASSERT_FALSE(
authzSession->isAuthorizedForActionsOnResource(otherUsersCollResource, ActionType::insert));
ASSERT_FALSE(
authzSession->isAuthorizedForActionsOnResource(otherUsersCollResource, ActionType::find));
ASSERT_FALSE(
authzSession->isAuthorizedForActionsOnResource(testIndexesCollResource, ActionType::find));
ASSERT_FALSE(
authzSession->isAuthorizedForActionsOnResource(testProfileCollResource, ActionType::find));
ASSERT_FALSE(
authzSession->isAuthorizedForActionsOnResource(otherIndexesCollResource, ActionType::find));
ASSERT_FALSE(
authzSession->isAuthorizedForActionsOnResource(otherProfileCollResource, ActionType::find));
}
TEST_F(AuthorizationSessionTest, InvalidateUser) {
// Add a readWrite user
ASSERT_OK(managerState->insertPrivilegeDocument(&_txn,
BSON("user"
<< "spencer"
<< "db"
<< "test"
<< "credentials"
<< BSON("MONGODB-CR"
<< "a")
<< "roles"
<< BSON_ARRAY(BSON("role"
<< "readWrite"
<< "db"
<< "test"))),
BSONObj()));
ASSERT_OK(authzSession->addAndAuthorizeUser(&_txn, UserName("spencer", "test")));
ASSERT_TRUE(
authzSession->isAuthorizedForActionsOnResource(testFooCollResource, ActionType::find));
ASSERT_TRUE(
authzSession->isAuthorizedForActionsOnResource(testFooCollResource, ActionType::insert));
User* user = authzSession->lookupUser(UserName("spencer", "test"));
ASSERT(user->isValid());
// Change the user to be read-only
int ignored;
managerState->remove(
&_txn, AuthorizationManager::usersCollectionNamespace, BSONObj(), BSONObj(), &ignored);
ASSERT_OK(managerState->insertPrivilegeDocument(&_txn,
BSON("user"
<< "spencer"
<< "db"
<< "test"
<< "credentials"
<< BSON("MONGODB-CR"
<< "a")
<< "roles"
<< BSON_ARRAY(BSON("role"
<< "read"
<< "db"
<< "test"))),
BSONObj()));
// Make sure that invalidating the user causes the session to reload its privileges.
authzManager->invalidateUserByName(user->getName());
authzSession->startRequest(&_txn); // Refreshes cached data for invalid users
ASSERT_TRUE(
authzSession->isAuthorizedForActionsOnResource(testFooCollResource, ActionType::find));
ASSERT_FALSE(
authzSession->isAuthorizedForActionsOnResource(testFooCollResource, ActionType::insert));
user = authzSession->lookupUser(UserName("spencer", "test"));
ASSERT(user->isValid());
// Delete the user.
managerState->remove(
&_txn, AuthorizationManager::usersCollectionNamespace, BSONObj(), BSONObj(), &ignored);
// Make sure that invalidating the user causes the session to reload its privileges.
authzManager->invalidateUserByName(user->getName());
authzSession->startRequest(&_txn); // Refreshes cached data for invalid users
ASSERT_FALSE(
authzSession->isAuthorizedForActionsOnResource(testFooCollResource, ActionType::find));
ASSERT_FALSE(
authzSession->isAuthorizedForActionsOnResource(testFooCollResource, ActionType::insert));
ASSERT_FALSE(authzSession->lookupUser(UserName("spencer", "test")));
}
TEST_F(AuthorizationSessionTest, UseOldUserInfoInFaceOfConnectivityProblems) {
// Add a readWrite user
ASSERT_OK(managerState->insertPrivilegeDocument(&_txn,
BSON("user"
<< "spencer"
<< "db"
<< "test"
<< "credentials"
<< BSON("MONGODB-CR"
<< "a")
<< "roles"
<< BSON_ARRAY(BSON("role"
<< "readWrite"
<< "db"
<< "test"))),
BSONObj()));
ASSERT_OK(authzSession->addAndAuthorizeUser(&_txn, UserName("spencer", "test")));
ASSERT_TRUE(
authzSession->isAuthorizedForActionsOnResource(testFooCollResource, ActionType::find));
ASSERT_TRUE(
authzSession->isAuthorizedForActionsOnResource(testFooCollResource, ActionType::insert));
User* user = authzSession->lookupUser(UserName("spencer", "test"));
ASSERT(user->isValid());
// Change the user to be read-only
int ignored;
managerState->setFindsShouldFail(true);
managerState->remove(
&_txn, AuthorizationManager::usersCollectionNamespace, BSONObj(), BSONObj(), &ignored);
ASSERT_OK(managerState->insertPrivilegeDocument(&_txn,
BSON("user"
<< "spencer"
<< "db"
<< "test"
<< "credentials"
<< BSON("MONGODB-CR"
<< "a")
<< "roles"
<< BSON_ARRAY(BSON("role"
<< "read"
<< "db"
<< "test"))),
BSONObj()));
// Even though the user's privileges have been reduced, since we've configured user
// document lookup to fail, the authz session should continue to use its known out-of-date
// privilege data.
authzManager->invalidateUserByName(user->getName());
authzSession->startRequest(&_txn); // Refreshes cached data for invalid users
ASSERT_TRUE(
authzSession->isAuthorizedForActionsOnResource(testFooCollResource, ActionType::find));
ASSERT_TRUE(
authzSession->isAuthorizedForActionsOnResource(testFooCollResource, ActionType::insert));
// Once we configure document lookup to succeed again, authorization checks should
// observe the new values.
managerState->setFindsShouldFail(false);
authzSession->startRequest(&_txn); // Refreshes cached data for invalid users
ASSERT_TRUE(
authzSession->isAuthorizedForActionsOnResource(testFooCollResource, ActionType::find));
ASSERT_FALSE(
authzSession->isAuthorizedForActionsOnResource(testFooCollResource, ActionType::insert));
}
TEST_F(AuthorizationSessionTest, CheckAuthForAggregateFailsIfPipelineIsNotAnArray) {
BSONObj cmdObjIntPipeline = BSON("aggregate" << testFooNss.coll() << "pipeline" << 7);
ASSERT_EQ(ErrorCodes::TypeMismatch,
authzSession->checkAuthForAggregate(testFooNss, cmdObjIntPipeline));
BSONObj cmdObjObjPipeline = BSON("aggregate" << testFooNss.coll() << "pipeline" << BSONObj());
ASSERT_EQ(ErrorCodes::TypeMismatch,
authzSession->checkAuthForAggregate(testFooNss, cmdObjObjPipeline));
BSONObj cmdObjNoPipeline = BSON("aggregate" << testFooNss.coll());
ASSERT_EQ(ErrorCodes::TypeMismatch,
authzSession->checkAuthForAggregate(testFooNss, cmdObjNoPipeline));
}
TEST_F(AuthorizationSessionTest, CheckAuthForAggregateFailsIfPipelineFirstStageIsNotAnObject) {
BSONObj cmdObjFirstStageInt =
BSON("aggregate" << testFooNss.coll() << "pipeline" << BSON_ARRAY(7));
ASSERT_EQ(ErrorCodes::TypeMismatch,
authzSession->checkAuthForAggregate(testFooNss, cmdObjFirstStageInt));
BSONObj cmdObjFirstStageArray =
BSON("aggregate" << testFooNss.coll() << "pipeline" << BSON_ARRAY(BSONArray()));
ASSERT_EQ(ErrorCodes::TypeMismatch,
authzSession->checkAuthForAggregate(testFooNss, cmdObjFirstStageArray));
}
TEST_F(AuthorizationSessionTest, CannotAggregateEmptyPipelineWithoutFindAction) {
BSONObj cmdObj = BSON("aggregate" << testFooNss.coll() << "pipeline" << BSONArray());
ASSERT_EQ(ErrorCodes::Unauthorized, authzSession->checkAuthForAggregate(testFooNss, cmdObj));
}
TEST_F(AuthorizationSessionTest, CanAggregateEmptyPipelineWithFindAction) {
authzSession->assumePrivilegesForDB(Privilege(testFooCollResource, {ActionType::find}));
BSONObj cmdObj = BSON("aggregate" << testFooNss.coll() << "pipeline" << BSONArray());
ASSERT_OK(authzSession->checkAuthForAggregate(testFooNss, cmdObj));
}
TEST_F(AuthorizationSessionTest, CannotAggregateWithoutFindActionIfFirstStageNotIndexOrCollStats) {
authzSession->assumePrivilegesForDB(
Privilege(testFooCollResource, {ActionType::indexStats, ActionType::collStats}));
BSONArray pipeline = BSON_ARRAY(BSON("$limit" << 1) << BSON("$collStats" << BSONObj())
<< BSON("$indexStats" << BSONObj()));
BSONObj cmdObj = BSON("aggregate" << testFooNss.coll() << "pipeline" << pipeline);
ASSERT_EQ(ErrorCodes::Unauthorized, authzSession->checkAuthForAggregate(testFooNss, cmdObj));
}
TEST_F(AuthorizationSessionTest, CanAggregateWithFindActionIfFirstStageNotIndexOrCollStats) {
authzSession->assumePrivilegesForDB(Privilege(testFooCollResource, {ActionType::find}));
BSONArray pipeline = BSON_ARRAY(BSON("$limit" << 1) << BSON("$collStats" << BSONObj())
<< BSON("$indexStats" << BSONObj()));
BSONObj cmdObj = BSON("aggregate" << testFooNss.coll() << "pipeline" << pipeline);
ASSERT_OK(authzSession->checkAuthForAggregate(testFooNss, cmdObj));
}
TEST_F(AuthorizationSessionTest, CannotAggregateCollStatsWithoutCollStatsAction) {
authzSession->assumePrivilegesForDB(Privilege(testFooCollResource, {ActionType::find}));
BSONArray pipeline = BSON_ARRAY(BSON("$collStats" << BSONObj()));
BSONObj cmdObj = BSON("aggregate" << testFooNss.coll() << "pipeline" << pipeline);
ASSERT_EQ(ErrorCodes::Unauthorized, authzSession->checkAuthForAggregate(testFooNss, cmdObj));
}
TEST_F(AuthorizationSessionTest, CanAggregateCollStatsWithCollStatsAction) {
authzSession->assumePrivilegesForDB(Privilege(testFooCollResource, {ActionType::collStats}));
BSONArray pipeline = BSON_ARRAY(BSON("$collStats" << BSONObj()));
BSONObj cmdObj = BSON("aggregate" << testFooNss.coll() << "pipeline" << pipeline);
ASSERT_OK(authzSession->checkAuthForAggregate(testFooNss, cmdObj));
}
TEST_F(AuthorizationSessionTest, CannotAggregateIndexStatsWithoutIndexStatsAction) {
authzSession->assumePrivilegesForDB(Privilege(testFooCollResource, {ActionType::find}));
BSONArray pipeline = BSON_ARRAY(BSON("$indexStats" << BSONObj()));
BSONObj cmdObj = BSON("aggregate" << testFooNss.coll() << "pipeline" << pipeline);
ASSERT_EQ(ErrorCodes::Unauthorized, authzSession->checkAuthForAggregate(testFooNss, cmdObj));
}
TEST_F(AuthorizationSessionTest, CanAggregateIndexStatsWithIndexStatsAction) {
authzSession->assumePrivilegesForDB(Privilege(testFooCollResource, {ActionType::indexStats}));
BSONArray pipeline = BSON_ARRAY(BSON("$indexStats" << BSONObj()));
BSONObj cmdObj = BSON("aggregate" << testFooNss.coll() << "pipeline" << pipeline);
ASSERT_OK(authzSession->checkAuthForAggregate(testFooNss, cmdObj));
}
TEST_F(AuthorizationSessionTest, AddPrivilegesForStageFailsIfOutNamespaceIsNotValid) {
BSONArray pipeline = BSON_ARRAY(BSON("$out"
<< ""));
BSONObj cmdObj = BSON("aggregate" << testFooNss.coll() << "pipeline" << pipeline);
ASSERT_THROWS_CODE(
authzSession->checkAuthForAggregate(testFooNss, cmdObj), UserException, 17139);
}
TEST_F(AuthorizationSessionTest, CannotAggregateOutWithoutInsertAndRemoveOnTargetNamespace) {
// We only have find on the aggregation namespace.
authzSession->assumePrivilegesForDB(Privilege(testFooCollResource, {ActionType::find}));
BSONArray pipeline = BSON_ARRAY(BSON("$out" << testBarNss.coll()));
BSONObj cmdObj = BSON("aggregate" << testFooNss.coll() << "pipeline" << pipeline);
ASSERT_EQ(ErrorCodes::Unauthorized, authzSession->checkAuthForAggregate(testFooNss, cmdObj));
// We have insert but not remove on the $out namespace.
authzSession->assumePrivilegesForDB({Privilege(testFooCollResource, {ActionType::find}),
Privilege(testBarCollResource, {ActionType::insert})});
ASSERT_EQ(ErrorCodes::Unauthorized, authzSession->checkAuthForAggregate(testFooNss, cmdObj));
// We have remove but not insert on the $out namespace.
authzSession->assumePrivilegesForDB({Privilege(testFooCollResource, {ActionType::find}),
Privilege(testBarCollResource, {ActionType::remove})});
ASSERT_EQ(ErrorCodes::Unauthorized, authzSession->checkAuthForAggregate(testFooNss, cmdObj));
}
TEST_F(AuthorizationSessionTest, CanAggregateOutWithInsertAndRemoveOnTargetNamespace) {
authzSession->assumePrivilegesForDB(
{Privilege(testFooCollResource, {ActionType::find}),
Privilege(testBarCollResource, {ActionType::insert, ActionType::remove})});
BSONArray pipeline = BSON_ARRAY(BSON("$out" << testBarNss.coll()));
BSONObj cmdObj = BSON("aggregate" << testFooNss.coll() << "pipeline" << pipeline);
ASSERT_OK(authzSession->checkAuthForAggregate(testFooNss, cmdObj));
BSONObj cmdObjNoBypassDocumentValidation = BSON(
"aggregate" << testFooNss.coll() << "pipeline" << pipeline << "bypassDocumentValidation"
<< false);
ASSERT_OK(authzSession->checkAuthForAggregate(testFooNss, cmdObjNoBypassDocumentValidation));
}
TEST_F(AuthorizationSessionTest,
CannotAggregateOutBypassingValidationWithoutBypassDocumentValidationOnTargetNamespace) {
authzSession->assumePrivilegesForDB(
{Privilege(testFooCollResource, {ActionType::find}),
Privilege(testBarCollResource, {ActionType::insert, ActionType::remove})});
BSONArray pipeline = BSON_ARRAY(BSON("$out" << testBarNss.coll()));
BSONObj cmdObj = BSON("aggregate" << testFooNss.coll() << "pipeline" << pipeline
<< "bypassDocumentValidation"
<< true);
ASSERT_EQ(ErrorCodes::Unauthorized, authzSession->checkAuthForAggregate(testFooNss, cmdObj));
}
TEST_F(AuthorizationSessionTest,
CanAggregateOutBypassingValidationWithBypassDocumentValidationOnTargetNamespace) {
authzSession->assumePrivilegesForDB(
{Privilege(testFooCollResource, {ActionType::find}),
Privilege(
testBarCollResource,
{ActionType::insert, ActionType::remove, ActionType::bypassDocumentValidation})});
BSONArray pipeline = BSON_ARRAY(BSON("$out" << testBarNss.coll()));
BSONObj cmdObj = BSON("aggregate" << testFooNss.coll() << "pipeline" << pipeline
<< "bypassDocumentValidation"
<< true);
ASSERT_OK(authzSession->checkAuthForAggregate(testFooNss, cmdObj));
}
TEST_F(AuthorizationSessionTest, CannotAggregateLookupWithoutFindOnJoinedNamespace) {
authzSession->assumePrivilegesForDB(Privilege(testFooCollResource, {ActionType::find}));
BSONArray pipeline = BSON_ARRAY(BSON("$lookup" << BSON("from" << testBarNss.coll())));
BSONObj cmdObj = BSON("aggregate" << testFooNss.coll() << "pipeline" << pipeline);
ASSERT_EQ(ErrorCodes::Unauthorized, authzSession->checkAuthForAggregate(testFooNss, cmdObj));
}
TEST_F(AuthorizationSessionTest, CanAggregateLookupWithFindOnJoinedNamespace) {
authzSession->assumePrivilegesForDB({Privilege(testFooCollResource, {ActionType::find}),
Privilege(testBarCollResource, {ActionType::find})});
BSONArray pipeline = BSON_ARRAY(BSON("$lookup" << BSON("from" << testBarNss.coll())));
BSONObj cmdObj = BSON("aggregate" << testFooNss.coll() << "pipeline" << pipeline);
ASSERT_OK(authzSession->checkAuthForAggregate(testFooNss, cmdObj));
}
TEST_F(AuthorizationSessionTest, CannotAggregateGraphLookupWithoutFindOnJoinedNamespace) {
authzSession->assumePrivilegesForDB(Privilege(testFooCollResource, {ActionType::find}));
BSONArray pipeline = BSON_ARRAY(BSON("$graphLookup" << BSON("from" << testBarNss.coll())));
BSONObj cmdObj = BSON("aggregate" << testFooNss.coll() << "pipeline" << pipeline);
ASSERT_EQ(ErrorCodes::Unauthorized, authzSession->checkAuthForAggregate(testFooNss, cmdObj));
}
TEST_F(AuthorizationSessionTest, CanAggregateGraphLookupWithFindOnJoinedNamespace) {
authzSession->assumePrivilegesForDB({Privilege(testFooCollResource, {ActionType::find}),
Privilege(testBarCollResource, {ActionType::find})});
BSONArray pipeline = BSON_ARRAY(BSON("$graphLookup" << BSON("from" << testBarNss.coll())));
BSONObj cmdObj = BSON("aggregate" << testFooNss.coll() << "pipeline" << pipeline);
ASSERT_OK(authzSession->checkAuthForAggregate(testFooNss, cmdObj));
}
TEST_F(AuthorizationSessionTest,
CannotAggregateFacetWithLookupAndGraphLookupWithoutFindOnJoinedNamespaces) {
// We only have find on the aggregation namespace.
authzSession->assumePrivilegesForDB(Privilege(testFooCollResource, {ActionType::find}));
BSONArray pipeline =
BSON_ARRAY(fromjson("{$facet: {lookup: [{$lookup: {from: 'bar'}}], graphLookup: "
"[{$graphLookup: {from: 'qux'}}]}}"));
BSONObj cmdObj = BSON("aggregate" << testFooNss.coll() << "pipeline" << pipeline);
ASSERT_EQ(ErrorCodes::Unauthorized, authzSession->checkAuthForAggregate(testFooNss, cmdObj));
// We have find on the $lookup namespace but not on the $graphLookup namespace.
authzSession->assumePrivilegesForDB({Privilege(testFooCollResource, {ActionType::find}),
Privilege(testBarCollResource, {ActionType::find})});
ASSERT_EQ(ErrorCodes::Unauthorized, authzSession->checkAuthForAggregate(testFooNss, cmdObj));
// We have find on the $graphLookup namespace but not on the $lookup namespace.
authzSession->assumePrivilegesForDB({Privilege(testFooCollResource, {ActionType::find}),
Privilege(testQuxCollResource, {ActionType::find})});
ASSERT_EQ(ErrorCodes::Unauthorized, authzSession->checkAuthForAggregate(testFooNss, cmdObj));
}
TEST_F(AuthorizationSessionTest,
CanAggregateFacetWithLookupAndGraphLookupWithFindOnJoinedNamespaces) {
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: "
"[{$graphLookup: {from: 'qux'}}]}}"));
BSONObj cmdObj = BSON("aggregate" << testFooNss.coll() << "pipeline" << pipeline);
ASSERT_OK(authzSession->checkAuthForAggregate(testFooNss, cmdObj));
}
} // namespace
} // namespace mongo