diff options
Diffstat (limited to 'src')
33 files changed, 242 insertions, 205 deletions
diff --git a/src/mongo/db/auth/authorization_manager.h b/src/mongo/db/auth/authorization_manager.h index 69077aebe6a..0f4f89e24dc 100644 --- a/src/mongo/db/auth/authorization_manager.h +++ b/src/mongo/db/auth/authorization_manager.h @@ -305,14 +305,14 @@ public: std::vector<BSONObj>* result) = 0; /** - * Returns a Status or UserHandle for the given userName. If the user cache already has a + * Returns a Status or UserHandle for the given userRequest. If the user cache already has a * user object for this user, it returns a handle from the cache, otherwise it reads the - * user document from disk or LDAP - this may block for a long time. + * user document from the AuthzManagerExternalState - this may block for a long time. * * The returned user may be invalid by the time the caller gets access to it. */ virtual StatusWith<UserHandle> acquireUser(OperationContext* opCtx, - const UserName& userName) = 0; + const UserRequest& userRequest) = 0; /** * Validate the ID associated with a known user while refreshing session cache. diff --git a/src/mongo/db/auth/authorization_manager_impl.cpp b/src/mongo/db/auth/authorization_manager_impl.cpp index db1081b8bf3..9a50ec921ab 100644 --- a/src/mongo/db/auth/authorization_manager_impl.cpp +++ b/src/mongo/db/auth/authorization_manager_impl.cpp @@ -68,7 +68,8 @@ namespace mongo { namespace { std::shared_ptr<UserHandle> createSystemUserHandle() { - auto user = std::make_shared<UserHandle>(User(UserName("__system", "local"))); + UserRequest request(UserName("__system", "local"), boost::none); + auto user = std::make_shared<UserHandle>(User(std::move(request))); ActionSet allActions; allActions.addAllActions(); @@ -284,29 +285,6 @@ bool appliesToAuthzData(StringData op, const NamespaceString& nss, const BSONObj } } -/** - * Returns true if roles for this user were provided by the client, and can be obtained from - * the connection. - */ -bool shouldUseRolesFromConnection(OperationContext* opCtx, const UserName& userName) { -#ifdef MONGO_CONFIG_SSL - if (!opCtx || !opCtx->getClient() || !opCtx->getClient()->session()) { - return false; - } - - if (!allowRolesFromX509Certificates) { - return false; - } - - auto& sslPeerInfo = SSLPeerInfo::forSession(opCtx->getClient()->session()); - return sslPeerInfo.subjectName.toString() == userName.getUser() && - userName.getDB() == "$external"_sd && !sslPeerInfo.roles.empty(); -#else - return false; -#endif -} - - std::unique_ptr<AuthorizationManager> authorizationManagerCreateImpl( ServiceContext* serviceContext) { return std::make_unique<AuthorizationManagerImpl>(serviceContext, @@ -494,29 +472,17 @@ MONGO_FAIL_POINT_DEFINE(authUserCacheSleep); } // namespace StatusWith<UserHandle> AuthorizationManagerImpl::acquireUser(OperationContext* opCtx, - const UserName& userName) try { + const UserRequest& request) try { + const auto& userName = request.name; + auto systemUser = internalSecurity.getUser(); if (userName == (*systemUser)->getName()) { + uassert(ErrorCodes::OperationFailed, + "Attempted to acquire system user with predefined roles", + request.roles == boost::none); return *systemUser; } - UserRequest request(userName, boost::none); - -#ifdef MONGO_CONFIG_SSL - // Clients connected via TLS may present an X.509 certificate which contains an authorization - // grant. If this is the case, the roles must be provided to the external state, for expansion - // into privileges. - if (shouldUseRolesFromConnection(opCtx, userName)) { - auto& sslPeerInfo = SSLPeerInfo::forSession(opCtx->getClient()->session()); - request.roles = std::set<RoleName>(); - - // In order to be hashable, the role names must be converted from unordered_set to a set. - std::copy(sslPeerInfo.roles.begin(), - sslPeerInfo.roles.end(), - std::inserter(*request.roles, request.roles->begin())); - } -#endif - if (authUserCacheBypass.shouldFail()) { // Bypass cache and force a fresh load of the user. auto loadedUser = uassertStatusOK(_externalState->getUserObject(opCtx, request)); @@ -559,7 +525,7 @@ StatusWith<UserHandle> AuthorizationManagerImpl::reacquireUser(OperationContext* // Make a good faith effort to acquire an up-to-date user object, since the one // we've cached is marked "out-of-date." - auto swUserHandle = acquireUser(opCtx, userName); + auto swUserHandle = acquireUser(opCtx, user->getUserRequest()); if (!swUserHandle.isOK()) { return swUserHandle.getStatus(); } @@ -645,6 +611,18 @@ void AuthorizationManagerImpl::_pinnedUsersThreadRoutine() noexcept try { } } + // Find UserRequests for UserNames we need to pin if they exist in the cache. + std::map<UserName, UserRequest> pinNow; + _userCache.peekLatestCachedIf([&](const UserRequest& request, const User& user) { + if (std::any_of(usersToPin.begin(), usersToPin.end(), [&](const auto& userName) { + return user.getName() == userName; + })) { + pinNow.emplace(request.name, request); + } + // Don't need any output vector. + return false; + }); + for (const auto& userName : usersToPin) { if (std::any_of(pinnedUsers.begin(), pinnedUsers.end(), [&](const auto& user) { return user->getName() == userName; @@ -652,7 +630,14 @@ void AuthorizationManagerImpl::_pinnedUsersThreadRoutine() noexcept try { continue; } - auto swUser = acquireUser(opCtx.get(), userName); + auto request = ([&] { + if (auto it = pinNow.find(userName); it != pinNow.end()) { + return it->second; + } + return UserRequest(userName, boost::none); + })(); + + auto swUser = acquireUser(opCtx.get(), request); if (swUser.isOK()) { LOGV2_DEBUG(20232, 2, "Pinned user", "user"_attr = userName); diff --git a/src/mongo/db/auth/authorization_manager_impl.h b/src/mongo/db/auth/authorization_manager_impl.h index fcec7b97e32..17502fd0067 100644 --- a/src/mongo/db/auth/authorization_manager_impl.h +++ b/src/mongo/db/auth/authorization_manager_impl.h @@ -97,7 +97,8 @@ public: bool showBuiltinRoles, std::vector<BSONObj>* result) override; - StatusWith<UserHandle> acquireUser(OperationContext* opCtx, const UserName& userName) override; + StatusWith<UserHandle> acquireUser(OperationContext* opCtx, + const UserRequest& userRequest) override; StatusWith<UserHandle> reacquireUser(OperationContext* opCtx, const UserHandle& user) override; /** diff --git a/src/mongo/db/auth/authorization_manager_test.cpp b/src/mongo/db/auth/authorization_manager_test.cpp index f91f3f0a329..3363aead426 100644 --- a/src/mongo/db/auth/authorization_manager_test.cpp +++ b/src/mongo/db/auth/authorization_manager_test.cpp @@ -140,7 +140,7 @@ TEST_F(AuthorizationManagerTest, testAcquireV2User) { << "admin"))), BSONObj())); - auto swu = authzManager->acquireUser(opCtx.get(), UserName("v2read", "test")); + auto swu = authzManager->acquireUser(opCtx.get(), {{"v2read", "test"}, boost::none}); ASSERT_OK(swu.getStatus()); auto v2read = std::move(swu.getValue()); ASSERT_EQUALS(UserName("v2read", "test"), v2read->getName()); @@ -153,7 +153,7 @@ TEST_F(AuthorizationManagerTest, testAcquireV2User) { ASSERT(testDBPrivilege.getActions().contains(ActionType::find)); // Make sure user's refCount is 0 at the end of the test to avoid an assertion failure - swu = authzManager->acquireUser(opCtx.get(), UserName("v2cluster", "admin")); + swu = authzManager->acquireUser(opCtx.get(), {{"v2cluster", "admin"}, boost::none}); ASSERT_OK(swu.getStatus()); auto v2cluster = std::move(swu.getValue()); ASSERT_EQUALS(UserName("v2cluster", "admin"), v2cluster->getName()); @@ -169,24 +169,19 @@ TEST_F(AuthorizationManagerTest, testAcquireV2User) { #ifdef MONGO_CONFIG_SSL TEST_F(AuthorizationManagerTest, testLocalX509Authorization) { - setX509PeerInfo(session, - SSLPeerInfo(buildX509Name(), - boost::none, - {RoleName("read", "test"), RoleName("readWrite", "test")})); + std::set<RoleName> roles({{"read", "test"}, {"readWrite", "test"}}); + UserRequest request(UserName("CN=mongodb.com", "$external"), roles); - auto swu = authzManager->acquireUser(opCtx.get(), UserName("CN=mongodb.com", "$external")); + auto swu = authzManager->acquireUser(opCtx.get(), request); ASSERT_OK(swu.getStatus()); auto x509User = std::move(swu.getValue()); ASSERT(x509User.isValid()); - stdx::unordered_set<RoleName> expectedRoles{RoleName("read", "test"), - RoleName("readWrite", "test")}; - RoleNameIterator roles = x509User->getRoles(); - stdx::unordered_set<RoleName> acquiredRoles; - while (roles.more()) { - acquiredRoles.insert(roles.next()); + std::set<RoleName> gotRoles; + for (auto it = x509User->getRoles(); it.more();) { + gotRoles.emplace(it.next()); } - ASSERT_TRUE(expectedRoles == acquiredRoles); + ASSERT_TRUE(roles == gotRoles); const User::ResourcePrivilegeMap& privileges = x509User->getPrivileges(); ASSERT_FALSE(privileges.empty()); @@ -202,14 +197,16 @@ TEST_F(AuthorizationManagerTest, testLocalX509AuthorizationInvalidUser) { {RoleName("read", "test"), RoleName("write", "test")})); ASSERT_NOT_OK( - authzManager->acquireUser(opCtx.get(), UserName("CN=10gen.com", "$external")).getStatus()); + authzManager->acquireUser(opCtx.get(), {{"CN=10gen.com", "$external"}, boost::none}) + .getStatus()); } TEST_F(AuthorizationManagerTest, testLocalX509AuthenticationNoAuthorization) { setX509PeerInfo(session, {}); - ASSERT_NOT_OK(authzManager->acquireUser(opCtx.get(), UserName("CN=mongodb.com", "$external")) - .getStatus()); + ASSERT_NOT_OK( + authzManager->acquireUser(opCtx.get(), {{"CN=mongodb.com", "$external"}, boost::none}) + .getStatus()); } #endif @@ -240,7 +237,7 @@ TEST_F(AuthorizationManagerTest, testAcquireV2UserWithUnrecognizedActions) { << "insert")))), BSONObj())); - auto swu = authzManager->acquireUser(opCtx.get(), UserName("myUser", "test")); + auto swu = authzManager->acquireUser(opCtx.get(), {{"myUser", "test"}, boost::none}); ASSERT_OK(swu.getStatus()); auto myUser = std::move(swu.getValue()); ASSERT_EQUALS(UserName("myUser", "test"), myUser->getName()); @@ -317,14 +314,12 @@ TEST_F(AuthorizationManagerTest, testRefreshExternalV2User) { std::vector<UserHandle> checkedOutUsers; checkedOutUsers.reserve(userDocs.size()); for (const auto& userDoc : userDocs) { - auto swUser = authzManager->acquireUser( - opCtx.get(), - UserName(userDoc.getStringField(kUserFieldName), userDoc.getStringField(kDbFieldName))); + const UserName userName(userDoc.getStringField(kUserFieldName), + userDoc.getStringField(kDbFieldName)); + auto swUser = authzManager->acquireUser(opCtx.get(), {userName, boost::none}); ASSERT_OK(swUser.getStatus()); auto user = std::move(swUser.getValue()); - ASSERT_EQUALS( - UserName(userDoc.getStringField(kUserFieldName), userDoc.getStringField(kDbFieldName)), - user->getName()); + ASSERT_EQUALS(userName, user->getName()); ASSERT(user.isValid()); RoleNameIterator cachedUserRoles = user->getRoles(); @@ -364,14 +359,12 @@ TEST_F(AuthorizationManagerTest, testRefreshExternalV2User) { // Retrieve all users from the cache and verify that only the external ones contain the newly // added role. for (const auto& userDoc : userDocs) { - auto swUser = authzManager->acquireUser( - opCtx.get(), - UserName(userDoc.getStringField(kUserFieldName), userDoc.getStringField(kDbFieldName))); + const UserName userName(userDoc.getStringField(kUserFieldName), + userDoc.getStringField(kDbFieldName)); + auto swUser = authzManager->acquireUser(opCtx.get(), {userName, boost::none}); ASSERT_OK(swUser.getStatus()); auto user = std::move(swUser.getValue()); - ASSERT_EQUALS( - UserName(userDoc.getStringField(kUserFieldName), userDoc.getStringField(kDbFieldName)), - user->getName()); + ASSERT_EQUALS(userName, user->getName()); ASSERT(user.isValid()); RoleNameIterator cachedUserRolesIt = user->getRoles(); diff --git a/src/mongo/db/auth/authorization_session.h b/src/mongo/db/auth/authorization_session.h index 777da1ad56d..6bca9cd0192 100644 --- a/src/mongo/db/auth/authorization_session.h +++ b/src/mongo/db/auth/authorization_session.h @@ -144,7 +144,7 @@ public: * Adds the User identified by "UserName" to the authorization session, acquiring privileges * for it in the process. */ - virtual Status addAndAuthorizeUser(OperationContext* opCtx, const UserName& userName) = 0; + virtual Status addAndAuthorizeUser(OperationContext* opCtx, const UserRequest& userRequest) = 0; // Returns the authenticated user with the given name. Returns NULL // if no such user is found. diff --git a/src/mongo/db/auth/authorization_session_for_test.cpp b/src/mongo/db/auth/authorization_session_for_test.cpp index 620716c5e4f..e88dd65ebc4 100644 --- a/src/mongo/db/auth/authorization_session_for_test.cpp +++ b/src/mongo/db/auth/authorization_session_for_test.cpp @@ -48,7 +48,8 @@ void AuthorizationSessionForTest::assumePrivilegesForDB(Privilege privilege, Str void AuthorizationSessionForTest::assumePrivilegesForDB(PrivilegeVector privileges, StringData dbName) { - _authenticatedUser = UserHandle(User(UserName("authorizationSessionForTestUser", dbName))); + UserRequest request(UserName("authorizationSessionForTestUser"_sd, dbName), boost::none); + _authenticatedUser = UserHandle(User(request)); _authenticatedUser.value()->addPrivileges(privileges); _authenticationMode = AuthorizationSession::AuthenticationMode::kConnection; _updateInternalAuthorizationState(); diff --git a/src/mongo/db/auth/authorization_session_impl.cpp b/src/mongo/db/auth/authorization_session_impl.cpp index 90083b297ba..85b8bcc0c6e 100644 --- a/src/mongo/db/auth/authorization_session_impl.cpp +++ b/src/mongo/db/auth/authorization_session_impl.cpp @@ -200,7 +200,9 @@ void AuthorizationSessionImpl::startContractTracking() { } Status AuthorizationSessionImpl::addAndAuthorizeUser(OperationContext* opCtx, - const UserName& userName) try { + const UserRequest& userRequest) try { + const auto& userName = userRequest.name; + // Check before we start to reveal as little as possible. Note that we do not need the lock // because only the Client thread can mutate _authenticatedUser. if (_authenticatedUser) { @@ -232,7 +234,7 @@ Status AuthorizationSessionImpl::addAndAuthorizeUser(OperationContext* opCtx, } AuthorizationManager* authzManager = AuthorizationManager::get(opCtx->getServiceContext()); - auto user = uassertStatusOK(authzManager->acquireUser(opCtx, userName)); + auto user = uassertStatusOK(authzManager->acquireUser(opCtx, userRequest)); auto restrictionStatus = user->validateRestrictions(opCtx); if (!restrictionStatus.isOK()) { diff --git a/src/mongo/db/auth/authorization_session_impl.h b/src/mongo/db/auth/authorization_session_impl.h index 94a13c59249..e9e19c63bb7 100644 --- a/src/mongo/db/auth/authorization_session_impl.h +++ b/src/mongo/db/auth/authorization_session_impl.h @@ -76,7 +76,7 @@ public: void startContractTracking() override; - Status addAndAuthorizeUser(OperationContext* opCtx, const UserName& userName) override; + Status addAndAuthorizeUser(OperationContext* opCtx, const UserRequest& userRequest) override; User* lookupUser(const UserName& name) override; diff --git a/src/mongo/db/auth/authorization_session_test.cpp b/src/mongo/db/auth/authorization_session_test.cpp index 1336554897a..b1a980f5396 100644 --- a/src/mongo/db/auth/authorization_session_test.cpp +++ b/src/mongo/db/auth/authorization_session_test.cpp @@ -172,37 +172,53 @@ const ResourcePattern otherProfileCollResource( const ResourcePattern thirdProfileCollResource( ResourcePattern::forExactNamespace(NamespaceString("third.system.profile"))); +const UserName kUser1Test("user1"_sd, "test"_sd); +const UserRequest kUser1TestRequest(kUser1Test, boost::none); +const UserName kUser2Test("user2"_sd, "test"_sd); +const UserRequest kUser2TestRequest(kUser2Test, boost::none); + TEST_F(AuthorizationSessionTest, MultiAuthSameUserAllowed) { authzSession->startContractTracking(); - ASSERT_OK(createUser({"user1", "test"}, {})); - ASSERT_OK(authzSession->addAndAuthorizeUser(_opCtx.get(), {"user1", "test"})); - ASSERT_OK(authzSession->addAndAuthorizeUser(_opCtx.get(), {"user1", "test"})); + ASSERT_OK(createUser(kUser1Test, {})); + ASSERT_OK(authzSession->addAndAuthorizeUser(_opCtx.get(), kUser1TestRequest)); + ASSERT_OK(authzSession->addAndAuthorizeUser(_opCtx.get(), kUser1TestRequest)); authzSession->logoutAllDatabases(_client.get(), "Test finished"); } TEST_F(AuthorizationSessionTest, MultiAuthSameDBDisallowed) { authzSession->startContractTracking(); - ASSERT_OK(createUser({"user1", "test"}, {})); - ASSERT_OK(createUser({"user2", "test"}, {})); + ASSERT_OK(createUser(kUser1Test, {})); + ASSERT_OK(createUser(kUser2Test, {})); - ASSERT_OK(authzSession->addAndAuthorizeUser(_opCtx.get(), {"user1", "test"})); - ASSERT_NOT_OK(authzSession->addAndAuthorizeUser(_opCtx.get(), {"user2", "test"})); + ASSERT_OK(authzSession->addAndAuthorizeUser(_opCtx.get(), kUser1TestRequest)); + ASSERT_NOT_OK(authzSession->addAndAuthorizeUser(_opCtx.get(), kUser2TestRequest)); authzSession->logoutAllDatabases(_client.get(), "Test finished"); } +const UserName kUserTest1("user"_sd, "test1"_sd); +const UserRequest kUserTest1Request(kUserTest1, boost::none); +const UserName kUserTest2("user"_sd, "test2"_sd); +const UserRequest kUserTest2Request(kUserTest2, boost::none); + TEST_F(AuthorizationSessionTest, MultiAuthMultiDBDisallowed) { authzSession->startContractTracking(); - ASSERT_OK(createUser({"user", "test1"}, {})); - ASSERT_OK(createUser({"user", "test2"}, {})); + ASSERT_OK(createUser(kUserTest1, {})); + ASSERT_OK(createUser(kUserTest2, {})); - ASSERT_OK(authzSession->addAndAuthorizeUser(_opCtx.get(), {"user", "test1"})); - ASSERT_NOT_OK(authzSession->addAndAuthorizeUser(_opCtx.get(), {"user", "test2"})); + ASSERT_OK(authzSession->addAndAuthorizeUser(_opCtx.get(), kUserTest1Request)); + ASSERT_NOT_OK(authzSession->addAndAuthorizeUser(_opCtx.get(), kUserTest2Request)); authzSession->logoutAllDatabases(_client.get(), "Test finished"); } +const UserName kSpencerTest("spencer"_sd, "test"_sd); +const UserRequest kSpencerTestRequest(kSpencerTest, boost::none); + +const UserName kAdminAdmin("admin"_sd, "admin"_sd); +const UserRequest kAdminAdminRequest(kAdminAdmin, boost::none); + TEST_F(AuthorizationSessionTest, AddUserAndCheckAuthorization) { authzSession->startContractTracking(); @@ -218,11 +234,11 @@ TEST_F(AuthorizationSessionTest, AddUserAndCheckAuthorization) { // Check that you can't authorize a user that doesn't exist. ASSERT_EQUALS(ErrorCodes::UserNotFound, - authzSession->addAndAuthorizeUser(_opCtx.get(), UserName("spencer", "test"))); + authzSession->addAndAuthorizeUser(_opCtx.get(), kSpencerTestRequest)); // Add a user with readWrite and dbAdmin on the test DB - ASSERT_OK(createUser({"spencer", "test"}, {{"readWrite", "test"}, {"dbAdmin", "test"}})); - ASSERT_OK(authzSession->addAndAuthorizeUser(_opCtx.get(), UserName("spencer", "test"))); + ASSERT_OK(createUser(kSpencerTest, {{"readWrite", "test"}, {"dbAdmin", "test"}})); + ASSERT_OK(authzSession->addAndAuthorizeUser(_opCtx.get(), kSpencerTestRequest)); ASSERT_TRUE( authzSession->isAuthorizedForActionsOnResource(testFooCollResource, ActionType::insert)); @@ -233,8 +249,8 @@ TEST_F(AuthorizationSessionTest, AddUserAndCheckAuthorization) { authzSession->logoutDatabase(_client.get(), "test", "Kill the test!"); // Add an admin user with readWriteAnyDatabase - ASSERT_OK(createUser({"admin", "admin"}, {{"readWriteAnyDatabase", "admin"}})); - ASSERT_OK(authzSession->addAndAuthorizeUser(_opCtx.get(), UserName("admin", "admin"))); + ASSERT_OK(createUser(kAdminAdmin, {{"readWriteAnyDatabase", "admin"}})); + ASSERT_OK(authzSession->addAndAuthorizeUser(_opCtx.get(), kAdminAdminRequest)); ASSERT_TRUE(authzSession->isAuthorizedForActionsOnResource( ResourcePattern::forExactNamespace(NamespaceString("anydb.somecollection")), @@ -286,9 +302,9 @@ TEST_F(AuthorizationSessionTest, AddUserAndCheckAuthorization) { TEST_F(AuthorizationSessionTest, DuplicateRolesOK) { // Add a user with doubled-up readWrite and single dbAdmin on the test DB - ASSERT_OK(createUser({"spencer", "test"}, + ASSERT_OK(createUser(kSpencerTest, {{"readWrite", "test"}, {"dbAdmin", "test"}, {"readWrite", "test"}})); - ASSERT_OK(authzSession->addAndAuthorizeUser(_opCtx.get(), UserName("spencer", "test"))); + ASSERT_OK(authzSession->addAndAuthorizeUser(_opCtx.get(), kSpencerTestRequest)); ASSERT_TRUE( authzSession->isAuthorizedForActionsOnResource(testFooCollResource, ActionType::insert)); @@ -299,14 +315,24 @@ TEST_F(AuthorizationSessionTest, DuplicateRolesOK) { authzSession->logoutDatabase(_client.get(), "test", "Kill the test!"); } +const UserName kRWTest("rw"_sd, "test"_sd); +const UserName kUserAdminTest("useradmin"_sd, "test"_sd); +const UserName kRWAnyTest("rwany"_sd, "test"_sd); +const UserName kUserAdminAnyTest("useradminany"_sd, "test"_sd); + +const UserRequest kRWTestRequest(kRWTest, boost::none); +const UserRequest kUserAdminTestRequest(kUserAdminTest, boost::none); +const UserRequest kRWAnyTestRequest(kRWAnyTest, boost::none); +const UserRequest kUserAdminAnyTestRequest(kUserAdminAnyTest, boost::none); + TEST_F(AuthorizationSessionTest, SystemCollectionsAccessControl) { - ASSERT_OK(createUser({"rw", "test"}, {{"readWrite", "test"}, {"dbAdmin", "test"}})); - ASSERT_OK(createUser({"useradmin", "test"}, {{"userAdmin", "test"}})); - ASSERT_OK(createUser({"rwany", "test"}, + ASSERT_OK(createUser(kRWTest, {{"readWrite", "test"}, {"dbAdmin", "test"}})); + ASSERT_OK(createUser(kUserAdminTest, {{"userAdmin", "test"}})); + ASSERT_OK(createUser(kRWAnyTest, {{"readWriteAnyDatabase", "admin"}, {"dbAdminAnyDatabase", "admin"}})); - ASSERT_OK(createUser({"useradminany", "test"}, {{"userAdminAnyDatabase", "admin"}})); + ASSERT_OK(createUser(kUserAdminAnyTest, {{"userAdminAnyDatabase", "admin"}})); - ASSERT_OK(authzSession->addAndAuthorizeUser(_opCtx.get(), UserName("rwany", "test"))); + ASSERT_OK(authzSession->addAndAuthorizeUser(_opCtx.get(), kRWAnyTestRequest)); ASSERT_FALSE( authzSession->isAuthorizedForActionsOnResource(testUsersCollResource, ActionType::insert)); @@ -322,7 +348,7 @@ TEST_F(AuthorizationSessionTest, SystemCollectionsAccessControl) { authzSession->isAuthorizedForActionsOnResource(otherProfileCollResource, ActionType::find)); authzSession->logoutDatabase(_client.get(), "test", "Kill the test!"); - ASSERT_OK(authzSession->addAndAuthorizeUser(_opCtx.get(), UserName("useradminany", "test"))); + ASSERT_OK(authzSession->addAndAuthorizeUser(_opCtx.get(), kUserAdminAnyTestRequest)); ASSERT_FALSE( authzSession->isAuthorizedForActionsOnResource(testUsersCollResource, ActionType::insert)); ASSERT_TRUE( @@ -337,7 +363,7 @@ TEST_F(AuthorizationSessionTest, SystemCollectionsAccessControl) { authzSession->isAuthorizedForActionsOnResource(otherProfileCollResource, ActionType::find)); authzSession->logoutDatabase(_client.get(), "test", "Kill the test!"); - ASSERT_OK(authzSession->addAndAuthorizeUser(_opCtx.get(), UserName("rw", "test"))); + ASSERT_OK(authzSession->addAndAuthorizeUser(_opCtx.get(), kRWTestRequest)); ASSERT_FALSE( authzSession->isAuthorizedForActionsOnResource(testUsersCollResource, ActionType::insert)); @@ -353,7 +379,7 @@ TEST_F(AuthorizationSessionTest, SystemCollectionsAccessControl) { authzSession->isAuthorizedForActionsOnResource(otherProfileCollResource, ActionType::find)); authzSession->logoutDatabase(_client.get(), "test", "Kill the test!"); - ASSERT_OK(authzSession->addAndAuthorizeUser(_opCtx.get(), UserName("useradmin", "test"))); + ASSERT_OK(authzSession->addAndAuthorizeUser(_opCtx.get(), kUserAdminTestRequest)); ASSERT_FALSE( authzSession->isAuthorizedForActionsOnResource(testUsersCollResource, ActionType::insert)); ASSERT_FALSE( @@ -371,15 +397,15 @@ TEST_F(AuthorizationSessionTest, SystemCollectionsAccessControl) { TEST_F(AuthorizationSessionTest, InvalidateUser) { // Add a readWrite user - ASSERT_OK(createUser({"spencer", "test"}, {{"readWrite", "test"}})); - ASSERT_OK(authzSession->addAndAuthorizeUser(_opCtx.get(), UserName("spencer", "test"))); + ASSERT_OK(createUser(kSpencerTest, {{"readWrite", "test"}})); + ASSERT_OK(authzSession->addAndAuthorizeUser(_opCtx.get(), kSpencerTestRequest)); ASSERT_TRUE( authzSession->isAuthorizedForActionsOnResource(testFooCollResource, ActionType::find)); ASSERT_TRUE( authzSession->isAuthorizedForActionsOnResource(testFooCollResource, ActionType::insert)); - User* user = authzSession->lookupUser(UserName("spencer", "test")); + User* user = authzSession->lookupUser(kSpencerTest); // Change the user to be read-only int ignored; @@ -388,7 +414,7 @@ TEST_F(AuthorizationSessionTest, InvalidateUser) { BSONObj(), BSONObj(), &ignored)); - ASSERT_OK(createUser({"spencer", "test"}, {{"read", "test"}})); + ASSERT_OK(createUser(kSpencerTest, {{"read", "test"}})); // Make sure that invalidating the user causes the session to reload its privileges. authzManager->invalidateUserByName(_opCtx.get(), user->getName()); @@ -398,7 +424,7 @@ TEST_F(AuthorizationSessionTest, InvalidateUser) { ASSERT_FALSE( authzSession->isAuthorizedForActionsOnResource(testFooCollResource, ActionType::insert)); - user = authzSession->lookupUser(UserName("spencer", "test")); + user = authzSession->lookupUser(kSpencerTest); // Delete the user. ASSERT_OK(managerState->remove(_opCtx.get(), @@ -413,21 +439,21 @@ TEST_F(AuthorizationSessionTest, InvalidateUser) { authzSession->isAuthorizedForActionsOnResource(testFooCollResource, ActionType::find)); ASSERT_FALSE( authzSession->isAuthorizedForActionsOnResource(testFooCollResource, ActionType::insert)); - ASSERT_FALSE(authzSession->lookupUser(UserName("spencer", "test"))); + ASSERT_FALSE(authzSession->lookupUser(kSpencerTest)); authzSession->logoutDatabase(_client.get(), "test", "Kill the test!"); } TEST_F(AuthorizationSessionTest, UseOldUserInfoInFaceOfConnectivityProblems) { // Add a readWrite user - ASSERT_OK(createUser({"spencer", "test"}, {{"readWrite", "test"}})); - ASSERT_OK(authzSession->addAndAuthorizeUser(_opCtx.get(), UserName("spencer", "test"))); + ASSERT_OK(createUser(kSpencerTest, {{"readWrite", "test"}})); + ASSERT_OK(authzSession->addAndAuthorizeUser(_opCtx.get(), kSpencerTestRequest)); ASSERT_TRUE( authzSession->isAuthorizedForActionsOnResource(testFooCollResource, ActionType::find)); ASSERT_TRUE( authzSession->isAuthorizedForActionsOnResource(testFooCollResource, ActionType::insert)); - User* user = authzSession->lookupUser(UserName("spencer", "test")); + User* user = authzSession->lookupUser(kSpencerTest); // Change the user to be read-only int ignored; @@ -437,7 +463,7 @@ TEST_F(AuthorizationSessionTest, UseOldUserInfoInFaceOfConnectivityProblems) { BSONObj(), BSONObj(), &ignored)); - ASSERT_OK(createUser({"spencer", "test"}, {{"read", "test"}})); + ASSERT_OK(createUser(kSpencerTest, {{"read", "test"}})); // 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 @@ -491,7 +517,7 @@ TEST_F(AuthorizationSessionTest, AcquireUserObtainsAndValidatesAuthenticationRes std::make_unique<RestrictionEnvironment>( SockAddr::create(clientSource, 5555, AF_UNSPEC), SockAddr::create(serverAddress, 27017, AF_UNSPEC))); - ASSERT_OK(authzSession->addAndAuthorizeUser(_opCtx.get(), UserName("spencer", "test"))); + ASSERT_OK(authzSession->addAndAuthorizeUser(_opCtx.get(), kSpencerTestRequest)); authzSession->logoutDatabase(_client.get(), "test", "Kill the test!"); }; @@ -500,11 +526,11 @@ TEST_F(AuthorizationSessionTest, AcquireUserObtainsAndValidatesAuthenticationRes std::make_unique<RestrictionEnvironment>( SockAddr::create(clientSource, 5555, AF_UNSPEC), SockAddr::create(serverAddress, 27017, AF_UNSPEC))); - ASSERT_NOT_OK(authzSession->addAndAuthorizeUser(_opCtx.get(), UserName("spencer", "test"))); + ASSERT_NOT_OK(authzSession->addAndAuthorizeUser(_opCtx.get(), kSpencerTestRequest)); }; // The empty RestrictionEnvironment will cause addAndAuthorizeUser to fail. - ASSERT_NOT_OK(authzSession->addAndAuthorizeUser(_opCtx.get(), UserName("spencer", "test"))); + ASSERT_NOT_OK(authzSession->addAndAuthorizeUser(_opCtx.get(), kSpencerTestRequest)); // A clientSource from the 192.168.0.0/24 block will succeed in connecting to a server // listening on 192.168.0.2. @@ -1059,28 +1085,26 @@ TEST_F(AuthorizationSessionTest, UnauthorizedSessionIsCoauthorizedWithNobody) { } TEST_F(AuthorizationSessionTest, UnauthorizedSessionIsNotCoauthorizedWithAnybody) { - ASSERT_FALSE(authzSession->isCoauthorizedWith(UserName("spencer", "test"))); + ASSERT_FALSE(authzSession->isCoauthorizedWith(kSpencerTest)); } TEST_F(AuthorizationSessionTest, UnauthorizedSessionIsCoauthorizedWithAnybodyWhenAuthIsDisabled) { authzManager->setAuthEnabled(false); - ASSERT_TRUE(authzSession->isCoauthorizedWith(UserName("spencer", "test"))); + ASSERT_TRUE(authzSession->isCoauthorizedWith(kSpencerTest)); } TEST_F(AuthorizationSessionTest, AuthorizedSessionIsNotCoauthorizedNobody) { - UserName user("spencer", "test"); - ASSERT_OK(createUser(user, {})); - ASSERT_OK(authzSession->addAndAuthorizeUser(_opCtx.get(), user)); + ASSERT_OK(createUser(kSpencerTest, {})); + ASSERT_OK(authzSession->addAndAuthorizeUser(_opCtx.get(), kSpencerTestRequest)); ASSERT_FALSE(authzSession->isCoauthorizedWith(boost::none)); authzSession->logoutDatabase(_client.get(), "test", "Kill the test!"); } TEST_F(AuthorizationSessionTest, AuthorizedSessionIsCoauthorizedNobodyWhenAuthIsDisabled) { - UserName user("spencer", "test"); authzManager->setAuthEnabled(false); - ASSERT_OK(createUser(user, {})); - ASSERT_OK(authzSession->addAndAuthorizeUser(_opCtx.get(), user)); - ASSERT_TRUE(authzSession->isCoauthorizedWith(user)); + ASSERT_OK(createUser(kSpencerTest, {})); + ASSERT_OK(authzSession->addAndAuthorizeUser(_opCtx.get(), kSpencerTestRequest)); + ASSERT_TRUE(authzSession->isCoauthorizedWith(kSpencerTest)); authzSession->logoutDatabase(_client.get(), "test", "Kill the test!"); } @@ -1448,6 +1472,9 @@ TEST_F(SystemBucketsTest, CanCheckIfHasAnyPrivilegeInResourceDBForSystemBuckets) ASSERT_TRUE(authzSession->isAuthorizedForAnyActionOnAnyResourceInDB(sb_db_other)); } +const UserName kGMarksAdmin("gmarks"_sd, "admin"_sd); +const UserRequest kGMarksAdminRequest(kGMarksAdmin, boost::none); + TEST_F(AuthorizationSessionTest, MayBypassWriteBlockingModeIsSetCorrectly) { ASSERT_FALSE(authzSession->mayBypassWriteBlockingMode()); @@ -1463,7 +1490,7 @@ TEST_F(AuthorizationSessionTest, MayBypassWriteBlockingModeIsSetCorrectly) { << "db" << "test"))), BSONObj())); - ASSERT_OK(authzSession->addAndAuthorizeUser(_opCtx.get(), UserName("spencer", "test"))); + ASSERT_OK(authzSession->addAndAuthorizeUser(_opCtx.get(), kSpencerTestRequest)); ASSERT_FALSE(authzSession->mayBypassWriteBlockingMode()); // Add a user with restore role on admin db and ensure we can bypass @@ -1480,7 +1507,7 @@ TEST_F(AuthorizationSessionTest, MayBypassWriteBlockingModeIsSetCorrectly) { BSONObj())); authzSession->logoutDatabase(_client.get(), "test", "End of test"); - ASSERT_OK(authzSession->addAndAuthorizeUser(_opCtx.get(), UserName("gmarks", "admin"))); + ASSERT_OK(authzSession->addAndAuthorizeUser(_opCtx.get(), kGMarksAdminRequest)); ASSERT_TRUE(authzSession->mayBypassWriteBlockingMode()); // Remove that user by logging out of the admin db and ensure we can't bypass anymore @@ -1502,7 +1529,7 @@ TEST_F(AuthorizationSessionTest, MayBypassWriteBlockingModeIsSetCorrectly) { BSONObj())); authzSession->logoutDatabase(_client.get(), "admin", ""); - ASSERT_OK(authzSession->addAndAuthorizeUser(_opCtx.get(), UserName("admin", "admin"))); + ASSERT_OK(authzSession->addAndAuthorizeUser(_opCtx.get(), kAdminAdminRequest)); ASSERT_TRUE(authzSession->mayBypassWriteBlockingMode()); // Remove non-privileged user by logging out of test db and ensure we can still bypass diff --git a/src/mongo/db/auth/authz_manager_external_state_local.cpp b/src/mongo/db/auth/authz_manager_external_state_local.cpp index 9f98e5f7032..bf2b0238eac 100644 --- a/src/mongo/db/auth/authz_manager_external_state_local.cpp +++ b/src/mongo/db/auth/authz_manager_external_state_local.cpp @@ -293,7 +293,7 @@ StatusWith<User> AuthzManagerExternalStateLocal::getUserObject(OperationContext* const UserRequest& userReq) try { const UserName& userName = userReq.name; std::vector<RoleName> directRoles; - User user(userReq.name); + User user(userReq); auto rolesLock = _lockRoles(opCtx, userName.getTenant()); diff --git a/src/mongo/db/auth/authz_manager_external_state_s.cpp b/src/mongo/db/auth/authz_manager_external_state_s.cpp index 61705c9c9b9..4f415fd7247 100644 --- a/src/mongo/db/auth/authz_manager_external_state_s.cpp +++ b/src/mongo/db/auth/authz_manager_external_state_s.cpp @@ -118,7 +118,7 @@ StatusWith<User> AuthzManagerExternalStateMongos::getUserObject(OperationContext return status; } - User user(userReq.name); + User user(userReq); V2UserDocumentParser dp; dp.setTenantId(getActiveTenant(opCtx)); status = dp.initializeUserFromUserDocument(userDoc, &user); diff --git a/src/mongo/db/auth/sasl_commands.cpp b/src/mongo/db/auth/sasl_commands.cpp index 5c00f9c23c9..4d5755c5d95 100644 --- a/src/mongo/db/auth/sasl_commands.cpp +++ b/src/mongo/db/auth/sasl_commands.cpp @@ -184,9 +184,9 @@ SaslReply doSaslStep(OperationContext* opCtx, } if (mechanism.isSuccess()) { - UserName userName(mechanism.getPrincipalName(), mechanism.getAuthenticationDatabase()); + auto request = mechanism.getUserRequest(); uassertStatusOK( - AuthorizationSession::get(opCtx->getClient())->addAndAuthorizeUser(opCtx, userName)); + AuthorizationSession::get(opCtx->getClient())->addAndAuthorizeUser(opCtx, request)); if (!serverGlobalParams.quiet.load()) { auto attrs = makeLogAttributes(); diff --git a/src/mongo/db/auth/sasl_mechanism_registry.cpp b/src/mongo/db/auth/sasl_mechanism_registry.cpp index 4ba33279435..e49a1c07185 100644 --- a/src/mongo/db/auth/sasl_mechanism_registry.cpp +++ b/src/mongo/db/auth/sasl_mechanism_registry.cpp @@ -103,7 +103,7 @@ void SASLServerMechanismRegistry::advertiseMechanismNamesForUser(OperationContex AuthorizationManager* authManager = AuthorizationManager::get(opCtx->getServiceContext()); UserHandle user; - const auto swUser = authManager->acquireUser(opCtx, userName); + const auto swUser = authManager->acquireUser(opCtx, UserRequest(userName, boost::none)); if (!swUser.isOK()) { auto& status = swUser.getStatus(); if (status.code() == ErrorCodes::UserNotFound) { diff --git a/src/mongo/db/auth/sasl_mechanism_registry.h b/src/mongo/db/auth/sasl_mechanism_registry.h index 06b0850125b..2fe16292b80 100644 --- a/src/mongo/db/auth/sasl_mechanism_registry.h +++ b/src/mongo/db/auth/sasl_mechanism_registry.h @@ -206,6 +206,10 @@ public: return Status::OK(); } + virtual UserRequest getUserRequest() const { + return UserRequest(UserName(getPrincipalName(), getAuthenticationDatabase()), boost::none); + } + protected: /** * Mechanism provided step implementation. diff --git a/src/mongo/db/auth/sasl_mechanism_registry_test.cpp b/src/mongo/db/auth/sasl_mechanism_registry_test.cpp index 2f39ddea4f0..a7bb4d75440 100644 --- a/src/mongo/db/auth/sasl_mechanism_registry_test.cpp +++ b/src/mongo/db/auth/sasl_mechanism_registry_test.cpp @@ -225,7 +225,8 @@ public: << "roles" << BSONArray()), BSONObj())); - internalSecurity.setUser(std::make_shared<UserHandle>(User(UserName("__system", "local")))); + UserRequest systemLocal(UserName("__system"_sd, "local"_sd), boost::none); + internalSecurity.setUser(std::make_shared<UserHandle>(User(systemLocal))); } BSONObj getMechsFor(const UserName user) { diff --git a/src/mongo/db/auth/sasl_plain_server_conversation.cpp b/src/mongo/db/auth/sasl_plain_server_conversation.cpp index 08401c59835..8ec01b8f0c7 100644 --- a/src/mongo/db/auth/sasl_plain_server_conversation.cpp +++ b/src/mongo/db/auth/sasl_plain_server_conversation.cpp @@ -126,7 +126,8 @@ StatusWith<std::tuple<bool, std::string>> SASLPlainServerMechanism::stepImpl( // The authentication database is also the source database for the user. auto swUser = authManager->acquireUser( - opCtx, UserName(ServerMechanismBase::_principalName, _authenticationDatabase)); + opCtx, + UserRequest({ServerMechanismBase::_principalName, _authenticationDatabase}, boost::none)); if (!swUser.isOK()) { return swUser.getStatus(); diff --git a/src/mongo/db/auth/sasl_scram_server_conversation.cpp b/src/mongo/db/auth/sasl_scram_server_conversation.cpp index 87ff7a789db..3271626aaf7 100644 --- a/src/mongo/db/auth/sasl_scram_server_conversation.cpp +++ b/src/mongo/db/auth/sasl_scram_server_conversation.cpp @@ -203,7 +203,7 @@ StatusWith<std::tuple<bool, std::string>> SaslSCRAMServerMechanism<Policy>::_fir // The authentication database is also the source database for the user. auto authManager = AuthorizationManager::get(opCtx->getServiceContext()); - auto swUser = authManager->acquireUser(opCtx, user); + auto swUser = authManager->acquireUser(opCtx, UserRequest(user, boost::none)); if (!swUser.isOK()) { return swUser.getStatus(); } diff --git a/src/mongo/db/auth/security_key_test.cpp b/src/mongo/db/auth/security_key_test.cpp index e64aded89f6..7a33cfbbad7 100644 --- a/src/mongo/db/auth/security_key_test.cpp +++ b/src/mongo/db/auth/security_key_test.cpp @@ -160,7 +160,8 @@ TEST(SecurityFile, Test) { } TEST(SecurityKey, Test) { - internalSecurity.setUser(std::make_shared<UserHandle>(User(UserName("__system", "local")))); + UserRequest systemLocal(UserName("__system"_sd, "local"_sd), boost::none); + internalSecurity.setUser(std::make_shared<UserHandle>(User(systemLocal))); for (const auto& testCase : testCases) { TestFile file(testCase.fileContents, testCase.mode != TestCase::FailureMode::Permissions); diff --git a/src/mongo/db/auth/security_token_authentication_guard.cpp b/src/mongo/db/auth/security_token_authentication_guard.cpp index 5be6de3dc75..a8f86fa240d 100644 --- a/src/mongo/db/auth/security_token_authentication_guard.cpp +++ b/src/mongo/db/auth/security_token_authentication_guard.cpp @@ -41,9 +41,9 @@ namespace auth { SecurityTokenAuthenticationGuard::SecurityTokenAuthenticationGuard( OperationContext* opCtx, const ValidatedTenancyScope& token) { if (token.hasAuthenticatedUser()) { - const auto& userName = token.authenticatedUser(); + UserRequest request(token.authenticatedUser(), boost::none); auto* client = opCtx->getClient(); - uassertStatusOK(AuthorizationSession::get(client)->addAndAuthorizeUser(opCtx, userName)); + uassertStatusOK(AuthorizationSession::get(client)->addAndAuthorizeUser(opCtx, request)); _client = client; LOGV2_DEBUG(5838100, diff --git a/src/mongo/db/auth/user.cpp b/src/mongo/db/auth/user.cpp index a80e833c9e7..c50de72a546 100644 --- a/src/mongo/db/auth/user.cpp +++ b/src/mongo/db/auth/user.cpp @@ -66,8 +66,8 @@ SHA256Block computeDigest(const UserName& name) { } // namespace -User::User(const UserName& name) - : _name(name), _isInvalidated(false), _digest(computeDigest(_name)) {} +User::User(UserRequest request) + : _request(std::move(request)), _isInvalidated(false), _digest(computeDigest(_request.name)) {} template <> User::SCRAMCredentials<SHA1Block>& User::CredentialData::scram<SHA1Block>() { @@ -201,10 +201,10 @@ void User::reportForUsersInfo(BSONObjBuilder* builder, bool showCredentials, bool showPrivileges, bool showAuthenticationRestrictions) const { - builder->append(kIdFieldName, _name.getUnambiguousName()); + builder->append(kIdFieldName, getName().getUnambiguousName()); UUID::fromCDR(ConstDataRange(_id)).appendToBuilder(builder, kUserIdFieldName); - builder->append(kUserFieldName, _name.getUser()); - builder->append(kDbFieldName, _name.getDB()); + builder->append(kUserFieldName, getName().getUser()); + builder->append(kDbFieldName, getName().getDB()); BSONArrayBuilder mechanismNamesBuilder(builder->subarrayStart(kMechanismsFieldName)); for (const StringData& mechanism : _credentials.toMechanismsVector()) { diff --git a/src/mongo/db/auth/user.h b/src/mongo/db/auth/user.h index 52b9e4e38d9..83dcc398775 100644 --- a/src/mongo/db/auth/user.h +++ b/src/mongo/db/auth/user.h @@ -47,6 +47,36 @@ namespace mongo { /** + * Represents the properties required to request a UserHandle. + * This type is hashable and may be used as a key describing requests + */ +struct UserRequest { + UserRequest(UserName name, boost::optional<std::set<RoleName>> roles) + : name(std::move(name)), roles(std::move(roles)) {} + + + template <typename H> + friend H AbslHashValue(H h, const UserRequest& key) { + auto state = H::combine(std::move(h), key.name); + if (key.roles) { + for (const auto& role : *key.roles) { + state = H::combine(std::move(state), role); + } + } + return state; + } + + bool operator==(const UserRequest& key) const { + return name == key.name && roles == key.roles; + } + + // The name of the requested user + UserName name; + // Any authorization grants which should override and be used in favor of roles acquisition. + boost::optional<std::set<RoleName>> roles; +}; + +/** * Represents a MongoDB user. Stores information about the user necessary for access control * checks and authentications, such as what privileges this user has, as well as what roles * the user belongs to. @@ -158,7 +188,7 @@ public: using ResourcePrivilegeMap = stdx::unordered_map<ResourcePattern, Privilege>; - explicit User(const UserName& name); + explicit User(UserRequest request); User(User&&) = default; User& operator=(User&&) = default; @@ -170,11 +200,15 @@ public: _id = std::move(id); } + const UserRequest& getUserRequest() const { + return _request; + } + /** * Returns the user name for this user. */ const UserName& getName() const { - return _name; + return _request.name; } /** @@ -326,8 +360,8 @@ private: // Unique ID (often UUID) for this user. May be empty for legacy users. UserId _id; - // The full user name (as specified by the administrator) - UserName _name; + // The original UserRequest which resolved into this user + UserRequest _request; // User was explicitly invalidated bool _isInvalidated; @@ -354,36 +388,6 @@ private: RestrictionDocuments _indirectRestrictions; }; -/** - * Represents the properties required to request a UserHandle. - * This type is hashable and may be used as a key describing requests - */ -struct UserRequest { - UserRequest(const UserName& name, boost::optional<std::set<RoleName>> roles) - : name(name), roles(std::move(roles)) {} - - - template <typename H> - friend H AbslHashValue(H h, const UserRequest& key) { - auto state = H::combine(std::move(h), key.name); - if (key.roles) { - for (const auto& role : *key.roles) { - state = H::combine(std::move(state), role); - } - } - return state; - } - - bool operator==(const UserRequest& key) const { - return name == key.name && roles == key.roles; - } - - // The name of the requested user - UserName name; - // Any authorization grants which should override and be used in favor of roles acquisition. - boost::optional<std::set<RoleName>> roles; -}; - using UserCache = ReadThroughCache<UserRequest, User>; using UserHandle = UserCache::ValueHandle; diff --git a/src/mongo/db/auth/user_document_parser_test.cpp b/src/mongo/db/auth/user_document_parser_test.cpp index 00ff394a222..9773927ad50 100644 --- a/src/mongo/db/auth/user_document_parser_test.cpp +++ b/src/mongo/db/auth/user_document_parser_test.cpp @@ -63,8 +63,8 @@ public: BSONObj sha1_creds, sha256_creds; void setUp() { - user.reset(new User(UserName("spencer", "test"))); - adminUser.reset(new User(UserName("admin", "admin"))); + user.reset(new User(UserRequest(UserName("spencer", "test"), boost::none))); + adminUser.reset(new User(UserRequest(UserName("admin", "admin"), boost::none))); sha1_creds = scram::Secrets<SHA1Block>::generateCredentials( "a", saslGlobalParams.scramSHA1IterationCount.load()); diff --git a/src/mongo/db/auth/validated_tenancy_scope_test.cpp b/src/mongo/db/auth/validated_tenancy_scope_test.cpp index 94b35e9626b..66c48653916 100644 --- a/src/mongo/db/auth/validated_tenancy_scope_test.cpp +++ b/src/mongo/db/auth/validated_tenancy_scope_test.cpp @@ -48,7 +48,7 @@ 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 user(UserRequest(UserName("useTenant"_sd, "admin"_sd), boost::none)); user.setPrivileges( {Privilege(ResourcePattern::forClusterResource(), ActionType::useTenant)}); auto* as = dynamic_cast<AuthorizationSessionImpl*>(AuthorizationSession::get(client)); diff --git a/src/mongo/db/commands/SConscript b/src/mongo/db/commands/SConscript index 052dfba1186..143fdd94491 100644 --- a/src/mongo/db/commands/SConscript +++ b/src/mongo/db/commands/SConscript @@ -176,6 +176,7 @@ env.Library( '$BUILD_DIR/mongo/db/auth/auth', '$BUILD_DIR/mongo/db/auth/auth_options', '$BUILD_DIR/mongo/db/auth/authentication_session', + '$BUILD_DIR/mongo/db/auth/authorization_manager_global', '$BUILD_DIR/mongo/db/auth/cluster_auth_mode', '$BUILD_DIR/mongo/db/commands', '$BUILD_DIR/mongo/db/server_base', diff --git a/src/mongo/db/commands/authentication_commands.cpp b/src/mongo/db/commands/authentication_commands.cpp index 7c2c5d231a9..8feb2d6320c 100644 --- a/src/mongo/db/commands/authentication_commands.cpp +++ b/src/mongo/db/commands/authentication_commands.cpp @@ -44,6 +44,7 @@ #include "mongo/config.h" #include "mongo/db/auth/auth_options_gen.h" #include "mongo/db/auth/authentication_session.h" +#include "mongo/db/auth/authorization_manager_global_parameters_gen.h" #include "mongo/db/auth/authorization_session.h" #include "mongo/db/auth/cluster_auth_mode.h" #include "mongo/db/auth/privilege.h" @@ -242,7 +243,7 @@ void _authenticateX509(OperationContext* opCtx, AuthenticationSession* session) "No verified subject name available from client", !clientName.empty()); - auto user = [&] { + UserName userName = ([&] { if (session->getUserName().empty()) { auto user = UserName(clientName.toString(), session->getDatabase().toString()); session->updateUserName(user); @@ -253,7 +254,7 @@ void _authenticateX509(OperationContext* opCtx, AuthenticationSession* session) session->getUserName() == clientName.toString()); return UserName(session->getUserName().toString(), session->getDatabase().toString()); } - }(); + })(); uassert(ErrorCodes::ProtocolError, "SSL support is required for the MONGODB-X509 mechanism.", @@ -269,7 +270,7 @@ void _authenticateX509(OperationContext* opCtx, AuthenticationSession* session) uassert(ErrorCodes::ProtocolError, "X.509 authentication must always use the $external database.", - user.getDB() == kExternalDB); + userName.getDB() == kExternalDB); auto isInternalClient = [&]() -> bool { return opCtx->getClient()->session()->getTags() & transport::Session::kInternalClient; @@ -277,11 +278,27 @@ void _authenticateX509(OperationContext* opCtx, AuthenticationSession* session) const auto clusterAuthMode = ClusterAuthMode::get(opCtx->getServiceContext()); + boost::optional<std::set<RoleName>> roles; + if (allowRolesFromX509Certificates) { + auto& sslPeerInfo = SSLPeerInfo::forSession(opCtx->getClient()->session()); + if (!sslPeerInfo.roles.empty() && + (sslPeerInfo.subjectName.toString() == userName.getUser())) { + roles = std::set<RoleName>(); + + // In order to be hashable, the role names must be converted from unordered_set to a + // set. + std::copy(sslPeerInfo.roles.begin(), + sslPeerInfo.roles.end(), + std::inserter(*roles, roles->begin())); + } + } + UserRequest request(std::move(userName), std::move(roles)); + auto authorizeExternalUser = [&] { uassert(ErrorCodes::BadValue, kX509AuthenticationDisabledMessage, !isX509AuthDisabled(opCtx->getServiceContext())); - uassertStatusOK(authorizationSession->addAndAuthorizeUser(opCtx, user)); + uassertStatusOK(authorizationSession->addAndAuthorizeUser(opCtx, request)); }; if (sslConfiguration->isClusterMember(clientName)) { diff --git a/src/mongo/db/commands/user_management_commands.cpp b/src/mongo/db/commands/user_management_commands.cpp index 328b214ae5e..409a2883fd3 100644 --- a/src/mongo/db/commands/user_management_commands.cpp +++ b/src/mongo/db/commands/user_management_commands.cpp @@ -139,7 +139,7 @@ Status getCurrentUserRoles(OperationContext* opCtx, AuthorizationManager* authzManager, const UserName& userName, stdx::unordered_set<RoleName>* roles) { - auto swUser = authzManager->acquireUser(opCtx, userName); + auto swUser = authzManager->acquireUser(opCtx, UserRequest(userName, boost::none)); if (!swUser.isOK()) { return swUser.getStatus(); } @@ -1385,7 +1385,8 @@ UsersInfoReply CmdUMCTyped<UsersInfoCommand, UMCInfoParams>::Invocation::typedRu } else { // Custom data is not required in the output, so it can be generated from a cached // user object. - auto swUserHandle = authzManager->acquireUser(opCtx, userName); + auto swUserHandle = + authzManager->acquireUser(opCtx, UserRequest(userName, boost::none)); if (swUserHandle.getStatus().code() == ErrorCodes::UserNotFound) { continue; } diff --git a/src/mongo/db/session/SConscript b/src/mongo/db/session/SConscript index 9862c8a660b..a0021e3ac81 100644 --- a/src/mongo/db/session/SConscript +++ b/src/mongo/db/session/SConscript @@ -155,6 +155,7 @@ env.Library( '$BUILD_DIR/mongo/db/api_parameters', '$BUILD_DIR/mongo/db/auth/auth', '$BUILD_DIR/mongo/db/auth/authprivilege', + '$BUILD_DIR/mongo/db/auth/user', '$BUILD_DIR/mongo/idl/idl_parser', '$BUILD_DIR/mongo/rpc/client_metadata', 'logical_session_id_helpers', diff --git a/src/mongo/db/session/kill_sessions.cpp b/src/mongo/db/session/kill_sessions.cpp index 7626afa111b..99d8fd5122a 100644 --- a/src/mongo/db/session/kill_sessions.cpp +++ b/src/mongo/db/session/kill_sessions.cpp @@ -109,12 +109,8 @@ KillAllSessionsByPatternItem makeKillAllSessionsByPattern(OperationContext* opCt const KillAllSessionsUser& kasu) { KillAllSessionsByPatternItem item = makeKillAllSessionsByPattern(opCtx); - auto authMgr = AuthorizationManager::get(opCtx->getServiceContext()); - - UserName un(kasu.getUser(), kasu.getDb()); - - auto user = uassertStatusOK(authMgr->acquireUser(opCtx, un)); - item.pattern.setUid(user->getDigest()); + User user(UserRequest(UserName(kasu.getUser(), kasu.getDb()), boost::none)); + item.pattern.setUid(user.getDigest()); return item; } diff --git a/src/mongo/db/session/logical_session_id_test.cpp b/src/mongo/db/session/logical_session_id_test.cpp index 736525ecf0f..1142e4335ef 100644 --- a/src/mongo/db/session/logical_session_id_test.cpp +++ b/src/mongo/db/session/logical_session_id_test.cpp @@ -111,7 +111,7 @@ public: << "db" << "test"))), BSONObj())); - ASSERT_OK(authzSession->addAndAuthorizeUser(_opCtx.get(), un)); + ASSERT_OK(authzSession->addAndAuthorizeUser(_opCtx.get(), {un, boost::none})); return authzSession->lookupUser(un); } @@ -126,7 +126,7 @@ public: << "db" << "admin"))), BSONObj())); - ASSERT_OK(authzSession->addAndAuthorizeUser(_opCtx.get(), un)); + ASSERT_OK(authzSession->addAndAuthorizeUser(_opCtx.get(), {un, boost::none})); return authzSession->lookupUser(un); } }; diff --git a/src/mongo/embedded/embedded_auth_manager.cpp b/src/mongo/embedded/embedded_auth_manager.cpp index ec2c7dc9184..255fc535210 100644 --- a/src/mongo/embedded/embedded_auth_manager.cpp +++ b/src/mongo/embedded/embedded_auth_manager.cpp @@ -108,7 +108,7 @@ public: UASSERT_NOT_IMPLEMENTED; } - StatusWith<UserHandle> acquireUser(OperationContext*, const UserName&) override { + StatusWith<UserHandle> acquireUser(OperationContext*, const UserRequest&) override { UASSERT_NOT_IMPLEMENTED; } diff --git a/src/mongo/embedded/embedded_auth_session.cpp b/src/mongo/embedded/embedded_auth_session.cpp index d086a0a3309..cae0689cb44 100644 --- a/src/mongo/embedded/embedded_auth_session.cpp +++ b/src/mongo/embedded/embedded_auth_session.cpp @@ -70,7 +70,7 @@ public: void startContractTracking() override {} - Status addAndAuthorizeUser(OperationContext*, const UserName&) override { + Status addAndAuthorizeUser(OperationContext*, const UserRequest&) override { UASSERT_NOT_IMPLEMENTED; } diff --git a/src/mongo/rpc/op_msg_test.cpp b/src/mongo/rpc/op_msg_test.cpp index d5287b18144..df6aa2e04a3 100644 --- a/src/mongo/rpc/op_msg_test.cpp +++ b/src/mongo/rpc/op_msg_test.cpp @@ -61,7 +61,7 @@ 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 user(UserRequest(UserName("useTenant", "admin"), boost::none)); user.setPrivileges( {Privilege(ResourcePattern::forClusterResource(), ActionType::useTenant)}); auto* as = dynamic_cast<AuthorizationSessionImpl*>(AuthorizationSession::get(client)); diff --git a/src/mongo/util/net/network_interface_ssl_test.cpp b/src/mongo/util/net/network_interface_ssl_test.cpp index fadbb694257..cc4fae14287 100644 --- a/src/mongo/util/net/network_interface_ssl_test.cpp +++ b/src/mongo/util/net/network_interface_ssl_test.cpp @@ -60,7 +60,8 @@ public: NetworkInterfaceIntegrationFixture::setUp(); // Setup an internal user so that we can use it for external auth - auto user = std::make_shared<UserHandle>(User(UserName("__system", "local"))); + UserRequest systemLocal(UserName("__system"_sd, "local"_sd), boost::none); + auto user = std::make_shared<UserHandle>(User(systemLocal)); internalSecurity.setUser(user); |