diff options
author | Sara Golemon <sara.golemon@mongodb.com> | 2022-05-10 17:54:01 +0000 |
---|---|---|
committer | Evergreen Agent <no-reply@evergreen.mongodb.com> | 2022-05-24 05:17:25 +0000 |
commit | 4fab61e9c5006e9a4c06860dc9e49e1d422ee859 (patch) | |
tree | c383da8e16d40422c3dff8dea6a38c6e2935e256 | |
parent | d3dae653da44b8cb87ff2a9687c0468aa52b6b44 (diff) | |
download | mongo-4fab61e9c5006e9a4c06860dc9e49e1d422ee859.tar.gz |
SERVER-66360 Remove multi-user authentication support
55 files changed, 539 insertions, 994 deletions
diff --git a/buildscripts/idl/idl_check_compatibility.py b/buildscripts/idl/idl_check_compatibility.py index 1a30f29428a..82428ef7e3b 100644 --- a/buildscripts/idl/idl_check_compatibility.py +++ b/buildscripts/idl/idl_check_compatibility.py @@ -200,6 +200,13 @@ IGNORE_COMMANDS_LIST: List[str] = [ 'setChangeStreamOptions', ] +RENAMED_COMPLEX_ACCESS_CHECKS = dict( + # Changed during 6.1 as part of removing multi-auth support. + get_single_user='get_authenticated_user', + get_authenticated_usernames='get_authenticated_username', + get_impersonated_usernames='get_impersonated_username', +) + class FieldCompatibility: """Information about a Field to check compatibility.""" @@ -1069,6 +1076,26 @@ def split_complex_checks( return checks, sorted(privileges, key=lambda x: len(x.action_type), reverse=True) +def compare_complex_access_checks(new_checks: List[str], old_checks: List[str]) -> bool: + """Compare two sets of access check names for equivalence.""" + # Quick path, common case where access checks match exactly. + if set(new_checks).issubset(old_checks): + return True + + def map_complex_access_check_name(name: str) -> str: + """Returns normalized name if it exists in the map, otherwise returns self.""" + if name in RENAMED_COMPLEX_ACCESS_CHECKS: + return RENAMED_COMPLEX_ACCESS_CHECKS[name] + else: + return name + + # Slow path allowing for access check renames. + old_normalized = [map_complex_access_check_name(name) for name in old_checks] + new_normalized = [map_complex_access_check_name(name) for name in new_checks] + + return set(new_normalized).issubset(old_normalized) + + def check_complex_checks(ctxt: IDLCompatibilityContext, old_complex_checks: List[syntax.AccessCheck], new_complex_checks: List[syntax.AccessCheck], cmd: syntax.Command, @@ -1080,7 +1107,7 @@ def check_complex_checks(ctxt: IDLCompatibilityContext, else: old_checks, old_privileges = split_complex_checks(old_complex_checks) new_checks, new_privileges = split_complex_checks(new_complex_checks) - if not set(new_checks).issubset(old_checks): + if not compare_complex_access_checks(new_checks, old_checks): ctxt.add_new_complex_checks_not_subset_error(cmd_name, new_idl_file_path) if len(new_privileges) > len(old_privileges): diff --git a/src/mongo/db/audit.h b/src/mongo/db/audit.h index 684983bf7be..418c73e408c 100644 --- a/src/mongo/db/audit.h +++ b/src/mongo/db/audit.h @@ -75,7 +75,7 @@ extern std::function<void(ServiceContext*)> initializeSynchronizeJob; * roleNames stored in ImpersonatedClientAttrs. */ struct ImpersonatedClientAttrs { - std::vector<UserName> userNames; + UserName userName; std::vector<RoleName> roleNames; ImpersonatedClientAttrs() = default; diff --git a/src/mongo/db/auth/SConscript b/src/mongo/db/auth/SConscript index 57af14b709d..a2bcc5b3295 100644 --- a/src/mongo/db/auth/SConscript +++ b/src/mongo/db/auth/SConscript @@ -181,7 +181,6 @@ env.Library( 'authorization_session_impl.cpp', 'authz_manager_external_state.cpp', 'authz_session_external_state.cpp', - 'user_set.cpp', 'authorization_manager_impl_parameters.idl', ], LIBDEPS=[ @@ -541,7 +540,6 @@ env.CppUnitTest( 'sasl_scram_test.cpp', 'security_key_test.cpp', 'user_document_parser_test.cpp', - 'user_set_test.cpp', ], LIBDEPS=[ '$BUILD_DIR/mongo/base', diff --git a/src/mongo/db/auth/access_checks.idl b/src/mongo/db/auth/access_checks.idl index d51e03d111c..3fd6549a842 100644 --- a/src/mongo/db/auth/access_checks.idl +++ b/src/mongo/db/auth/access_checks.idl @@ -37,10 +37,10 @@ enums: kCheckCursorSessionPrivilege : "check_cursor_session_privilege" kClearImpersonatedUserData : "clear_impersonated_user_data" kGetAuthenticatedRoleNames : "get_authenticated_role_names" - kGetAuthenticatedUserNames : "get_authenticated_user_names" + kGetAuthenticatedUserName : "get_authenticated_user_name" kGetImpersonatedRoleNames : "get_impersonated_role_names" - kGetImpersonatedUserNames : "get_impersonated_user_names" - kGetSingleUser : "get_single_user" + kGetImpersonatedUserName : "get_impersonated_user_name" + kGetAuthenticatedUser : "get_authenticated_user" kIsAuthenticated : "is_authenticated" kIsAuthenticatedAsUserWithRole : "is_authenticated_as_user_with_role" kIsAuthorizedForAnyActionOnAnyResourceInDB : "is_authorized_for_any_action_on_any_resource_in_db" diff --git a/src/mongo/db/auth/authorization_checks.cpp b/src/mongo/db/auth/authorization_checks.cpp index 06fa2164fbc..fa3d947f3f1 100644 --- a/src/mongo/db/auth/authorization_checks.cpp +++ b/src/mongo/db/auth/authorization_checks.cpp @@ -175,7 +175,7 @@ Status checkAuthForDelete(AuthorizationSession* authSession, Status checkAuthForKillCursors(AuthorizationSession* authSession, const NamespaceString& ns, - UserNameIterator cursorOwner) { + const boost::optional<UserName>& cursorOwner) { if (authSession->isAuthorizedForActionsOnResource(ResourcePattern::forClusterResource(), ActionType::killAnyCursor)) { return Status::OK(); diff --git a/src/mongo/db/auth/authorization_checks.h b/src/mongo/db/auth/authorization_checks.h index 4a211fb0ee1..10979dc263c 100644 --- a/src/mongo/db/auth/authorization_checks.h +++ b/src/mongo/db/auth/authorization_checks.h @@ -32,7 +32,6 @@ #include "mongo/base/status.h" #include "mongo/db/auth/authorization_session.h" -#include "mongo/db/auth/user_set.h" #include "mongo/db/namespace_string.h" #include "mongo/db/operation_context.h" #include "mongo/db/ops/write_ops.h" @@ -79,7 +78,7 @@ Status checkAuthForDelete(AuthorizationSession* authSession, // identifier. Status checkAuthForKillCursors(AuthorizationSession* authSession, const NamespaceString& cursorNss, - UserNameIterator cursorOwner); + const boost::optional<UserName>& cursorOwner); // Attempts to get the privileges necessary to run the aggregation pipeline specified in // 'request' on the namespace 'ns' either directly on mongoD or via mongoS. diff --git a/src/mongo/db/auth/authorization_session.cpp b/src/mongo/db/auth/authorization_session.cpp index fc5810e7a18..807d2bc66d7 100644 --- a/src/mongo/db/auth/authorization_session.cpp +++ b/src/mongo/db/auth/authorization_session.cpp @@ -57,9 +57,9 @@ namespace mongo { AuthorizationSession::~AuthorizationSession() = default; void AuthorizationSession::ScopedImpersonate::swap() { - auto impersonations = _authSession._getImpersonations(); using std::swap; - swap(*std::get<0>(impersonations), _users); + auto impersonations = _authSession._getImpersonations(); + swap(*std::get<0>(impersonations), _user); swap(*std::get<1>(impersonations), _roles); } diff --git a/src/mongo/db/auth/authorization_session.h b/src/mongo/db/auth/authorization_session.h index b6cab1ae951..777da1ad56d 100644 --- a/src/mongo/db/auth/authorization_session.h +++ b/src/mongo/db/auth/authorization_session.h @@ -40,7 +40,6 @@ #include "mongo/db/auth/authz_session_external_state.h" #include "mongo/db/auth/privilege.h" #include "mongo/db/auth/user_name.h" -#include "mongo/db/auth/user_set.h" #include "mongo/db/namespace_string.h" #include "mongo/db/operation_context.h" @@ -79,9 +78,9 @@ public: class ScopedImpersonate { public: ScopedImpersonate(AuthorizationSession* authSession, - std::vector<UserName>* users, + boost::optional<UserName>* user, std::vector<RoleName>* roles) - : _authSession(*authSession), _users(*users), _roles(*roles) { + : _authSession(*authSession), _user(*user), _roles(*roles) { swap(); } @@ -93,7 +92,7 @@ public: void swap(); AuthorizationSession& _authSession; - std::vector<UserName>& _users; + boost::optional<UserName>& _user; std::vector<RoleName>& _roles; }; @@ -153,9 +152,8 @@ public: // and ownership of the user stays with the AuthorizationManager virtual User* lookupUser(const UserName& name) = 0; - // Returns the single user on this auth session. If no user is authenticated, or if - // multiple users are authenticated, this method will throw an exception. - virtual User* getSingleUser() = 0; + // Get the authenticated user's object handle, if any. + virtual boost::optional<UserHandle> getAuthenticatedUser() = 0; // Is auth disabled? Returns true if auth is disabled. virtual bool shouldIgnoreAuthChecks() = 0; @@ -163,8 +161,8 @@ public: // Is authenticated as at least one user. virtual bool isAuthenticated() = 0; - // Gets an iterator over the names of all authenticated users stored in this manager. - virtual UserNameIterator getAuthenticatedUserNames() = 0; + // Gets the name of the currently authenticated user (if any). + virtual boost::optional<UserName> getAuthenticatedUserName() = 0; // Gets an iterator over the roles of all authenticated users stored in this manager. virtual RoleNameIterator getAuthenticatedRoleNames() = 0; @@ -258,13 +256,13 @@ public: // Returns true if the current session possesses a privilege which applies to the resource. virtual bool isAuthorizedForAnyActionOnResource(const ResourcePattern& resource) = 0; - // Replaces the data for users that a system user is impersonating with new data. - // The auditing system adds these users and their roles to each audit record in the log. - virtual void setImpersonatedUserData(const std::vector<UserName>& usernames, + // Replaces the data for the user that a system user is impersonating with new data. + // The auditing system adds this user and their roles to each audit record in the log. + virtual void setImpersonatedUserData(const UserName& username, const std::vector<RoleName>& roles) = 0; - // Gets an iterator over the names of all users that the system user is impersonating. - virtual UserNameIterator getImpersonatedUserNames() = 0; + // Gets the name of the user, if any, that the system user is impersonating. + virtual boost::optional<UserName> getImpersonatedUserName() = 0; // Gets an iterator over the roles of all users that the system user is impersonating. virtual RoleNameIterator getImpersonatedRoleNames() = 0; @@ -283,10 +281,11 @@ public: // in common. virtual bool isCoauthorizedWithClient(Client* opClient, WithLock opClientLock) = 0; - // Returns true if the session and 'userNameIter' share an authenticated user, or if both have - // no authenticated users. Impersonated users are not considered as 'authenticated' for the - // purpose of this check. This always returns true if auth is not enabled. - virtual bool isCoauthorizedWith(UserNameIterator userNameIter) = 0; + // Returns true if the specified userName is the currently authenticated user, + // or if the session is unauthenticated and `boost::none` is specified. + // Impersonated users are not considered as 'authenticated' for the purpose of this check. + // This always returns true if auth is not enabled. + virtual bool isCoauthorizedWith(const boost::optional<UserName>& userName) = 0; // Tells whether impersonation is active or not. This state is set when // setImpersonatedUserData is called and cleared when clearImpersonatedUserData is @@ -308,7 +307,7 @@ public: virtual bool mayBypassWriteBlockingMode() const = 0; protected: - virtual std::tuple<std::vector<UserName>*, std::vector<RoleName>*> _getImpersonations() = 0; + virtual std::tuple<boost::optional<UserName>*, std::vector<RoleName>*> _getImpersonations() = 0; }; // Returns a status encoding whether the current session in the specified `opCtx` has privilege to diff --git a/src/mongo/db/auth/authorization_session_for_test.cpp b/src/mongo/db/auth/authorization_session_for_test.cpp index 2bf9fae9e2b..3b0d7f247db 100644 --- a/src/mongo/db/auth/authorization_session_for_test.cpp +++ b/src/mongo/db/auth/authorization_session_for_test.cpp @@ -38,26 +38,19 @@ #include "mongo/db/auth/privilege.h" #include "mongo/db/auth/user.h" #include "mongo/db/auth/user_name.h" -#include "mongo/db/auth/user_set.h" namespace mongo { constexpr StringData AuthorizationSessionForTest::kTestDBName; -AuthorizationSessionForTest::~AuthorizationSessionForTest() { - revokeAllPrivileges(); -} - void AuthorizationSessionForTest::assumePrivilegesForDB(Privilege privilege, StringData dbName) { assumePrivilegesForDB(std::vector<Privilege>{privilege}, dbName); } void AuthorizationSessionForTest::assumePrivilegesForDB(PrivilegeVector privileges, StringData dbName) { - UserHandle userHandle(User(UserName("authorizationSessionForTestUser", dbName))); - userHandle->addPrivileges(privileges); - - _authenticatedUsers.add(userHandle); - _testUsers.emplace_back(std::move(userHandle)); + _authenticatedUser = UserHandle(User(UserName("authorizationSessionForTestUser", dbName))); + _authenticatedUser.get()->addPrivileges(privileges); + _authenticationMode = AuthorizationSession::AuthenticationMode::kConnection; _updateInternalAuthorizationState(); } @@ -73,22 +66,4 @@ void AuthorizationSessionForTest::assumePrivilegesForBuiltinRole(const RoleName& assumePrivilegesForDB(privileges, db); } -void AuthorizationSessionForTest::revokePrivilegesForDB(StringData dbName) { - _authenticatedUsers.removeByDBName(dbName); - _testUsers.erase( - std::remove_if(_testUsers.begin(), - _testUsers.end(), - [&](const auto& user) { return dbName == user->getName().getDB(); }), - _testUsers.end()); -} - -void AuthorizationSessionForTest::revokeAllPrivileges() { - _testUsers.erase(std::remove_if(_testUsers.begin(), - _testUsers.end(), - [&](const auto& user) { - _authenticatedUsers.removeByDBName(user->getName().getDB()); - return true; - }), - _testUsers.end()); -} } // namespace mongo diff --git a/src/mongo/db/auth/authorization_session_for_test.h b/src/mongo/db/auth/authorization_session_for_test.h index bc6a75c798f..61145661a95 100644 --- a/src/mongo/db/auth/authorization_session_for_test.h +++ b/src/mongo/db/auth/authorization_session_for_test.h @@ -50,11 +50,6 @@ public: static constexpr StringData kTestDBName = "authorizationSessionForTestDB"_sd; /** - * Cleans up any privileges granted via assumePrivilegesForDB(). - */ - ~AuthorizationSessionForTest(); - - /** * Grants this session all privileges in 'privileges' for the database named 'dbName'. Any prior * privileges granted on 'dbName' via a call to this method are erased. * @@ -64,25 +59,8 @@ public: void assumePrivilegesForDB(Privilege privilege, StringData dbName = kTestDBName); /** - * Revoke all privileges granted via assumePrivilegesForDB() on the database named 'dbName'. - * - * Do not use this method if also adding users via addAndAuthorizeUser() in the same database. - */ - void revokePrivilegesForDB(StringData dbName); - - /** - * Revokes all privileges granted via assumePrivilegesForDB() on every database. - * - * Do not use this method if also adding users via addAndAuthorizeUser() in the same database. - */ - void revokeAllPrivileges(); - - /** * Grants this session all privileges for the given builtin role. Do not mix with other methods. */ void assumePrivilegesForBuiltinRole(const RoleName& roleName); - -private: - std::vector<UserHandle> _testUsers; }; } // namespace mongo diff --git a/src/mongo/db/auth/authorization_session_impl.cpp b/src/mongo/db/auth/authorization_session_impl.cpp index b18cc379089..173a19cfd58 100644 --- a/src/mongo/db/auth/authorization_session_impl.cpp +++ b/src/mongo/db/auth/authorization_session_impl.cpp @@ -165,8 +165,8 @@ AuthorizationSessionImpl::AuthorizationSessionImpl( _mayBypassWriteBlockingMode(false) {} AuthorizationSessionImpl::~AuthorizationSessionImpl() { - invariant(_authenticatedUsers.count() == 0, - "All authenticated users should be logged out by the Client destruction hook"); + invariant(_authenticatedUser == boost::none, + "The authenticated user should have been logged out by the Client destruction hook"); } AuthorizationManager& AuthorizationSessionImpl::getAuthorizationManager() { @@ -179,13 +179,12 @@ void AuthorizationSessionImpl::startRequest(OperationContext* opCtx) { if (_authenticationMode == AuthenticationMode::kSecurityToken) { // Previously authenticated using SecurityToken, // clear that user and reset to unauthenticated state. - invariant(_authenticatedUsers.count() <= 1); - if (auto users = std::exchange(_authenticatedUsers, {}); users.count()) { + if (auto user = std::exchange(_authenticatedUser, boost::none); user) { LOGV2_DEBUG(6161507, 3, "security token based user still authenticated at start of request, " "clearing from authentication state", - "user"_attr = users.getNames().get().toBSON(true /* encode tenant */)); + "user"_attr = user.get()->getName().toBSON(true /* encode tenant */)); _updateInternalAuthorizationState(); } _authenticationMode = AuthenticationMode::kNone; @@ -202,15 +201,11 @@ void AuthorizationSessionImpl::startContractTracking() { Status AuthorizationSessionImpl::addAndAuthorizeUser(OperationContext* opCtx, const UserName& userName) try { - auto checkForMultipleUsers = [&]() { - const auto userCount = _authenticatedUsers.count(); - if (userCount == 0) { - // This is the first authentication. - return; - } - invariant(userCount == 1); - - auto previousUser = _authenticatedUsers.begin()->get()->getName(); + // 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) { + // Already logged in. + auto previousUser = _authenticatedUser.get()->getName(); if (previousUser == userName) { // Allow reauthenticating as the same user, but warn. LOGV2_WARNING(5626700, @@ -222,6 +217,8 @@ Status AuthorizationSessionImpl::addAndAuthorizeUser(OperationContext* opCtx, uassert(5626703, "Each client connection may only be authenticated once", !hasStrictAPI || allowMultipleUsersWithApiStrict.shouldFail()); + + return Status::OK(); } else { uassert(5626701, str::stream() << "Each client connection may only be authenticated once. " @@ -231,19 +228,11 @@ Status AuthorizationSessionImpl::addAndAuthorizeUser(OperationContext* opCtx, str::stream() << "Client has attempted to authenticate on multiple databases." << "Already authenticated as: " << previousUser); } - }; - - // 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 _authenticatedUsers. - checkForMultipleUsers(); - - AuthorizationManager* authzManager = AuthorizationManager::get(opCtx->getServiceContext()); - auto swUser = authzManager->acquireUser(opCtx, userName); - if (!swUser.isOK()) { - return swUser.getStatus(); + MONGO_UNREACHABLE; } - auto user = std::move(swUser.getValue()); + AuthorizationManager* authzManager = AuthorizationManager::get(opCtx->getServiceContext()); + auto user = uassertStatusOK(authzManager->acquireUser(opCtx, userName)); auto restrictionStatus = user->validateRestrictions(opCtx); if (!restrictionStatus.isOK()) { @@ -269,7 +258,7 @@ Status AuthorizationSessionImpl::addAndAuthorizeUser(OperationContext* opCtx, } else { _authenticationMode = AuthenticationMode::kConnection; } - _authenticatedUsers.add(std::move(user)); + _authenticatedUser = std::move(user); // If there are any users and roles in the impersonation data, clear it out. clearImpersonatedUserData(); @@ -284,29 +273,15 @@ Status AuthorizationSessionImpl::addAndAuthorizeUser(OperationContext* opCtx, User* AuthorizationSessionImpl::lookupUser(const UserName& name) { _contract.addAccessCheck(AccessCheckEnum::kLookupUser); - auto user = _authenticatedUsers.lookup(name); - return user ? user.get() : nullptr; -} - -User* AuthorizationSessionImpl::getSingleUser() { - UserName userName; - - _contract.addAccessCheck(AccessCheckEnum::kGetSingleUser); - - auto userNameItr = getAuthenticatedUserNames(); - if (userNameItr.more()) { - userName = userNameItr.next(); - if (userNameItr.more()) { - uasserted( - ErrorCodes::Unauthorized, - "logical sessions can't have multiple authenticated users (for more details see: " - "https://docs.mongodb.com/manual/core/authentication/#authentication-methods)"); - } - } else { - uasserted(ErrorCodes::Unauthorized, "there are no users authenticated"); + if (!_authenticatedUser || (_authenticatedUser.get()->getName() != name)) { + return nullptr; } + return _authenticatedUser->get(); +} - return lookupUser(userName); +boost::optional<UserHandle> AuthorizationSessionImpl::getAuthenticatedUser() { + _contract.addAccessCheck(AccessCheckEnum::kGetAuthenticatedUser); + return _authenticatedUser; } void AuthorizationSessionImpl::logoutSecurityTokenUser(Client* client) { @@ -316,13 +291,12 @@ void AuthorizationSessionImpl::logoutSecurityTokenUser(Client* client) { "Attempted to deauth a security token user while using standard login", _authenticationMode != AuthenticationMode::kConnection); - auto users = std::exchange(_authenticatedUsers, {}); - invariant(users.count() <= 1); - if (users.count() == 1) { + auto user = std::exchange(_authenticatedUser, boost::none); + if (user) { LOGV2_DEBUG(6161506, 5, "security token based user explicitly logged out", - "user"_attr = users.getNames().get().toBSON(true /* encode tenant */)); + "user"_attr = user.get()->getName().toBSON(true /* encode tenant */)); } // Explicitly skip auditing the logout event, @@ -338,12 +312,13 @@ void AuthorizationSessionImpl::logoutAllDatabases(Client* client, StringData rea "May not log out while using a security token based authentication", _authenticationMode != AuthenticationMode::kSecurityToken); - auto users = std::exchange(_authenticatedUsers, {}); - if (users.count() == 0) { + auto user = std::exchange(_authenticatedUser, boost::none); + if (user == boost::none) { return; } - audit::logLogout(client, reason, users.toBSON(), BSONArray()); + auto names = BSON_ARRAY(user.get()->getName().toBSON()); + audit::logLogout(client, reason, names, BSONArray()); clearImpersonatedUserData(); _updateInternalAuthorizationState(); @@ -359,22 +334,26 @@ void AuthorizationSessionImpl::logoutDatabase(Client* client, "May not log out while using a security token based authentication", _authenticationMode != AuthenticationMode::kSecurityToken); - // Emit logout audit event and then remove all users logged into dbname. - UserSet updatedUsers(_authenticatedUsers); - updatedUsers.removeByDBName(dbname); - if (updatedUsers.count() != _authenticatedUsers.count()) { - audit::logLogout(client, reason, _authenticatedUsers.toBSON(), updatedUsers.toBSON()); + if (!_authenticatedUser || (_authenticatedUser.get()->getName().getDB() != dbname)) { + return; } - std::swap(_authenticatedUsers, updatedUsers); + + auto names = BSON_ARRAY(_authenticatedUser.get()->getName().toBSON()); + audit::logLogout(client, reason, names, BSONArray()); + _authenticatedUser = boost::none; clearImpersonatedUserData(); _updateInternalAuthorizationState(); } -UserNameIterator AuthorizationSessionImpl::getAuthenticatedUserNames() { - _contract.addAccessCheck(AccessCheckEnum::kGetAuthenticatedUserNames); +boost::optional<UserName> AuthorizationSessionImpl::getAuthenticatedUserName() { + _contract.addAccessCheck(AccessCheckEnum::kGetAuthenticatedUserName); - return _authenticatedUsers.getNames(); + if (_authenticatedUser) { + return _authenticatedUser.get()->getName(); + } else { + return boost::none; + } } RoleNameIterator AuthorizationSessionImpl::getAuthenticatedRoleNames() { @@ -385,9 +364,8 @@ RoleNameIterator AuthorizationSessionImpl::getAuthenticatedRoleNames() { void AuthorizationSessionImpl::grantInternalAuthorization(Client* client) { stdx::lock_guard<Client> lk(*client); - if (MONGO_unlikely(_authenticatedUsers.count() > 0)) { - invariant(_authenticatedUsers.count() == 1); - auto previousUser = _authenticatedUsers.begin()->get()->getName(); + if (MONGO_unlikely(_authenticatedUser != boost::none)) { + auto previousUser = _authenticatedUser.get()->getName(); uassert(ErrorCodes::Unauthorized, str::stream() << "Unable to grant internal authorization, previously authorized as " << previousUser.getUnambiguousName(), @@ -395,7 +373,7 @@ void AuthorizationSessionImpl::grantInternalAuthorization(Client* client) { return; } - _authenticatedUsers.add(*internalSecurity.getUser()); + _authenticatedUser = *internalSecurity.getUser(); _updateInternalAuthorizationState(); } @@ -491,10 +469,8 @@ bool AuthorizationSessionImpl::isAuthorizedToCreateRole(const RoleName& roleName // The user may create a role if the localhost exception is enabled, and they already own the // role. This implies they have obtained the role through an external authorization mechanism. if (_externalState->shouldAllowLocalhost()) { - for (const auto& user : _authenticatedUsers) { - if (user->hasRole(roleName)) { - return true; - } + if (_authenticatedUser && _authenticatedUser.get()->hasRole(roleName)) { + return true; } LOGV2(20241, "Not authorized to create the first role in the system using the " @@ -669,14 +645,7 @@ StatusWith<PrivilegeVector> AuthorizationSessionImpl::checkAuthorizedToListColle bool AuthorizationSessionImpl::isAuthenticatedAsUserWithRole(const RoleName& roleName) { _contract.addAccessCheck(AccessCheckEnum::kIsAuthenticatedAsUserWithRole); - - for (UserSet::iterator it = _authenticatedUsers.begin(); it != _authenticatedUsers.end(); - ++it) { - if ((*it)->hasRole(roleName)) { - return true; - } - } - return false; + return (_authenticatedUser && _authenticatedUser.get()->hasRole(roleName)); } bool AuthorizationSessionImpl::shouldIgnoreAuthChecks() { @@ -688,113 +657,112 @@ bool AuthorizationSessionImpl::shouldIgnoreAuthChecks() { bool AuthorizationSessionImpl::isAuthenticated() { _contract.addAccessCheck(AccessCheckEnum::kIsAuthenticated); - return _authenticatedUsers.begin() != _authenticatedUsers.end(); + return _authenticatedUser != boost::none; } void AuthorizationSessionImpl::_refreshUserInfoAsNeeded(OperationContext* opCtx) { - AuthorizationManager& authMan = getAuthorizationManager(); - UserSet::iterator it = _authenticatedUsers.begin(); - auto removeUser = [&](const auto& it) { - // Take out a lock on the client here to ensure that no one reads while - // _authenticatedUsers is being modified. - stdx::lock_guard<Client> lk(*opCtx->getClient()); + if (_authenticatedUser == boost::none) { + return; + } - // The user is invalid, so make sure that we erase it from _authenticateUsers. - _authenticatedUsers.removeAt(it); + auto currentUser = _authenticatedUser.get(); + const auto& name = currentUser->getName(); + + const auto clearUser = [&] { + stdx::lock_guard<Client> lk(*opCtx->getClient()); + _authenticatedUser = boost::none; + _authenticationMode = AuthenticationMode::kNone; + _updateInternalAuthorizationState(); }; - auto replaceUser = [&](const auto& it, UserHandle updatedUser) { - // Take out a lock on the client here to ensure that no one reads while - // _authenticatedUsers is being modified. + const auto updateUser = [&](auto&& user) { stdx::lock_guard<Client> lk(*opCtx->getClient()); - _authenticatedUsers.replaceAt(it, std::move(updatedUser)); + _authenticatedUser = std::move(user); + LOGV2_DEBUG( + 20244, 1, "Updated session cache of user information for user", "user"_attr = name); + _updateInternalAuthorizationState(); }; - while (it != _authenticatedUsers.end()) { - // Anchor the UserHandle on the stack so we can refer to it throughout this iteration. - const auto currentUser = *it; - const auto& name = currentUser->getName(); - auto swUser = authMan.reacquireUser(opCtx, currentUser); - if (!swUser.isOK()) { - auto& status = swUser.getStatus(); - // If an external user is no longer in the cache and cannot be acquired from the cache's - // backing external service, it should be removed from _authenticatedUsers. This - // guarantees that no operations can be performed until the external authorization - // provider comes back up. - if (name.getDB() == "$external"_sd) { - removeUser(it++); - LOGV2(5914804, - "Removed external user from session cache of user information because of " - "error status", - "user"_attr = name, - "status"_attr = status); - continue; // No need to advance "it" in this case. - } + auto swUser = getAuthorizationManager().reacquireUser(opCtx, currentUser); + if (!swUser.isOK()) { + auto& status = swUser.getStatus(); + // If an external user is no longer in the cache and cannot be acquired from the cache's + // backing external service, it should be cleared from _authenticatedUser. This + // guarantees that no operations can be performed until the external authorization + // provider comes back up. + if (name.getDB() == "$external"_sd) { + clearUser(); + LOGV2(5914804, + "Removed external user from session cache of user information because of " + "error status", + "user"_attr = name, + "status"_attr = status); + return; + } - switch (status.code()) { - case ErrorCodes::UserNotFound: { - // User does not exist anymore; remove it from _authenticatedUsers. - removeUser(it++); - LOGV2(20245, - "Removed deleted user from session cache of user information", - "user"_attr = name); - continue; // No need to advance "it" in this case. - } - case ErrorCodes::UnsupportedFormat: { - // An auth subsystem has explicitly indicated a failure. - removeUser(it++); - LOGV2(20246, - "Removed user from session cache of user information because of " - "refresh failure", - "user"_attr = name, - "error"_attr = status); - continue; // No need to advance "it" in this case. - } - default: - // Unrecognized error; assume that it's transient, and continue working with the - // out-of-date privilege data. - LOGV2_WARNING(20247, - "Could not fetch updated user privilege information for {user}; " - "continuing to use old information. Reason is {error}", - "Could not fetch updated user privilege information, continuing " - "to use old information", - "user"_attr = name, - "error"_attr = redact(status)); - break; + switch (status.code()) { + case ErrorCodes::UserNotFound: { + // User does not exist anymore. + clearUser(); + LOGV2(20245, + "Removed deleted user from session cache of user information", + "user"_attr = name); + return; } - } else if (!currentUser.isValid() || currentUser->isInvalidated()) { - // Our user handle has changed, update the our list of users. - auto updatedUser = std::move(swUser.getValue()); - try { - uassertStatusOK(updatedUser->validateRestrictions(opCtx)); - } catch (const DBException& ex) { - removeUser(it++); - - LOGV2(20242, - "Removed user with unmet authentication restrictions from " - "session cache of user information. Restriction failed", + case ErrorCodes::UnsupportedFormat: { + // An auth subsystem has explicitly indicated a failure. + clearUser(); + LOGV2(20246, + "Removed user from session cache of user information because of " + "refresh failure", "user"_attr = name, - "reason"_attr = ex.reason()); - continue; // No need to advance "it" in this case. - } catch (...) { - removeUser(it++); - - LOGV2(20243, - "Evaluating authentication restrictions for user resulted in an " - "unknown exception. Removing user from the session cache", - "user"_attr = name); - continue; // No need to advance "it" in this case. + "error"_attr = status); + return; } + default: + // Unrecognized error; assume that it's transient, and continue working with the + // out-of-date privilege data. + LOGV2_WARNING(20247, + "Could not fetch updated user privilege information for {user}; " + "continuing to use old information. Reason is {error}", + "Could not fetch updated user privilege information, continuing " + "to use old information", + "user"_attr = name, + "error"_attr = redact(status)); + return; + } + } - replaceUser(it, std::move(updatedUser)); + // !ok check above should never fallthrough. + invariant(swUser.isOK()); - LOGV2_DEBUG( - 20244, 1, "Updated session cache of user information for user", "user"_attr = name); - } + if (currentUser.isValid() && !currentUser->isInvalidated()) { + // Current user may carry on, no need to update. + return; + } - ++it; + // Our user handle has changed, update it. + auto user = std::move(swUser.getValue()); + try { + uassertStatusOK(user->validateRestrictions(opCtx)); + } catch (const DBException& ex) { + clearUser(); + LOGV2(20242, + "Removed user with unmet authentication restrictions from " + "session cache of user information. Restriction failed", + "user"_attr = name, + "reason"_attr = ex.reason()); + return; + } catch (...) { + clearUser(); + LOGV2(20243, + "Evaluating authentication restrictions for user resulted in an " + "unknown exception. Removing user from the session cache", + "user"_attr = name); + return; } - _updateInternalAuthorizationState(); + + updateUser(std::move(user)); } bool AuthorizationSessionImpl::isAuthorizedForAnyActionOnAnyResourceInDB(StringData db) { @@ -804,59 +772,61 @@ bool AuthorizationSessionImpl::isAuthorizedForAnyActionOnAnyResourceInDB(StringD return true; } - for (const auto& user : _authenticatedUsers) { - // First lookup any Privileges on this database specifying Database resources - if (user->hasActionsForResource(ResourcePattern::forDatabaseName(db))) { - return true; - } + if (_authenticatedUser == boost::none) { + return false; + } + + const auto& user = _authenticatedUser.get(); + // First lookup any Privileges on this database specifying Database resources + if (user->hasActionsForResource(ResourcePattern::forDatabaseName(db))) { + return true; + } - // Any resource will match any collection in the database - if (user->hasActionsForResource(ResourcePattern::forAnyResource())) { + // Any resource will match any collection in the database + if (user->hasActionsForResource(ResourcePattern::forAnyResource())) { + return true; + } + + // Any resource will match any system_buckets collection in the database + if (user->hasActionsForResource(ResourcePattern::forAnySystemBuckets()) || + user->hasActionsForResource(ResourcePattern::forAnySystemBucketsInDatabase(db))) { + return true; + } + + // If the user is authorized for anyNormalResource, then they implicitly have access + // to most databases. + if (db != "local" && db != "config" && + user->hasActionsForResource(ResourcePattern::forAnyNormalResource())) { + return true; + } + + // We've checked all the resource types that can be directly expressed. Now we must + // iterate all privileges, until we see something that could reside in the target database. + auto map = user->getPrivileges(); + for (const auto& privilege : map) { + // If the user has a Collection privilege, then they're authorized for this resource + // on all databases. + if (privilege.first.isCollectionPattern()) { return true; } - // Any resource will match any system_buckets collection in the database - if (user->hasActionsForResource(ResourcePattern::forAnySystemBuckets()) || - user->hasActionsForResource(ResourcePattern::forAnySystemBucketsInDatabase(db))) { + // User can see system_buckets in any database so we consider them to have permission in + // this database + if (privilege.first.isAnySystemBucketsCollectionInAnyDB()) { return true; } - // If the user is authorized for anyNormalResource, then they implicitly have access - // to most databases. - if (db != "local" && db != "config" && - user->hasActionsForResource(ResourcePattern::forAnyNormalResource())) { + // If the user has an exact namespace privilege on a collection in this database, they + // have access to a resource in this database. + if (privilege.first.isExactNamespacePattern() && privilege.first.databaseToMatch() == db) { return true; } - // We've checked all the resource types that can be directly expressed. Now we must - // iterate all privileges, until we see something that could reside in the target database. - User::ResourcePrivilegeMap map = user->getPrivileges(); - for (const auto& privilege : map) { - // If the user has a Collection privilege, then they're authorized for this resource - // on all databases. - if (privilege.first.isCollectionPattern()) { - return true; - } - - // User can see system_buckets in any database so we consider them to have permission in - // this database - if (privilege.first.isAnySystemBucketsCollectionInAnyDB()) { - return true; - } - - // If the user has an exact namespace privilege on a collection in this database, they - // have access to a resource in this database. - if (privilege.first.isExactNamespacePattern() && - privilege.first.databaseToMatch() == db) { - return true; - } - - // If the user has an exact namespace privilege on a system.buckets collection in this - // database, they have access to a resource in this database. - if (privilege.first.isExactSystemBucketsCollection() && - privilege.first.databaseToMatch() == db) { - return true; - } + // If the user has an exact namespace privilege on a system.buckets collection in this + // database, they have access to a resource in this database. + if (privilege.first.isExactSystemBucketsCollection() && + privilege.first.databaseToMatch() == db) { + return true; } } @@ -870,15 +840,18 @@ bool AuthorizationSessionImpl::isAuthorizedForAnyActionOnResource(const Resource return true; } + if (_authenticatedUser == boost::none) { + return false; + } + std::array<ResourcePattern, resourceSearchListCapacity> resourceSearchList; const int resourceSearchListLength = buildResourceSearchList(resource, resourceSearchList.data()); + const auto& user = _authenticatedUser.get(); for (int i = 0; i < resourceSearchListLength; ++i) { - for (const auto& user : _authenticatedUsers) { - if (user->hasActionsForResource(resourceSearchList[i])) { - return true; - } + if (user->hasActionsForResource(resourceSearchList[i])) { + return true; } } @@ -895,95 +868,75 @@ bool AuthorizationSessionImpl::_isAuthorizedForPrivilege(const Privilege& privil const int resourceSearchListLength = buildResourceSearchList(target, resourceSearchList); ActionSet unmetRequirements = privilege.getActions(); - - PrivilegeVector defaultPrivileges = _getDefaultPrivileges(); - for (PrivilegeVector::iterator it = defaultPrivileges.begin(); it != defaultPrivileges.end(); - ++it) { + for (const auto& priv : _getDefaultPrivileges()) { for (int i = 0; i < resourceSearchListLength; ++i) { - if (!(it->getResourcePattern() == resourceSearchList[i])) + if (!(priv.getResourcePattern() == resourceSearchList[i])) { continue; + } - ActionSet userActions = it->getActions(); + ActionSet userActions = priv.getActions(); unmetRequirements.removeAllActionsFromSet(userActions); - if (unmetRequirements.empty()) + if (unmetRequirements.empty()) { return true; + } } } - for (const auto& user : _authenticatedUsers) { - for (int i = 0; i < resourceSearchListLength; ++i) { - ActionSet userActions = user->getActionsForResource(resourceSearchList[i]); - unmetRequirements.removeAllActionsFromSet(userActions); + if (_authenticatedUser == boost::none) { + return false; + } - if (unmetRequirements.empty()) { - return true; - } + const auto& user = _authenticatedUser.get(); + for (int i = 0; i < resourceSearchListLength; ++i) { + ActionSet userActions = user->getActionsForResource(resourceSearchList[i]); + unmetRequirements.removeAllActionsFromSet(userActions); + + if (unmetRequirements.empty()) { + return true; } } return false; } -void AuthorizationSessionImpl::setImpersonatedUserData(const std::vector<UserName>& usernames, +void AuthorizationSessionImpl::setImpersonatedUserData(const UserName& username, const std::vector<RoleName>& roles) { - _impersonatedUserNames = usernames; + _impersonatedUserName = username; _impersonatedRoleNames = roles; _impersonationFlag = true; } bool AuthorizationSessionImpl::isCoauthorizedWithClient(Client* opClient, WithLock opClientLock) { _contract.addAccessCheck(AccessCheckEnum::kIsCoauthorizedWithClient); - auto getUserNames = [](AuthorizationSession* authSession) { + auto getUserName = [](AuthorizationSession* authSession) { if (authSession->isImpersonating()) { - return authSession->getImpersonatedUserNames(); + return authSession->getImpersonatedUserName(); } else { - return authSession->getAuthenticatedUserNames(); + return authSession->getAuthenticatedUserName(); } }; - UserNameIterator it = getUserNames(this); - while (it.more()) { - UserNameIterator opIt = getUserNames(AuthorizationSession::get(opClient)); - while (opIt.more()) { - if (it.get() == opIt.get()) { - return true; - } - opIt.next(); - } - it.next(); + if (auto myname = getUserName(this)) { + return myname == getUserName(AuthorizationSession::get(opClient)); + } else { + return false; } - - return false; } -bool AuthorizationSessionImpl::isCoauthorizedWith(UserNameIterator userNameIter) { +bool AuthorizationSessionImpl::isCoauthorizedWith(const boost::optional<UserName>& userName) { _contract.addAccessCheck(AccessCheckEnum::kIsCoauthorizedWith); if (!getAuthorizationManager().isAuthEnabled()) { return true; } - if (!userNameIter.more() && !isAuthenticated()) { - return true; - } - - for (; userNameIter.more(); userNameIter.next()) { - for (UserNameIterator thisUserNameIter = getAuthenticatedUserNames(); - thisUserNameIter.more(); - thisUserNameIter.next()) { - if (*userNameIter == *thisUserNameIter) { - return true; - } - } - } - - return false; + return getAuthenticatedUserName() == userName; } -UserNameIterator AuthorizationSessionImpl::getImpersonatedUserNames() { - _contract.addAccessCheck(AccessCheckEnum::kGetImpersonatedUserNames); +boost::optional<UserName> AuthorizationSessionImpl::getImpersonatedUserName() { + _contract.addAccessCheck(AccessCheckEnum::kGetImpersonatedUserName); - return makeUserNameIterator(_impersonatedUserNames.begin(), _impersonatedUserNames.end()); + return _impersonatedUserName; } RoleNameIterator AuthorizationSessionImpl::getImpersonatedRoleNames() { @@ -1000,7 +953,7 @@ bool AuthorizationSessionImpl::isUsingLocalhostBypass() { // Clear the vectors of impersonated usernames and roles. void AuthorizationSessionImpl::clearImpersonatedUserData() { - _impersonatedUserNames.clear(); + _impersonatedUserName = boost::none; _impersonatedRoleNames.clear(); _impersonationFlag = false; } @@ -1079,15 +1032,15 @@ void AuthorizationSessionImpl::verifyContract(const AuthorizationContract* contr tempContract.addAccessCheck(AccessCheckEnum::kIsAuthenticated); // These checks are done by auditing + tempContract.addAccessCheck(AccessCheckEnum::kGetAuthenticatedUserName); tempContract.addAccessCheck(AccessCheckEnum::kGetAuthenticatedRoleNames); - tempContract.addAccessCheck(AccessCheckEnum::kGetAuthenticatedUserNames); - tempContract.addAccessCheck(AccessCheckEnum::kGetImpersonatedUserNames); + tempContract.addAccessCheck(AccessCheckEnum::kGetImpersonatedUserName); tempContract.addAccessCheck(AccessCheckEnum::kGetImpersonatedRoleNames); // Since internal sessions are started by the server, the generated authorization contract is // missing the following user access checks, so we add them here to allow commands that spawn // internal sessions to pass this authorization check. - tempContract.addAccessCheck(AccessCheckEnum::kGetSingleUser); + tempContract.addAccessCheck(AccessCheckEnum::kGetAuthenticatedUser); tempContract.addAccessCheck(AccessCheckEnum::kLookupUser); // "internal" comes from readRequestMetadata and sharded clusters @@ -1106,18 +1059,16 @@ void AuthorizationSessionImpl::verifyContract(const AuthorizationContract* contr void AuthorizationSessionImpl::_updateInternalAuthorizationState() { // Update the authenticated role names vector to reflect current state. _authenticatedRoleNames.clear(); - for (const auto& userHandle : _authenticatedUsers) { - RoleNameIterator roles = userHandle->getIndirectRoles(); + if (_authenticatedUser == boost::none) { + _authenticationMode = AuthenticationMode::kNone; + } else { + RoleNameIterator roles = _authenticatedUser.get()->getIndirectRoles(); while (roles.more()) { RoleName roleName = roles.next(); _authenticatedRoleNames.push_back(RoleName(roleName.getRole(), roleName.getDB())); } } - if (_authenticatedUsers.count() == 0) { - _authenticationMode = AuthenticationMode::kNone; - } - // Update cached _mayBypassWriteBlockingMode to reflect current state. _mayBypassWriteBlockingMode = _isAuthorizedForPrivilege(kBypassWriteBlockingModeOnClusterPrivilege); diff --git a/src/mongo/db/auth/authorization_session_impl.h b/src/mongo/db/auth/authorization_session_impl.h index eefbcbfabe5..94a13c59249 100644 --- a/src/mongo/db/auth/authorization_session_impl.h +++ b/src/mongo/db/auth/authorization_session_impl.h @@ -40,7 +40,6 @@ #include "mongo/db/auth/authz_session_external_state.h" #include "mongo/db/auth/privilege.h" #include "mongo/db/auth/user_name.h" -#include "mongo/db/auth/user_set.h" #include "mongo/db/namespace_string.h" namespace mongo { @@ -54,7 +53,7 @@ class Client; * * An AuthorizationSession object is present within every mongo::Client object. * - * Users in the _authenticatedUsers cache may get marked as invalid by the AuthorizationManager, + * The active _authenticatedUser may get marked as invalid by the AuthorizationManager, * for instance if their privileges are changed by a user or role modification command. At the * beginning of every user-initiated operation startRequest() gets called which updates * the cached information about any users who have been marked as invalid. This guarantees that @@ -85,9 +84,9 @@ public: bool isAuthenticated() override; - User* getSingleUser() override; + boost::optional<UserHandle> getAuthenticatedUser() override; - UserNameIterator getAuthenticatedUserNames() override; + boost::optional<UserName> getAuthenticatedUserName() override; RoleNameIterator getAuthenticatedRoleNames() override; @@ -137,10 +136,10 @@ public: bool isAuthorizedForAnyActionOnResource(const ResourcePattern& resource) override; - void setImpersonatedUserData(const std::vector<UserName>& usernames, + void setImpersonatedUserData(const UserName& username, const std::vector<RoleName>& roles) override; - UserNameIterator getImpersonatedUserNames() override; + boost::optional<UserName> getImpersonatedUserName() override; RoleNameIterator getImpersonatedRoleNames() override; @@ -148,7 +147,7 @@ public: bool isCoauthorizedWithClient(Client* opClient, WithLock opClientLock) override; - bool isCoauthorizedWith(UserNameIterator userNameIter) override; + bool isCoauthorizedWith(const boost::optional<UserName>& userName) override; bool isImpersonating() const override; @@ -173,8 +172,8 @@ protected: void _updateInternalAuthorizationState(); - // All Users who have been authenticated on this connection. - UserSet _authenticatedUsers; + // The User who has been authenticated on this connection. + boost::optional<UserHandle> _authenticatedUser; // What authentication mode we're currently operating in. AuthenticationMode _authenticationMode = AuthenticationMode::kNone; @@ -194,8 +193,8 @@ private: // lock on the admin database (to update out-of-date user privilege information). bool _isAuthorizedForPrivilege(const Privilege& privilege); - std::tuple<std::vector<UserName>*, std::vector<RoleName>*> _getImpersonations() override { - return std::make_tuple(&_impersonatedUserNames, &_impersonatedRoleNames); + std::tuple<boost::optional<UserName>*, std::vector<RoleName>*> _getImpersonations() override { + return std::make_tuple(&_impersonatedUserName, &_impersonatedRoleNames); } @@ -211,7 +210,7 @@ private: // A vector of impersonated UserNames and a vector of those users' RoleNames. // These are used in the auditing system. They are not used for authz checks. - std::vector<UserName> _impersonatedUserNames; + boost::optional<UserName> _impersonatedUserName; std::vector<RoleName> _impersonatedRoleNames; bool _impersonationFlag; diff --git a/src/mongo/db/auth/authorization_session_test.cpp b/src/mongo/db/auth/authorization_session_test.cpp index 4ecb9cca692..1336554897a 100644 --- a/src/mongo/db/auth/authorization_session_test.cpp +++ b/src/mongo/db/auth/authorization_session_test.cpp @@ -1054,45 +1054,33 @@ TEST_F(AuthorizationSessionTest, ASSERT_TRUE(authzSession->isAuthorizedForPrivileges(privileges)); } -TEST_F(AuthorizationSessionTest, UnauthorizedSessionIsCoauthorizedWithEmptyUserSet) { - std::vector<UserName> userSet; - ASSERT_TRUE( - authzSession->isCoauthorizedWith(makeUserNameIterator(userSet.begin(), userSet.end()))); +TEST_F(AuthorizationSessionTest, UnauthorizedSessionIsCoauthorizedWithNobody) { + ASSERT_TRUE(authzSession->isCoauthorizedWith(boost::none)); } -TEST_F(AuthorizationSessionTest, UnauthorizedSessionIsNotCoauthorizedWithNonemptyUserSet) { - std::vector<UserName> userSet; - userSet.emplace_back("spencer", "test"); - ASSERT_FALSE( - authzSession->isCoauthorizedWith(makeUserNameIterator(userSet.begin(), userSet.end()))); +TEST_F(AuthorizationSessionTest, UnauthorizedSessionIsNotCoauthorizedWithAnybody) { + ASSERT_FALSE(authzSession->isCoauthorizedWith(UserName("spencer", "test"))); } -TEST_F(AuthorizationSessionTest, - UnauthorizedSessionIsCoauthorizedWithNonemptyUserSetWhenAuthIsDisabled) { +TEST_F(AuthorizationSessionTest, UnauthorizedSessionIsCoauthorizedWithAnybodyWhenAuthIsDisabled) { authzManager->setAuthEnabled(false); - std::vector<UserName> userSet; - userSet.emplace_back("spencer", "test"); - ASSERT_TRUE( - authzSession->isCoauthorizedWith(makeUserNameIterator(userSet.begin(), userSet.end()))); + ASSERT_TRUE(authzSession->isCoauthorizedWith(UserName("spencer", "test"))); } -TEST_F(AuthorizationSessionTest, AuthorizedSessionIsNotCoauthorizedWithEmptyUserSet) { - ASSERT_OK(createUser({"spencer", "test"}, {})); - ASSERT_OK(authzSession->addAndAuthorizeUser(_opCtx.get(), UserName("spencer", "test"))); - std::vector<UserName> userSet; - ASSERT_FALSE( - authzSession->isCoauthorizedWith(makeUserNameIterator(userSet.begin(), userSet.end()))); +TEST_F(AuthorizationSessionTest, AuthorizedSessionIsNotCoauthorizedNobody) { + UserName user("spencer", "test"); + ASSERT_OK(createUser(user, {})); + ASSERT_OK(authzSession->addAndAuthorizeUser(_opCtx.get(), user)); + ASSERT_FALSE(authzSession->isCoauthorizedWith(boost::none)); authzSession->logoutDatabase(_client.get(), "test", "Kill the test!"); } -TEST_F(AuthorizationSessionTest, - AuthorizedSessionIsCoauthorizedWithEmptyUserSetWhenAuthIsDisabled) { +TEST_F(AuthorizationSessionTest, AuthorizedSessionIsCoauthorizedNobodyWhenAuthIsDisabled) { + UserName user("spencer", "test"); authzManager->setAuthEnabled(false); - ASSERT_OK(createUser({"spencer", "test"}, {})); - ASSERT_OK(authzSession->addAndAuthorizeUser(_opCtx.get(), UserName("spencer", "test"))); - std::vector<UserName> userSet; - ASSERT_TRUE( - authzSession->isCoauthorizedWith(makeUserNameIterator(userSet.begin(), userSet.end()))); + ASSERT_OK(createUser(user, {})); + ASSERT_OK(authzSession->addAndAuthorizeUser(_opCtx.get(), user)); + ASSERT_TRUE(authzSession->isCoauthorizedWith(user)); authzSession->logoutDatabase(_client.get(), "test", "Kill the test!"); } diff --git a/src/mongo/db/auth/impersonation_session.cpp b/src/mongo/db/auth/impersonation_session.cpp index 3846b255bb0..88742e20357 100644 --- a/src/mongo/db/auth/impersonation_session.cpp +++ b/src/mongo/db/auth/impersonation_session.cpp @@ -55,7 +55,8 @@ ImpersonationSessionGuard::ImpersonationSessionGuard(OperationContext* opCtx) : authSession->isAuthorizedForPrivilege( Privilege(ResourcePattern::forClusterResource(), ActionType::impersonate))); fassert(ErrorCodes::InternalError, !authSession->isImpersonating()); - authSession->setImpersonatedUserData(impersonatedUsersAndRoles->getUsers(), + fassert(ErrorCodes::InternalError, impersonatedUsersAndRoles->getUsers().size() == 1); + authSession->setImpersonatedUserData(impersonatedUsersAndRoles->getUsers()[0], impersonatedUsersAndRoles->getRoles()); _active = true; return; diff --git a/src/mongo/db/auth/user_set.cpp b/src/mongo/db/auth/user_set.cpp deleted file mode 100644 index cdc19034fbc..00000000000 --- a/src/mongo/db/auth/user_set.cpp +++ /dev/null @@ -1,121 +0,0 @@ -/** - * Copyright (C) 2018-present MongoDB, Inc. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the Server Side Public License, version 1, - * as published by MongoDB, Inc. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * Server Side Public License for more details. - * - * You should have received a copy of the Server Side Public License - * along with this program. If not, see - * <http://www.mongodb.com/licensing/server-side-public-license>. - * - * As a special exception, the copyright holders give permission to link the - * code of portions of this program with the OpenSSL library under certain - * conditions as described in each individual source file and distribute - * linked combinations including the program with the OpenSSL library. You - * must comply with the Server Side Public License in all respects for - * all of the code used other than as permitted herein. If you modify file(s) - * with this exception, you may extend this exception to your version of the - * file(s), but you are not obligated to do so. If you do not wish to do so, - * delete this exception statement from your version. If you delete this - * exception statement from all source files in the program, then also delete - * it in the license file. - */ - -#include "mongo/platform/basic.h" - -#include "mongo/db/auth/user_set.h" - -#include <algorithm> - -namespace mongo { -namespace { - -class UserSetNameIteratorImpl : public UserNameIterator::Impl { - UserSetNameIteratorImpl(const UserSetNameIteratorImpl&) = delete; - UserSetNameIteratorImpl& operator=(const UserSetNameIteratorImpl&) = delete; - -public: - UserSetNameIteratorImpl(const UserSet::const_iterator& begin, - const UserSet::const_iterator& end) - : _curr(begin), _end(end) {} - - ~UserSetNameIteratorImpl() = default; - - bool more() const override { - return _curr != _end; - } - - const UserName& next() override { - return (*(_curr++))->getName(); - } - - const UserName& get() const override { - return (*_curr)->getName(); - } - - UserNameIterator::Impl* doClone() const override { - return new UserSetNameIteratorImpl(_curr, _end); - } - -private: - UserSet::const_iterator _curr; - UserSet::const_iterator _end; -}; - -} // namespace - -UserSet::UserSet() = default; - -void UserSet::add(UserHandle user) { - auto it = std::find_if(_users.begin(), _users.end(), [&](const auto& storedUser) { - return user->getName().getDB() == storedUser->getName().getDB(); - }); - if (it == _users.end()) { - _users.push_back(std::move(user)); - } else { - *it = std::move(user); - } -} - -void UserSet::removeByDBName(StringData dbname) { - auto it = std::find_if(_users.begin(), _users.end(), [&](const auto& user) { - return user->getName().getDB() == dbname; - }); - if (it != _users.end()) { - _users.erase(it); - } -} - -void UserSet::replaceAt(iterator it, UserHandle replacement) { - *it = std::move(replacement); -} - -void UserSet::removeAt(iterator it) { - _users.erase(it); -} - -UserHandle UserSet::lookup(const UserName& name) const { - auto it = std::find_if( - _users.begin(), _users.end(), [&](const auto& user) { return user->getName() == name; }); - return (it != _users.end()) ? *it : UserHandle(); -} - -UserHandle UserSet::lookupByDBName(StringData dbname) const { - auto it = std::find_if(_users.begin(), _users.end(), [&](const auto& user) { - return user->getName().getDB() == dbname; - }); - return (it != _users.end()) ? *it : UserHandle(); -} - -UserNameIterator UserSet::getNames() const { - return UserNameIterator( - std::make_unique<UserSetNameIteratorImpl>(_users.cbegin(), _users.cend())); -} - -} // namespace mongo diff --git a/src/mongo/db/auth/user_set.h b/src/mongo/db/auth/user_set.h deleted file mode 100644 index bc1bb633fcb..00000000000 --- a/src/mongo/db/auth/user_set.h +++ /dev/null @@ -1,127 +0,0 @@ -/** - * Copyright (C) 2018-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 <list> - -#include "mongo/db/auth/user.h" - -namespace mongo { - -/** - * A collection of authenticated users. - * This class does not do any locking/synchronization, the consumer will be responsible for - * synchronizing access. - */ -class UserSet { -public: - using iterator = std::list<UserHandle>::iterator; - using const_iterator = std::list<UserHandle>::const_iterator; - - UserSet(); - UserSet(const UserSet&) = default; - UserSet& operator=(const UserSet&) = default; - - /** - * Adds a User to the UserSet. - * - * As there can only be one user per database in the UserSet, if a User already exists for - * the new User's database, the old user will be removed from the set and returned. It is - * the caller's responsibility to then release that user. If no user already exists for the - * new user's database, returns NULL. - * - * Invalidates any outstanding iterators or NameIterators. - */ - void add(UserHandle user); - - /** - * Replaces the user at "it" with "replacement." Does not take ownership of the User. - * Returns a pointer to the old user referenced by "it". Does _not_ invalidate "iterator" - * instances. - */ - void replaceAt(iterator it, UserHandle replacement); - - /** - * Removes the user at "it", and returns a pointer to it. After this call, "it" remains - * valid. It will either equal "end()", or refer to some user between the values of "it" - * and "end()" before this call was made. - */ - void removeAt(iterator it); - - /** - * Removes the User whose authentication credentials came from dbname, and returns that - * user. It is the caller's responsibility to then release that user back to the - * authorizationManger. If no user exists for the given database, returns NULL; - */ - void removeByDBName(StringData dbname); - - // Returns the User with the given name, or NULL if not found. - // Ownership of the returned User remains with the UserSet. The pointer - // returned is only guaranteed to remain valid until the next non-const method is called - // on the UserSet. - UserHandle lookup(const UserName& name) const; - - // Gets the user whose authentication credentials came from dbname, or NULL if none - // exist. There should be at most one such user. - UserHandle lookupByDBName(StringData dbname) const; - - // Gets an iterator over the names of the users stored in the set. The iterator is - // valid until the next non-const method is called on the UserSet. - UserNameIterator getNames() const; - - iterator begin() { - return _users.begin(); - } - - iterator end() { - return _users.end(); - } - - // Returns the number of users stored in the set. - std::size_t count() const { - return _users.size(); - } - - // Generates a BSONArray representation of the UserSet. - BSONArray toBSON() const { - BSONArrayBuilder userNamesArray; - for (const auto& userHandle : _users) { - userHandle->getName().serializeToBSON(&userNamesArray); - } - return userNamesArray.arr(); - } - -private: - // The UserSet maintains ownership of the Users in it, and is responsible for - // returning them to the AuthorizationManager when done with them. - std::list<UserHandle> _users; -}; - -} // namespace mongo diff --git a/src/mongo/db/auth/user_set_test.cpp b/src/mongo/db/auth/user_set_test.cpp deleted file mode 100644 index e2a87b12b4c..00000000000 --- a/src/mongo/db/auth/user_set_test.cpp +++ /dev/null @@ -1,112 +0,0 @@ -/** - * Copyright (C) 2018-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. - */ - -/** - * Unit tests of the UserSet type. - */ - -#include "mongo/db/auth/user.h" -#include "mongo/db/auth/user_name.h" -#include "mongo/db/auth/user_set.h" -#include "mongo/unittest/unittest.h" - -#define ASSERT_NULL(EXPR) ASSERT_FALSE((EXPR)) - -namespace mongo { -namespace { - -TEST(UserSetTest, BasicTest) { - UserSet set; - - UserHandle p1(User(UserName("Bob", "test"))); - UserHandle p2(User(UserName("George", "test"))); - UserHandle p3(User(UserName("Bob", "test2"))); - - ASSERT_NULL(set.lookup(UserName("Bob", "test"))); - ASSERT_NULL(set.lookup(UserName("George", "test"))); - ASSERT_NULL(set.lookup(UserName("Bob", "test2"))); - ASSERT_NULL(set.lookupByDBName("test")); - ASSERT_NULL(set.lookupByDBName("test2")); - - set.add(p1); - - ASSERT_EQUALS(p1, set.lookup(UserName("Bob", "test"))); - ASSERT_EQUALS(p1, set.lookupByDBName("test")); - ASSERT_NULL(set.lookup(UserName("George", "test"))); - ASSERT_NULL(set.lookup(UserName("Bob", "test2"))); - ASSERT_NULL(set.lookupByDBName("test2")); - - // This should not replace the existing user "Bob" because they are different databases - set.add(p3); - - ASSERT_EQUALS(p1, set.lookup(UserName("Bob", "test"))); - ASSERT_EQUALS(p1, set.lookupByDBName("test")); - ASSERT_NULL(set.lookup(UserName("George", "test"))); - ASSERT_EQUALS(p3, set.lookup(UserName("Bob", "test2"))); - ASSERT_EQUALS(p3, set.lookupByDBName("test2")); - - set.add(p2); // This should replace Bob since they're on the same database - - ASSERT_NULL(set.lookup(UserName("Bob", "test"))); - ASSERT_EQUALS(p2, set.lookup(UserName("George", "test"))); - ASSERT_EQUALS(p2, set.lookupByDBName("test")); - ASSERT_EQUALS(p3, set.lookup(UserName("Bob", "test2"))); - ASSERT_EQUALS(p3, set.lookupByDBName("test2")); - - set.removeByDBName("test"_sd); - - ASSERT_NULL(set.lookup(UserName("Bob", "test"))); - ASSERT_NULL(set.lookup(UserName("George", "test"))); - ASSERT_NULL(set.lookupByDBName("test")); - ASSERT_EQUALS(p3, set.lookup(UserName("Bob", "test2"))); - ASSERT_EQUALS(p3, set.lookupByDBName("test2")); - - UserNameIterator iter = set.getNames(); - ASSERT_TRUE(iter.more()); - ASSERT_EQUALS(iter.next(), UserName("Bob", "test2")); - ASSERT_FALSE(iter.more()); -} - -TEST(UserSetTest, IterateNames) { - UserSet pset; - UserNameIterator iter = pset.getNames(); - ASSERT(!iter.more()); - - UserHandle user(User(UserName("bob", "test"))); - pset.add(std::move(user)); - - iter = pset.getNames(); - ASSERT(iter.more()); - ASSERT_EQUALS(*iter, UserName("bob", "test")); - ASSERT_EQUALS(iter.next(), UserName("bob", "test")); - ASSERT(!iter.more()); -} - -} // namespace -} // namespace mongo diff --git a/src/mongo/db/clientcursor.cpp b/src/mongo/db/clientcursor.cpp index bb205d784f2..9c1be7e4fc2 100644 --- a/src/mongo/db/clientcursor.cpp +++ b/src/mongo/db/clientcursor.cpp @@ -87,7 +87,7 @@ ClientCursor::ClientCursor(ClientCursorParams params, Date_t now) : _cursorid(cursorId), _nss(std::move(params.nss)), - _authenticatedUsers(std::move(params.authenticatedUsers)), + _authenticatedUser(std::move(params.authenticatedUser)), _lsid(operationUsingCursor->getLogicalSessionId()), _txnNumber(operationUsingCursor->getTxnNumber()), _apiParameters(std::move(params.apiParameters)), diff --git a/src/mongo/db/clientcursor.h b/src/mongo/db/clientcursor.h index 0724c49d4dd..70b5143fe8e 100644 --- a/src/mongo/db/clientcursor.h +++ b/src/mongo/db/clientcursor.h @@ -58,7 +58,7 @@ class RecoveryUnit; struct ClientCursorParams { ClientCursorParams(std::unique_ptr<PlanExecutor, PlanExecutor::Deleter> planExecutor, NamespaceString nss, - UserNameIterator authenticatedUsersIter, + boost::optional<UserName> authenticatedUser, APIParameters apiParameters, WriteConcernOptions writeConcernOptions, repl::ReadConcernArgs readConcernArgs, @@ -67,6 +67,7 @@ struct ClientCursorParams { PrivilegeVector originatingPrivileges) : exec(std::move(planExecutor)), nss(std::move(nss)), + authenticatedUser(std::move(authenticatedUser)), apiParameters(std::move(apiParameters)), writeConcernOptions(std::move(writeConcernOptions)), readConcernArgs(std::move(readConcernArgs)), @@ -81,11 +82,7 @@ struct ClientCursorParams { exec->getCanonicalQuery()->getFindCommandRequest()) : TailableModeEnum::kNormal), originatingCommandObj(originatingCommandObj.getOwned()), - originatingPrivileges(std::move(originatingPrivileges)) { - while (authenticatedUsersIter.more()) { - authenticatedUsers.emplace_back(authenticatedUsersIter.next()); - } - } + originatingPrivileges(std::move(originatingPrivileges)) {} void setTailableMode(TailableModeEnum newMode) { tailableMode = newMode; @@ -93,7 +90,7 @@ struct ClientCursorParams { std::unique_ptr<PlanExecutor, PlanExecutor::Deleter> exec; const NamespaceString nss; - std::vector<UserName> authenticatedUsers; + boost::optional<UserName> authenticatedUser; const APIParameters apiParameters; const WriteConcernOptions writeConcernOptions; const repl::ReadConcernArgs readConcernArgs; @@ -133,8 +130,8 @@ public: return _nss; } - UserNameIterator getAuthenticatedUsers() const { - return makeUserNameIterator(_authenticatedUsers.begin(), _authenticatedUsers.end()); + boost::optional<UserName> getAuthenticatedUser() const { + return _authenticatedUser; } boost::optional<LogicalSessionId> getSessionId() const { @@ -358,11 +355,11 @@ private: // have the correct partition of the CursorManager locked (just like _authenticatedUsers). const NamespaceString _nss; - // The set of authenticated users when this cursor was created. Threads may read from this + // The authenticated user when this cursor was created. Threads may read from this // field (using the getter) even if they don't have the cursor pinned as long as they hold the // correct partition's lock in the CursorManager. They must hold the lock to prevent the cursor // from being freed by another thread during the read. - const std::vector<UserName> _authenticatedUsers; + const boost::optional<UserName> _authenticatedUser; // A logical session id for this cursor, if it is running inside of a session. const boost::optional<LogicalSessionId> _lsid; diff --git a/src/mongo/db/commands/connection_status.cpp b/src/mongo/db/commands/connection_status.cpp index 1e688d2274b..903fc7fce38 100644 --- a/src/mongo/db/commands/connection_status.cpp +++ b/src/mongo/db/commands/connection_status.cpp @@ -50,7 +50,11 @@ public: auto* as = AuthorizationSession::get(opCtx->getClient()); ConnectionStatusReplyAuthInfo info; - info.setAuthenticatedUsers(iteratorToVector<UserName>(as->getAuthenticatedUserNames())); + std::vector<UserName> userNames; + if (auto userName = as->getAuthenticatedUserName()) { + userNames.push_back(std::move(userName.get())); + } + info.setAuthenticatedUsers(std::move(userNames)); info.setAuthenticatedUserRoles( iteratorToVector<RoleName>(as->getAuthenticatedRoleNames())); if (request().getShowPrivileges()) { @@ -77,9 +81,8 @@ public: // entries in the connection status output. User::ResourcePrivilegeMap unified; - for (auto nameIt = as->getAuthenticatedUserNames(); nameIt.more(); nameIt.next()) { - auto* authUser = as->lookupUser(*nameIt); - for (const auto& privIter : authUser->getPrivileges()) { + if (auto authUser = as->getAuthenticatedUser()) { + for (const auto& privIter : authUser.get()->getPrivileges()) { auto it = unified.find(privIter.first); if (it == unified.end()) { unified[privIter.first] = privIter.second; diff --git a/src/mongo/db/commands/create.idl b/src/mongo/db/commands/create.idl index 69747ea67f2..f45b6018ac2 100644 --- a/src/mongo/db/commands/create.idl +++ b/src/mongo/db/commands/create.idl @@ -61,7 +61,7 @@ commands: access_check: complex: - check: should_ignore_auth_checks - - check: get_single_user # Can be triggered by aggregation + - check: get_authenticated_user # Can be triggered by aggregation - check: lookup_user # Can be triggered by aggregation - privilege: resource_pattern: exact_namespace diff --git a/src/mongo/db/commands/end_sessions_command.cpp b/src/mongo/db/commands/end_sessions_command.cpp index b1d5ae4ca3f..3d11bc0d03e 100644 --- a/src/mongo/db/commands/end_sessions_command.cpp +++ b/src/mongo/db/commands/end_sessions_command.cpp @@ -83,9 +83,7 @@ public: void doCheckAuthorization(OperationContext* opCtx) const final { // It is always ok to run this command, as long as you are authenticated // as some user, if auth is enabled. - uassert(ErrorCodes::Unauthorized, - "Not authorized to run endSessions command", - AuthorizationSession::get(opCtx->getClient())->getSingleUser()); + // requiresAuth() => true covers this for us. } Reply typedRun(OperationContext* opCtx) final { diff --git a/src/mongo/db/commands/find_cmd.cpp b/src/mongo/db/commands/find_cmd.cpp index 9c46f14d1f1..ef292a00ead 100644 --- a/src/mongo/db/commands/find_cmd.cpp +++ b/src/mongo/db/commands/find_cmd.cpp @@ -686,7 +686,7 @@ public: opCtx, {std::move(exec), nss, - AuthorizationSession::get(opCtx->getClient())->getAuthenticatedUserNames(), + AuthorizationSession::get(opCtx->getClient())->getAuthenticatedUserName(), APIParameters::get(opCtx), opCtx->getWriteConcern(), repl::ReadConcernArgs::get(opCtx), diff --git a/src/mongo/db/commands/getmore_cmd.cpp b/src/mongo/db/commands/getmore_cmd.cpp index a344ab03278..6aed4fb7583 100644 --- a/src/mongo/db/commands/getmore_cmd.cpp +++ b/src/mongo/db/commands/getmore_cmd.cpp @@ -151,10 +151,8 @@ void validateTxnNumber(OperationContext* opCtx, int64_t cursorId, const ClientCu void validateAuthorization(const OperationContext* opCtx, const ClientCursor& cursor) { auto authzSession = AuthorizationSession::get(opCtx->getClient()); - // A user can only call getMore on their own cursor. If there were multiple users - // authenticated when the cursor was created, then at least one of them must be - // authenticated in order to run getMore on the cursor. - if (!authzSession->isCoauthorizedWith(cursor.getAuthenticatedUsers())) { + // A user can only call getMore on their own cursor. + if (!authzSession->isCoauthorizedWith(cursor.getAuthenticatedUser())) { uasserted(ErrorCodes::Unauthorized, str::stream() << "cursor id " << cursor.cursorid() << " was not created by the authenticated user"); diff --git a/src/mongo/db/commands/kill_op_cmd_base.cpp b/src/mongo/db/commands/kill_op_cmd_base.cpp index bace16861bc..a056e6cc514 100644 --- a/src/mongo/db/commands/kill_op_cmd_base.cpp +++ b/src/mongo/db/commands/kill_op_cmd_base.cpp @@ -53,8 +53,11 @@ void KillOpCmdBase::reportSuccessfulCompletion(OperationContext* opCtx, auto client = opCtx->getClient(); if (client) { if (AuthorizationManager::get(client->getServiceContext())->isAuthEnabled()) { - auto user = AuthorizationSession::get(client)->getAuthenticatedUserNames(); - attr.add("user", user->toBSON()); + if (auto user = AuthorizationSession::get(client)->getAuthenticatedUserName()) { + attr.add("user", BSON_ARRAY(user->toBSON())); + } else { + attr.add("user", BSONArray()); + } } if (client->session()) { diff --git a/src/mongo/db/commands/kill_sessions_command.cpp b/src/mongo/db/commands/kill_sessions_command.cpp index 80d26ef32e7..298446164f9 100644 --- a/src/mongo/db/commands/kill_sessions_command.cpp +++ b/src/mongo/db/commands/kill_sessions_command.cpp @@ -58,13 +58,10 @@ KillAllSessionsByPatternSet patternsForLoggedInUser(OperationContext* opCtx) { KillAllSessionsByPatternSet patterns; if (AuthorizationManager::get(serviceContext)->isAuthEnabled()) { - auto authzSession = AuthorizationSession::get(client); - for (auto iter = authzSession->getAuthenticatedUserNames(); iter.more(); iter.next()) { - User* user = authzSession->lookupUser(*iter); - invariant(user); - + auto* as = AuthorizationSession::get(client); + if (auto user = as->getAuthenticatedUser()) { auto item = makeKillAllSessionsByPattern(opCtx); - item.pattern.setUid(user->getDigest()); + item.pattern.setUid(user.get()->getDigest()); patterns.emplace(std::move(item)); } } else { diff --git a/src/mongo/db/commands/list_collections.cpp b/src/mongo/db/commands/list_collections.cpp index 92cf819bda2..28cf8bbb451 100644 --- a/src/mongo/db/commands/list_collections.cpp +++ b/src/mongo/db/commands/list_collections.cpp @@ -544,7 +544,7 @@ public: opCtx, {std::move(exec), cursorNss, - AuthorizationSession::get(opCtx->getClient())->getAuthenticatedUserNames(), + AuthorizationSession::get(opCtx->getClient())->getAuthenticatedUserName(), APIParameters::get(opCtx), opCtx->getWriteConcern(), repl::ReadConcernArgs::get(opCtx), diff --git a/src/mongo/db/commands/list_indexes.cpp b/src/mongo/db/commands/list_indexes.cpp index 4b77947e520..8281615d664 100644 --- a/src/mongo/db/commands/list_indexes.cpp +++ b/src/mongo/db/commands/list_indexes.cpp @@ -353,7 +353,7 @@ public: opCtx, {std::move(exec), nss, - AuthorizationSession::get(opCtx->getClient())->getAuthenticatedUserNames(), + AuthorizationSession::get(opCtx->getClient())->getAuthenticatedUserName(), APIParameters::get(opCtx), opCtx->getWriteConcern(), repl::ReadConcernArgs::get(opCtx), diff --git a/src/mongo/db/commands/refresh_sessions_command.cpp b/src/mongo/db/commands/refresh_sessions_command.cpp index a15eb760d08..209fef853c3 100644 --- a/src/mongo/db/commands/refresh_sessions_command.cpp +++ b/src/mongo/db/commands/refresh_sessions_command.cpp @@ -72,9 +72,7 @@ public: void doCheckAuthorization(OperationContext* opCtx) const final { // It is always ok to run this command, as long as you are authenticated // as some user, if auth is enabled. - uassert(ErrorCodes::Unauthorized, - "Not authorized to run refreshSessions command", - AuthorizationSession::get(opCtx->getClient())->getSingleUser()); + // requiresAuth() => true covers this for us. } Reply typedRun(OperationContext* opCtx) final { diff --git a/src/mongo/db/commands/run_aggregate.cpp b/src/mongo/db/commands/run_aggregate.cpp index 6330ebdfdf1..576ee9c3d39 100644 --- a/src/mongo/db/commands/run_aggregate.cpp +++ b/src/mongo/db/commands/run_aggregate.cpp @@ -989,7 +989,7 @@ Status runAggregate(OperationContext* opCtx, ClientCursorParams cursorParams( std::move(exec), origNss, - AuthorizationSession::get(opCtx->getClient())->getAuthenticatedUserNames(), + AuthorizationSession::get(opCtx->getClient())->getAuthenticatedUserName(), APIParameters::get(opCtx), opCtx->getWriteConcern(), repl::ReadConcernArgs::get(opCtx), diff --git a/src/mongo/db/commands/sessions_commands.idl b/src/mongo/db/commands/sessions_commands.idl index c5d315b31f3..d9528ad6908 100644 --- a/src/mongo/db/commands/sessions_commands.idl +++ b/src/mongo/db/commands/sessions_commands.idl @@ -46,7 +46,7 @@ commands: reply_type: OkReply access_check: complex: - - check: get_single_user + - check: get_authenticated_user - check: lookup_user refreshSessionsFromClient: @@ -60,5 +60,5 @@ commands: reply_type: OkReply access_check: complex: - - check: get_single_user + - check: get_authenticated_user - check: lookup_user diff --git a/src/mongo/db/curop.cpp b/src/mongo/db/curop.cpp index 17953399f68..08158554ad9 100644 --- a/src/mongo/db/curop.cpp +++ b/src/mongo/db/curop.cpp @@ -204,10 +204,7 @@ void CurOp::reportCurrentOpForClient(OperationContext* opCtx, const auto serializeAuthenticatedUsers = [&](StringData name) { if (authSession->isAuthenticated()) { BSONArrayBuilder users(infoBuilder->subarrayStart(name)); - for (auto userIt = authSession->getAuthenticatedUserNames(); userIt.more(); - userIt.next()) { - userIt->serializeToBSON(&users); - } + authSession->getAuthenticatedUserName()->serializeToBSON(&users); } }; @@ -1109,28 +1106,16 @@ void OpDebug::append(OperationContext* opCtx, void OpDebug::appendUserInfo(const CurOp& c, BSONObjBuilder& builder, AuthorizationSession* authSession) { - UserNameIterator nameIter = authSession->getAuthenticatedUserNames(); - - UserName bestUser; - if (nameIter.more()) - bestUser = *nameIter; - std::string opdb(nsToDatabase(c.getNS())); BSONArrayBuilder allUsers(builder.subarrayStart("allUsers")); - for (; nameIter.more(); nameIter.next()) { - BSONObjBuilder nextUser(allUsers.subobjStart()); - nextUser.append(AuthorizationManager::USER_NAME_FIELD_NAME, nameIter->getUser()); - nextUser.append(AuthorizationManager::USER_DB_FIELD_NAME, nameIter->getDB()); - nextUser.doneFast(); - - if (nameIter->getDB() == opdb) { - bestUser = *nameIter; - } + auto name = authSession->getAuthenticatedUserName(); + if (name) { + name->serializeToBSON(&allUsers); } allUsers.doneFast(); - builder.append("user", bestUser.getUser().empty() ? "" : bestUser.getDisplayName()); + builder.append("user", name ? name->getDisplayName() : ""); } std::function<BSONObj(ProfileFilter::Args)> OpDebug::appendStaged(StringSet requestedFields, diff --git a/src/mongo/db/cursor_manager.cpp b/src/mongo/db/cursor_manager.cpp index ea2ce3f969d..8d09c6811fb 100644 --- a/src/mongo/db/cursor_manager.cpp +++ b/src/mongo/db/cursor_manager.cpp @@ -324,7 +324,7 @@ std::vector<GenericCursor> CursorManager::getIdleCursors( // Exclude cursors that this user does not own if auth is enabled. if (ctxAuth->getAuthorizationManager().isAuthEnabled() && userMode == MongoProcessInterface::CurrentOpUserMode::kExcludeOthers && - !ctxAuth->isCoauthorizedWith(cursor->getAuthenticatedUsers())) { + !ctxAuth->isCoauthorizedWith(cursor->getAuthenticatedUser())) { continue; } // Exclude pinned cursors. @@ -479,7 +479,7 @@ Status CursorManager::checkAuthForKillCursors(OperationContext* opCtx, CursorId // after the cursor's creation. We're guaranteed that the cursor won't get destroyed while we're // reading from it because we hold the partition's lock. AuthorizationSession* as = AuthorizationSession::get(opCtx->getClient()); - return auth::checkAuthForKillCursors(as, cursor->nss(), cursor->getAuthenticatedUsers()); + return auth::checkAuthForKillCursors(as, cursor->nss(), cursor->getAuthenticatedUser()); } } // namespace mongo diff --git a/src/mongo/db/dollar_tenant_decoration_test.cpp b/src/mongo/db/dollar_tenant_decoration_test.cpp index c19a3415139..391250a1791 100644 --- a/src/mongo/db/dollar_tenant_decoration_test.cpp +++ b/src/mongo/db/dollar_tenant_decoration_test.cpp @@ -57,7 +57,11 @@ public: {Privilege(ResourcePattern::forClusterResource(), ActionType::useTenant)}); auto* as = dynamic_cast<AuthorizationSessionImpl*>(AuthorizationSession::get(opCtx->getClient())); - as->_authenticatedUsers.add(std::move(user)); + if (as->_authenticatedUser != boost::none) { + as->logoutAllDatabases(opCtx->getClient(), "AuthorizationSessionImplTestHelper"_sd); + } + as->_authenticatedUser = std::move(user); + as->_authenticationMode = AuthorizationSession::AuthenticationMode::kConnection; as->_updateInternalAuthorizationState(); } }; diff --git a/src/mongo/db/exec/js_function.cpp b/src/mongo/db/exec/js_function.cpp index e618ad0d352..339a7192d25 100644 --- a/src/mongo/db/exec/js_function.cpp +++ b/src/mongo/db/exec/js_function.cpp @@ -44,12 +44,12 @@ std::string getAuthenticatedUserNamesToken(Client* client) { StringBuilder sb; auto as = AuthorizationSession::get(client); - for (auto nameIter = as->getAuthenticatedUserNames(); nameIter.more(); nameIter.next()) { + if (auto name = as->getAuthenticatedUserName()) { // Using a NUL byte which isn't valid in usernames to separate them. - if (const auto& tenant = nameIter->getTenant()) { + if (const auto& tenant = name->getTenant()) { sb << '\0' << tenant->toString(); } - sb << '\0' << nameIter->getUnambiguousName(); + sb << '\0' << name->getUnambiguousName(); } return sb.str(); diff --git a/src/mongo/db/index_builds_coordinator_mongod.cpp b/src/mongo/db/index_builds_coordinator_mongod.cpp index a99c5f05173..fb309720f22 100644 --- a/src/mongo/db/index_builds_coordinator_mongod.cpp +++ b/src/mongo/db/index_builds_coordinator_mongod.cpp @@ -370,7 +370,7 @@ IndexBuildsCoordinatorMongod::_startIndexBuild(OperationContext* opCtx, // Load the external client's attributes into this thread's client for auditing. auto authSession = AuthorizationSession::get(opCtx->getClient()); if (authSession) { - authSession->setImpersonatedUserData(std::move(impersonatedClientAttrs.userNames), + authSession->setImpersonatedUserData(std::move(impersonatedClientAttrs.userName), std::move(impersonatedClientAttrs.roleNames)); } diff --git a/src/mongo/db/introspect.cpp b/src/mongo/db/introspect.cpp index 7d7da8040ae..ac00155ad77 100644 --- a/src/mongo/db/introspect.cpp +++ b/src/mongo/db/introspect.cpp @@ -35,7 +35,6 @@ #include "mongo/bson/util/builder.h" #include "mongo/db/auth/authorization_manager.h" #include "mongo/db/auth/authorization_session.h" -#include "mongo/db/auth/user_set.h" #include "mongo/db/catalog/collection.h" #include "mongo/db/client.h" #include "mongo/db/concurrency/exception_util.h" diff --git a/src/mongo/db/kill_sessions.cpp b/src/mongo/db/kill_sessions.cpp index b167e14c156..0d830500154 100644 --- a/src/mongo/db/kill_sessions.cpp +++ b/src/mongo/db/kill_sessions.cpp @@ -42,14 +42,14 @@ namespace mongo { namespace { std::vector<KillAllSessionsUser> getKillAllSessionsImpersonateUsers(OperationContext* opCtx) { - AuthorizationSession* authSession = AuthorizationSession::get(opCtx->getClient()); + auto* as = AuthorizationSession::get(opCtx->getClient()); std::vector<KillAllSessionsUser> out; - for (auto iter = authSession->getAuthenticatedUserNames(); iter.more(); iter.next()) { + if (auto name = as->getAuthenticatedUserName()) { out.emplace_back(); - out.back().setUser(iter->getUser()); - out.back().setDb(iter->getDB()); + out.back().setUser(name->getUser()); + out.back().setDb(name->getDB()); } return out; @@ -71,19 +71,19 @@ std::vector<KillAllSessionsRole> getKillAllSessionsImpersonateRoles(OperationCon } // namespace -std::tuple<std::vector<UserName>, std::vector<RoleName>> getKillAllSessionsByPatternImpersonateData( - const KillAllSessionsByPattern& pattern) { - std::tuple<std::vector<UserName>, std::vector<RoleName>> out; +std::tuple<boost::optional<UserName>, std::vector<RoleName>> +getKillAllSessionsByPatternImpersonateData(const KillAllSessionsByPattern& pattern) { + std::tuple<boost::optional<UserName>, std::vector<RoleName>> out; - auto& users = std::get<0>(out); + auto& user = std::get<0>(out); auto& roles = std::get<1>(out); - if (pattern.getUsers()) { - users.reserve(pattern.getUsers()->size()); - - for (auto&& user : pattern.getUsers().get()) { - users.emplace_back(user.getUser(), user.getDb()); - } + if (pattern.getUsers() && (pattern.getUsers()->size() > 0)) { + uassert(ErrorCodes::BadValue, + "Too many users in impersonation data", + pattern.getUsers()->size() <= 1); + const auto& impUser = pattern.getUsers().get()[0]; + user = UserName(impUser.getUser(), impUser.getDb()); } if (pattern.getRoles()) { @@ -119,16 +119,14 @@ KillAllSessionsByPatternItem makeKillAllSessionsByPattern(OperationContext* opCt } KillAllSessionsByPatternSet makeSessionFilterForAuthenticatedUsers(OperationContext* opCtx) { - AuthorizationSession* authSession = AuthorizationSession::get(opCtx->getClient()); + auto* as = AuthorizationSession::get(opCtx->getClient()); KillAllSessionsByPatternSet patterns; - for (auto it = authSession->getAuthenticatedUserNames(); it.more(); it.next()) { - if (auto user = authSession->lookupUser(*it)) { - KillAllSessionsByPattern pattern; - pattern.setUid(user->getDigest()); - KillAllSessionsByPatternItem item{std::move(pattern), APIParameters::get(opCtx)}; - patterns.emplace(std::move(item)); - } + if (auto user = as->getAuthenticatedUser()) { + KillAllSessionsByPattern pattern; + pattern.setUid(user.get()->getDigest()); + KillAllSessionsByPatternItem item{std::move(pattern), APIParameters::get(opCtx)}; + patterns.emplace(std::move(item)); } return patterns; } diff --git a/src/mongo/db/kill_sessions.h b/src/mongo/db/kill_sessions.h index 31ceeed9375..c666388208b 100644 --- a/src/mongo/db/kill_sessions.h +++ b/src/mongo/db/kill_sessions.h @@ -84,8 +84,8 @@ inline bool operator!=(const KillAllSessionsByPatternItem& lhs, using KillAllSessionsByPatternSet = stdx::unordered_set<KillAllSessionsByPatternItem, KillAllSessionsByPatternItemHash>; -std::tuple<std::vector<UserName>, std::vector<RoleName>> getKillAllSessionsByPatternImpersonateData( - const KillAllSessionsByPattern& pattern); +std::tuple<boost::optional<UserName>, std::vector<RoleName>> +getKillAllSessionsByPatternImpersonateData(const KillAllSessionsByPattern& pattern); /** * Note: All three of the below makeKillAllSessionsByPattern helpers take opCtx to inline the diff --git a/src/mongo/db/kill_sessions_common.cpp b/src/mongo/db/kill_sessions_common.cpp index a26c3c9fd5e..90a92c5d9b6 100644 --- a/src/mongo/db/kill_sessions_common.cpp +++ b/src/mongo/db/kill_sessions_common.cpp @@ -102,8 +102,11 @@ void killSessionsReport(OperationContext* opCtx, const BSONObj& cmdObj) { auto client = opCtx->getClient(); if (client) { if (AuthorizationManager::get(client->getServiceContext())->isAuthEnabled()) { - auto user = AuthorizationSession::get(client)->getAuthenticatedUserNames(); - attr.add("user", user->toBSON()); + if (auto user = AuthorizationSession::get(client)->getAuthenticatedUserName()) { + attr.add("user", BSON_ARRAY(user->toBSON())); + } else { + attr.add("user", BSONArray()); + } } if (client->session()) { diff --git a/src/mongo/db/kill_sessions_common.h b/src/mongo/db/kill_sessions_common.h index 44fb96857e9..d74dda981a1 100644 --- a/src/mongo/db/kill_sessions_common.h +++ b/src/mongo/db/kill_sessions_common.h @@ -68,13 +68,13 @@ public: AuthorizationSession* authSession = AuthorizationSession::get(opCtx->getClient()); if (pattern.getUsers() && pattern.getRoles()) { - std::tie(_names, _roles) = getKillAllSessionsByPatternImpersonateData(pattern); - _raii.emplace(authSession, &_names, &_roles); + std::tie(_name, _roles) = getKillAllSessionsByPatternImpersonateData(pattern); + _raii.emplace(authSession, &_name, &_roles); } } private: - std::vector<UserName> _names; + boost::optional<UserName> _name; std::vector<RoleName> _roles; boost::optional<AuthorizationSession::ScopedImpersonate> _raii; }; diff --git a/src/mongo/db/logical_session_id_helpers.cpp b/src/mongo/db/logical_session_id_helpers.cpp index 0f312f0d30f..035b54b061d 100644 --- a/src/mongo/db/logical_session_id_helpers.cpp +++ b/src/mongo/db/logical_session_id_helpers.cpp @@ -39,6 +39,23 @@ #include "mongo/db/operation_context.h" namespace mongo { +namespace { +/** + * If auth is not enabled, will return boost::none. + * Otherwise, a user must be actively authenticated on the client, + * and a handle to this user will be returned. + */ +boost::optional<UserHandle> getAuthenticatedUser(Client* client) { + if (!AuthorizationManager::get(client->getServiceContext())->isAuthEnabled()) { + return boost::none; + } + + auto optUser = AuthorizationSession::get(client)->getAuthenticatedUser(); + uassert(ErrorCodes::Unauthorized, "Logical sessions require authentication", optUser); + + return optUser.get(); +} +} // namespace /** * This is a safe hash that will not collide with a username because all full usernames include an @@ -47,20 +64,12 @@ namespace mongo { const auto kNoAuthDigest = SHA256Block::computeHash(reinterpret_cast<const uint8_t*>(""), 0); SHA256Block getLogicalSessionUserDigestForLoggedInUser(const OperationContext* opCtx) { - auto client = opCtx->getClient(); - ServiceContext* serviceContext = client->getServiceContext(); - - if (AuthorizationManager::get(serviceContext)->isAuthEnabled()) { - UserName userName; - - const auto user = AuthorizationSession::get(client)->getSingleUser(); - invariant(user); - + if (auto user = getAuthenticatedUser(opCtx->getClient())) { uassert(ErrorCodes::BadValue, "Username too long to use with logical sessions", - user->getName().getDisplayNameLength() < kMaximumUserNameLengthForLogicalSessions); - - return user->getDigest(); + user.get()->getName().getDisplayNameLength() < + kMaximumUserNameLengthForLogicalSessions); + return user.get()->getDigest(); } else { return kNoAuthDigest; } @@ -172,14 +181,9 @@ LogicalSessionRecord makeLogicalSessionRecord(OperationContext* opCtx, Date_t la LogicalSessionId id{}; LogicalSessionRecord lsr{}; - auto client = opCtx->getClient(); - ServiceContext* serviceContext = client->getServiceContext(); - if (AuthorizationManager::get(serviceContext)->isAuthEnabled()) { - auto user = AuthorizationSession::get(client)->getSingleUser(); - invariant(user); - - id.setUid(user->getDigest()); - lsr.setUser(StringData(user->getName().getDisplayName())); + if (auto user = getAuthenticatedUser(opCtx->getClient())) { + id.setUid(user.get()->getDigest()); + lsr.setUser(StringData(user.get()->getName().getDisplayName())); } else { id.setUid(kNoAuthDigest); } @@ -210,14 +214,9 @@ LogicalSessionRecord makeLogicalSessionRecord(OperationContext* opCtx, Date_t lastUse) { auto lsr = makeLogicalSessionRecord(lsid, lastUse); - auto client = opCtx->getClient(); - ServiceContext* serviceContext = client->getServiceContext(); - if (AuthorizationManager::get(serviceContext)->isAuthEnabled()) { - auto user = AuthorizationSession::get(client)->getSingleUser(); - invariant(user); - - if (user->getDigest() == lsid.getUid()) { - lsr.setUser(StringData(user->getName().getDisplayName())); + if (auto user = getAuthenticatedUser(opCtx->getClient())) { + if (user.get()->getDigest() == lsid.getUid()) { + lsr.setUser(StringData(user.get()->getName().getDisplayName())); } } diff --git a/src/mongo/db/pipeline/aggregate_command.idl b/src/mongo/db/pipeline/aggregate_command.idl index 03637da1041..cff03899e4f 100644 --- a/src/mongo/db/pipeline/aggregate_command.idl +++ b/src/mongo/db/pipeline/aggregate_command.idl @@ -86,7 +86,7 @@ commands: - check: is_coauthorized_with_client - check: is_coauthorized_with - check: should_ignore_auth_checks - - check: get_single_user + - check: get_authenticated_user - check: lookup_user - privilege: # many commands resource_pattern: exact_namespace diff --git a/src/mongo/db/pipeline/document_source_list_local_sessions.cpp b/src/mongo/db/pipeline/document_source_list_local_sessions.cpp index 6be5ca875a9..426a8d3bacb 100644 --- a/src/mongo/db/pipeline/document_source_list_local_sessions.cpp +++ b/src/mongo/db/pipeline/document_source_list_local_sessions.cpp @@ -88,9 +88,10 @@ ListSessionsUser getUserNameForLoggedInUser(const OperationContext* opCtx) { ListSessionsUser user; if (AuthorizationManager::get(client->getServiceContext())->isAuthEnabled()) { - const auto& userName = AuthorizationSession::get(client)->getSingleUser()->getName(); - user.setUser(userName.getUser()); - user.setDb(userName.getDB()); + const auto& userName = AuthorizationSession::get(client)->getAuthenticatedUserName(); + uassert(ErrorCodes::Unauthorized, "There is no user authenticated", userName); + user.setUser(userName->getUser()); + user.setDb(userName->getDB()); } else { user.setUser(""); user.setDb(""); diff --git a/src/mongo/db/s/forwardable_operation_metadata.cpp b/src/mongo/db/s/forwardable_operation_metadata.cpp index d3ca368cfb8..1a83a2eb1f1 100644 --- a/src/mongo/db/s/forwardable_operation_metadata.cpp +++ b/src/mongo/db/s/forwardable_operation_metadata.cpp @@ -62,8 +62,10 @@ void ForwardableOperationMetadata::setOn(OperationContext* opCtx) const { if (const auto& optAuthMetadata = getImpersonatedUserMetadata()) { const auto& authMetadata = optAuthMetadata.get(); - if (!authMetadata.getUsers().empty() || !authMetadata.getRoles().empty()) { - AuthorizationSession::get(client)->setImpersonatedUserData(authMetadata.getUsers(), + const auto& users = authMetadata.getUsers(); + if (!users.empty() || !authMetadata.getRoles().empty()) { + fassert(ErrorCodes::InternalError, users.size() == 1); + AuthorizationSession::get(client)->setImpersonatedUserData(users[0], authMetadata.getRoles()); } } diff --git a/src/mongo/embedded/embedded_auth_session.cpp b/src/mongo/embedded/embedded_auth_session.cpp index 9c9e2899c24..d086a0a3309 100644 --- a/src/mongo/embedded/embedded_auth_session.cpp +++ b/src/mongo/embedded/embedded_auth_session.cpp @@ -78,7 +78,7 @@ public: UASSERT_NOT_IMPLEMENTED; } - User* getSingleUser() override { + boost::optional<UserHandle> getAuthenticatedUser() override { UASSERT_NOT_IMPLEMENTED; } @@ -92,8 +92,8 @@ public: return false; } - UserNameIterator getAuthenticatedUserNames() override { - return UserNameIterator(std::make_unique<Impl>()); + boost::optional<UserName> getAuthenticatedUserName() override { + return boost::none; } RoleNameIterator getAuthenticatedRoleNames() override { @@ -169,12 +169,11 @@ public: return true; } - void setImpersonatedUserData(const std::vector<UserName>&, - const std::vector<RoleName>&) override { + void setImpersonatedUserData(const UserName&, const std::vector<RoleName>&) override { UASSERT_NOT_IMPLEMENTED; } - UserNameIterator getImpersonatedUserNames() override { + boost::optional<UserName> getImpersonatedUserName() override { UASSERT_NOT_IMPLEMENTED; } @@ -190,7 +189,7 @@ public: return true; } - bool isCoauthorizedWith(UserNameIterator) override { + bool isCoauthorizedWith(const boost::optional<UserName>&) override { return true; } @@ -228,7 +227,7 @@ public: } protected: - std::tuple<std::vector<UserName>*, std::vector<RoleName>*> _getImpersonations() override { + std::tuple<boost::optional<UserName>*, std::vector<RoleName>*> _getImpersonations() override { UASSERT_NOT_IMPLEMENTED; } diff --git a/src/mongo/rpc/metadata/impersonated_user_metadata.cpp b/src/mongo/rpc/metadata/impersonated_user_metadata.cpp index 64b78931f39..63d0d4f7837 100644 --- a/src/mongo/rpc/metadata/impersonated_user_metadata.cpp +++ b/src/mongo/rpc/metadata/impersonated_user_metadata.cpp @@ -58,6 +58,10 @@ void readImpersonatedUserMetadata(const BSONElement& elem, OperationContext* opC IDLParserErrorContext errCtx(kImpersonationMetadataSectionName); auto data = ImpersonatedUserMetadata::parse(errCtx, elem.embeddedObject()); + uassert(ErrorCodes::BadValue, + "Impersonating multiple users is not supported", + data.getUsers().size() <= 1); + // Set the impersonation data only if there are actually impersonated // users/roles. if ((!data.getUsers().empty()) || (!data.getRoles().empty())) { @@ -75,20 +79,24 @@ void writeAuthDataToImpersonatedUserMetadata(OperationContext* opCtx, BSONObjBui // Otherwise construct a metadata section from the list of authenticated users/roles auto authSession = AuthorizationSession::get(opCtx->getClient()); - auto userNames = authSession->getImpersonatedUserNames(); + auto userName = authSession->getImpersonatedUserName(); auto roleNames = authSession->getImpersonatedRoleNames(); - if (!userNames.more() && !roleNames.more()) { - userNames = authSession->getAuthenticatedUserNames(); + if (!userName && !roleNames.more()) { + userName = authSession->getAuthenticatedUserName(); roleNames = authSession->getAuthenticatedRoleNames(); } // If there are no users/roles being impersonated just exit - if (!userNames.more() && !roleNames.more()) { + if (!userName && !roleNames.more()) { return; } ImpersonatedUserMetadata metadata; - metadata.setUsers(userNameIteratorToContainer<std::vector<UserName>>(userNames)); + if (userName) { + metadata.setUsers({userName.get()}); + } else { + metadata.setUsers({}); + } metadata.setRoles(roleNameIteratorToContainer<std::vector<RoleName>>(roleNames)); BSONObjBuilder section(out->subobjStart(kImpersonationMetadataSectionName)); diff --git a/src/mongo/s/commands/cluster_killcursors_cmd.cpp b/src/mongo/s/commands/cluster_killcursors_cmd.cpp index 716107f43d1..31faadc80f2 100644 --- a/src/mongo/s/commands/cluster_killcursors_cmd.cpp +++ b/src/mongo/s/commands/cluster_killcursors_cmd.cpp @@ -45,8 +45,9 @@ struct ClusterKillCursorsCmd { const NamespaceString& nss, CursorId cursorId) { auto const authzSession = AuthorizationSession::get(opCtx->getClient()); - auto authChecker = [&authzSession, &nss](UserNameIterator userNames) -> Status { - return auth::checkAuthForKillCursors(authzSession, nss, userNames); + auto authChecker = [&authzSession, + &nss](const boost::optional<UserName>& userName) -> Status { + return auth::checkAuthForKillCursors(authzSession, nss, userName); }; return Grid::get(opCtx)->getCursorManager()->checkAuthForKillCursors( diff --git a/src/mongo/s/commands/cluster_list_collections_cmd.cpp b/src/mongo/s/commands/cluster_list_collections_cmd.cpp index 511a086a4a8..8b55ddca0a8 100644 --- a/src/mongo/s/commands/cluster_list_collections_cmd.cpp +++ b/src/mongo/s/commands/cluster_list_collections_cmd.cpp @@ -127,10 +127,8 @@ BSONObj rewriteCommandForListingOwnCollections(OperationContext* opCtx, // Compute the set of collection names which would be permissible to return. std::set<std::string> collectionNames; - for (UserNameIterator nameIter = authzSession->getAuthenticatedUserNames(); nameIter.more(); - nameIter.next()) { - User* authUser = authzSession->lookupUser(*nameIter); - for (const auto& [resource, privilege] : authUser->getPrivileges()) { + if (auto authUser = authzSession->getAuthenticatedUser()) { + for (const auto& [resource, privilege] : authUser.get()->getPrivileges()) { if (resource.isCollectionPattern() || (resource.isExactNamespacePattern() && resource.databaseToMatch() == dbName)) { collectionNames.emplace(resource.collectionToMatch().toString()); diff --git a/src/mongo/s/query/cluster_aggregation_planner.cpp b/src/mongo/s/query/cluster_aggregation_planner.cpp index dc044f72309..0c80b838544 100644 --- a/src/mongo/s/query/cluster_aggregation_planner.cpp +++ b/src/mongo/s/query/cluster_aggregation_planner.cpp @@ -344,14 +344,14 @@ BSONObj establishMergingMongosCursor(OperationContext* opCtx, CursorId clusterCursorId = 0; if (cursorState == ClusterCursorManager::CursorState::NotExhausted) { - auto authUsers = AuthorizationSession::get(opCtx->getClient())->getAuthenticatedUserNames(); + auto authUser = AuthorizationSession::get(opCtx->getClient())->getAuthenticatedUserName(); clusterCursorId = uassertStatusOK(Grid::get(opCtx)->getCursorManager()->registerCursor( opCtx, ccc.releaseCursor(), requestedNss, ClusterCursorManager::CursorType::MultiTarget, ClusterCursorManager::CursorLifetime::Mortal, - authUsers)); + authUser)); } // Fill out the aggregation metrics in CurOp. diff --git a/src/mongo/s/query/cluster_cursor_manager.cpp b/src/mongo/s/query/cluster_cursor_manager.cpp index 5eaa7e05306..3361c1dace5 100644 --- a/src/mongo/s/query/cluster_cursor_manager.cpp +++ b/src/mongo/s/query/cluster_cursor_manager.cpp @@ -162,7 +162,7 @@ StatusWith<CursorId> ClusterCursorManager::registerCursor( const NamespaceString& nss, CursorType cursorType, CursorLifetime cursorLifetime, - UserNameIterator authenticatedUsers) { + const boost::optional<UserName>& authenticatedUser) { // Read the clock out of the lock. const auto now = _clockSource->now(); @@ -188,7 +188,7 @@ StatusWith<CursorId> ClusterCursorManager::registerCursor( cursorType, cursorLifetime, now, - authenticatedUsers, + authenticatedUser, opCtx->getClient()->getUUID(), opCtx->getOperationKey(), nss)); @@ -216,7 +216,7 @@ StatusWith<ClusterCursorManager::PinnedCursor> ClusterCursorManager::checkOutCur } // Check if the user is coauthorized to access this cursor. - auto authCheckStatus = authChecker(entry->getAuthenticatedUsers()); + auto authCheckStatus = authChecker(entry->getAuthenticatedUser()); if (!authCheckStatus.isOK()) { return authCheckStatus.withContext(str::stream() << "cursor id " << cursorId @@ -296,9 +296,9 @@ Status ClusterCursorManager::checkAuthForKillCursors(OperationContext* opCtx, return cursorNotFoundStatus(cursorId); } - // Note that getAuthenticatedUsers() is thread-safe, so it's okay to call even if there's + // Note that getAuthenticatedUser() is thread-safe, so it's okay to call even if there's // an operation using the cursor. - return authChecker(entry->getAuthenticatedUsers()); + return authChecker(entry->getAuthenticatedUser()); } void ClusterCursorManager::killOperationUsingCursor(WithLock, CursorEntry* entry) { @@ -497,7 +497,7 @@ std::vector<GenericCursor> ClusterCursorManager::getIdleCursors( // permission to see this cursor. if (ctxAuth->getAuthorizationManager().isAuthEnabled() && userMode == MongoProcessInterface::CurrentOpUserMode::kExcludeOthers && - !ctxAuth->isCoauthorizedWith(entry.getAuthenticatedUsers())) { + !ctxAuth->isCoauthorizedWith(entry.getAuthenticatedUser())) { continue; } if (entry.isKillPending() || entry.getOperationUsingCursor()) { diff --git a/src/mongo/s/query/cluster_cursor_manager.h b/src/mongo/s/query/cluster_cursor_manager.h index be10b0d60bd..730ccc40624 100644 --- a/src/mongo/s/query/cluster_cursor_manager.h +++ b/src/mongo/s/query/cluster_cursor_manager.h @@ -117,7 +117,7 @@ public: // Represents a function that may be passed into a ClusterCursorManager method which checks // whether the current client is authorized to perform the operation in question. The function // will be passed the list of users authorized to use the cursor. - using AuthzCheckFn = std::function<Status(UserNameIterator)>; + using AuthzCheckFn = std::function<Status(const boost::optional<UserName>&)>; /** * PinnedCursor is a moveable, non-copyable class representing ownership of a cursor that has @@ -226,7 +226,7 @@ public: CursorType cursorType, CursorLifetime cursorLifetime, Date_t lastActive, - UserNameIterator authenticatedUsersIter, + boost::optional<UserName> authenticatedUser, UUID clientUUID, boost::optional<OperationKey> opKey, NamespaceString nss) @@ -238,8 +238,7 @@ public: _opKey(std::move(opKey)), _nss(std::move(nss)), _originatingClient(std::move(clientUUID)), - _authenticatedUsers( - userNameIteratorToContainer<std::vector<UserName>>(authenticatedUsersIter)) { + _authenticatedUser(std::move(authenticatedUser)) { invariant(_cursor); } @@ -330,8 +329,8 @@ public: _lastActive = lastActive; } - UserNameIterator getAuthenticatedUsers() const { - return makeUserNameIterator(_authenticatedUsers.begin(), _authenticatedUsers.end()); + const boost::optional<UserName>& getAuthenticatedUser() const { + return _authenticatedUser; } const UUID& originatingClientUuid() const { @@ -365,7 +364,7 @@ public: /** * The set of users authorized to use this cursor. */ - std::vector<UserName> _authenticatedUsers; + boost::optional<UserName> _authenticatedUser; }; /** @@ -408,7 +407,7 @@ public: const NamespaceString& nss, CursorType cursorType, CursorLifetime cursorLifetime, - UserNameIterator authenticatedUsers); + const boost::optional<UserName>& authenticatedUser); /** * Moves the given cursor to the 'pinned' state, and transfers ownership of the cursor to the diff --git a/src/mongo/s/query/cluster_cursor_manager_test.cpp b/src/mongo/s/query/cluster_cursor_manager_test.cpp index 0176fe23947..58cd6bcdb02 100644 --- a/src/mongo/s/query/cluster_cursor_manager_test.cpp +++ b/src/mongo/s/query/cluster_cursor_manager_test.cpp @@ -60,11 +60,11 @@ protected: _manager.shutdown(_opCtx.get()); } - static Status successAuthChecker(UserNameIterator userNames) { + static Status successAuthChecker(const boost::optional<UserName>&) { return Status::OK(); }; - static Status failAuthChecker(UserNameIterator userNames) { + static Status failAuthChecker(const boost::optional<UserName>&) { return {ErrorCodes::Unauthorized, "Unauthorized"}; }; @@ -147,7 +147,7 @@ TEST_F(ClusterCursorManagerTest, RegisterCursor) { nss, ClusterCursorManager::CursorType::SingleTarget, ClusterCursorManager::CursorLifetime::Mortal, - UserNameIterator())); + boost::none)); auto pinnedCursor = getManager()->checkOutCursor(cursorId, getOperationContext(), successAuthChecker); ASSERT_OK(pinnedCursor.getStatus()); @@ -168,7 +168,7 @@ TEST_F(ClusterCursorManagerTest, RegisterCursorReturnsNonZeroId) { nss, ClusterCursorManager::CursorType::SingleTarget, ClusterCursorManager::CursorLifetime::Mortal, - UserNameIterator())); + boost::none)); ASSERT_NE(0, cursorId); } @@ -182,7 +182,7 @@ TEST_F(ClusterCursorManagerTest, CheckOutCursorBasic) { nss, ClusterCursorManager::CursorType::SingleTarget, ClusterCursorManager::CursorLifetime::Mortal, - UserNameIterator())); + boost::none)); auto checkedOutCursor = getManager()->checkOutCursor(cursorId, getOperationContext(), successAuthChecker); ASSERT_OK(checkedOutCursor.getStatus()); @@ -210,7 +210,7 @@ TEST_F(ClusterCursorManagerTest, CheckOutCursorMultipleCursors) { nss, ClusterCursorManager::CursorType::SingleTarget, ClusterCursorManager::CursorLifetime::Mortal, - UserNameIterator())); + boost::none)); } for (int i = 0; i < numCursors; ++i) { auto pinnedCursor = @@ -234,7 +234,7 @@ TEST_F(ClusterCursorManagerTest, CheckOutCursorPinned) { nss, ClusterCursorManager::CursorType::SingleTarget, ClusterCursorManager::CursorLifetime::Mortal, - UserNameIterator())); + boost::none)); auto pinnedCursor = getManager()->checkOutCursor(cursorId, getOperationContext(), successAuthChecker); ASSERT_OK(pinnedCursor.getStatus()); @@ -252,7 +252,7 @@ TEST_F(ClusterCursorManagerTest, CheckOutCursorKilled) { nss, ClusterCursorManager::CursorType::SingleTarget, ClusterCursorManager::CursorLifetime::Mortal, - UserNameIterator())); + boost::none)); killCursorFromDifferentOpCtx(cursorId); ASSERT_EQ(ErrorCodes::CursorNotFound, getManager() @@ -275,7 +275,7 @@ TEST_F(ClusterCursorManagerTest, CheckOutCursorWrongCursorId) { nss, ClusterCursorManager::CursorType::SingleTarget, ClusterCursorManager::CursorLifetime::Mortal, - UserNameIterator())); + boost::none)); ASSERT_EQ(ErrorCodes::CursorNotFound, getManager() ->checkOutCursor(cursorId + 1, getOperationContext(), successAuthChecker) @@ -291,7 +291,7 @@ TEST_F(ClusterCursorManagerTest, CheckOutCursorUpdateActiveTime) { nss, ClusterCursorManager::CursorType::SingleTarget, ClusterCursorManager::CursorLifetime::Mortal, - UserNameIterator())); + boost::none)); Date_t cursorRegistrationTime = getClockSource()->now(); getClockSource()->advance(Milliseconds(1)); auto checkedOutCursor = @@ -309,7 +309,7 @@ TEST_F(ClusterCursorManagerTest, CheckOutCursorAuthFails) { nss, ClusterCursorManager::CursorType::SingleTarget, ClusterCursorManager::CursorLifetime::Mortal, - UserNameIterator())); + boost::none)); auto checkedOutCursor = getManager()->checkOutCursor(cursorId, getOperationContext(), failAuthChecker); ASSERT_EQ(checkedOutCursor.getStatus(), ErrorCodes::Unauthorized); @@ -325,7 +325,7 @@ TEST_F(ClusterCursorManagerTest, ReturnCursorUpdateActiveTime) { nss, ClusterCursorManager::CursorType::SingleTarget, ClusterCursorManager::CursorLifetime::Mortal, - UserNameIterator())); + boost::none)); Date_t cursorCheckOutTime = getClockSource()->now(); auto checkedOutCursor = getManager()->checkOutCursor(cursorId, getOperationContext(), successAuthChecker); @@ -344,7 +344,7 @@ TEST_F(ClusterCursorManagerTest, KillUnpinnedCursorBasic) { nss, ClusterCursorManager::CursorType::SingleTarget, ClusterCursorManager::CursorLifetime::Mortal, - UserNameIterator())); + boost::none)); killCursorFromDifferentOpCtx(cursorId); ASSERT(isMockCursorKilled(0)); } @@ -357,7 +357,7 @@ TEST_F(ClusterCursorManagerTest, KillPinnedCursorBasic) { nss, ClusterCursorManager::CursorType::SingleTarget, ClusterCursorManager::CursorLifetime::Mortal, - UserNameIterator())); + boost::none)); auto pinnedCursor = getManager()->checkOutCursor(cursorId, getOperationContext(), successAuthChecker); ASSERT_OK(pinnedCursor.getStatus()); @@ -384,7 +384,7 @@ TEST_F(ClusterCursorManagerTest, KillCursorMultipleCursors) { nss, ClusterCursorManager::CursorType::SingleTarget, ClusterCursorManager::CursorLifetime::Mortal, - UserNameIterator())); + boost::none)); } // Kill each cursor and verify that it was successfully killed. for (size_t i = 0; i < numCursors; ++i) { @@ -408,7 +408,7 @@ TEST_F(ClusterCursorManagerTest, KillCursorWrongCursorId) { nss, ClusterCursorManager::CursorType::SingleTarget, ClusterCursorManager::CursorLifetime::Mortal, - UserNameIterator())); + boost::none)); Status killResult = getManager()->killCursor(getOperationContext(), cursorId + 1); ASSERT_EQ(ErrorCodes::CursorNotFound, killResult); } @@ -420,7 +420,7 @@ TEST_F(ClusterCursorManagerTest, KillMortalCursorsInactiveSinceBasic) { nss, ClusterCursorManager::CursorType::SingleTarget, ClusterCursorManager::CursorLifetime::Mortal, - UserNameIterator())); + boost::none)); getManager()->killMortalCursorsInactiveSince(getOperationContext(), getClockSource()->now()); ASSERT(isMockCursorKilled(0)); } @@ -434,7 +434,7 @@ TEST_F(ClusterCursorManagerTest, KillMortalCursorsInactiveSinceSkipUnexpired) { nss, ClusterCursorManager::CursorType::SingleTarget, ClusterCursorManager::CursorLifetime::Mortal, - UserNameIterator())); + boost::none)); getManager()->killMortalCursorsInactiveSince(getOperationContext(), timeBeforeCursorCreation); ASSERT(!isMockCursorKilled(0)); } @@ -446,7 +446,7 @@ TEST_F(ClusterCursorManagerTest, KillMortalCursorsInactiveSinceSkipImmortal) { nss, ClusterCursorManager::CursorType::SingleTarget, ClusterCursorManager::CursorLifetime::Immortal, - UserNameIterator())); + boost::none)); getManager()->killMortalCursorsInactiveSince(getOperationContext(), getClockSource()->now()); ASSERT(!isMockCursorKilled(0)); } @@ -460,7 +460,7 @@ TEST_F(ClusterCursorManagerTest, ShouldNotKillPinnedCursors) { nss, ClusterCursorManager::CursorType::SingleTarget, ClusterCursorManager::CursorLifetime::Mortal, - UserNameIterator())); + boost::none)); auto pin = assertGet( getManager()->checkOutCursor(cursorId, getOperationContext(), successAuthChecker)); getManager()->killMortalCursorsInactiveSince(getOperationContext(), getClockSource()->now()); @@ -485,7 +485,7 @@ TEST_F(ClusterCursorManagerTest, KillMortalCursorsInactiveSinceMultipleCursors) nss, ClusterCursorManager::CursorType::SingleTarget, ClusterCursorManager::CursorLifetime::Mortal, - UserNameIterator())); + boost::none)); getClockSource()->advance(Milliseconds(1)); } getManager()->killMortalCursorsInactiveSince(getOperationContext(), cutoff); @@ -507,7 +507,7 @@ TEST_F(ClusterCursorManagerTest, KillAllCursors) { nss, ClusterCursorManager::CursorType::SingleTarget, ClusterCursorManager::CursorLifetime::Mortal, - UserNameIterator())); + boost::none)); } getManager()->killAllCursors(getOperationContext()); for (size_t i = 0; i < numCursors; ++i) { @@ -523,7 +523,7 @@ TEST_F(ClusterCursorManagerTest, KillCursorsSatisfyingAlwaysTrueKillsAllCursors) nss, ClusterCursorManager::CursorType::SingleTarget, ClusterCursorManager::CursorLifetime::Mortal, - UserNameIterator())); + boost::none)); } auto pred = [](CursorId, const ClusterCursorManager::CursorEntry&) { return true; }; auto nKilled = getManager()->killCursorsSatisfying(getOperationContext(), std::move(pred)); @@ -541,7 +541,7 @@ TEST_F(ClusterCursorManagerTest, KillCursorsSatisfyingAlwaysFalseKillsNoCursors) nss, ClusterCursorManager::CursorType::SingleTarget, ClusterCursorManager::CursorLifetime::Mortal, - UserNameIterator())); + boost::none)); } auto pred = [](CursorId, const ClusterCursorManager::CursorEntry&) { return false; }; auto nKilled = getManager()->killCursorsSatisfying(getOperationContext(), std::move(pred)); @@ -562,7 +562,7 @@ TEST_F(ClusterCursorManagerTest, KillCursorsSatisfyingOnlyKillsMatchingSubset) { nss, ClusterCursorManager::CursorType::SingleTarget, ClusterCursorManager::CursorLifetime::Mortal, - UserNameIterator()); + boost::none); ASSERT_OK(swCursorId); if (shouldKillCursor(i)) idsToKill.insert(swCursorId.getValue()); @@ -589,7 +589,7 @@ TEST_F(ClusterCursorManagerTest, KillCursorsSatisfyingBasedOnOpKey) { nss, ClusterCursorManager::CursorType::SingleTarget, ClusterCursorManager::CursorLifetime::Mortal, - UserNameIterator())); + boost::none)); } auto pred = [&](CursorId id, const ClusterCursorManager::CursorEntry& entry) { return entry.getOperationKey() == getOperationContext()->getOperationKey(); @@ -608,7 +608,7 @@ TEST_F(ClusterCursorManagerTest, CorrectlyRecordsOriginatingClient) { nss, ClusterCursorManager::CursorType::MultiTarget, ClusterCursorManager::CursorLifetime::Mortal, - UserNameIterator())); + boost::none)); // Now insert some cursors under a different client. const size_t numAltClientCursors = 10; { @@ -621,7 +621,7 @@ TEST_F(ClusterCursorManagerTest, CorrectlyRecordsOriginatingClient) { nss, ClusterCursorManager::CursorType::MultiTarget, ClusterCursorManager::CursorLifetime::Mortal, - UserNameIterator())); + boost::none)); } } @@ -652,7 +652,7 @@ TEST_F(ClusterCursorManagerTest, StatsRegisterShardedCursor) { nss, ClusterCursorManager::CursorType::MultiTarget, ClusterCursorManager::CursorLifetime::Mortal, - UserNameIterator())); + boost::none)); ASSERT_EQ(1U, getManager()->stats().cursorsMultiTarget); } @@ -663,7 +663,7 @@ TEST_F(ClusterCursorManagerTest, StatsRegisterNotShardedCursor) { nss, ClusterCursorManager::CursorType::SingleTarget, ClusterCursorManager::CursorLifetime::Mortal, - UserNameIterator())); + boost::none)); ASSERT_EQ(1U, getManager()->stats().cursorsSingleTarget); } @@ -675,7 +675,7 @@ TEST_F(ClusterCursorManagerTest, StatsPinCursor) { nss, ClusterCursorManager::CursorType::MultiTarget, ClusterCursorManager::CursorLifetime::Mortal, - UserNameIterator())); + boost::none)); auto pinnedCursor = getManager()->checkOutCursor(cursorId, getOperationContext(), successAuthChecker); ASSERT_EQ(1U, getManager()->stats().cursorsPinned); @@ -691,7 +691,7 @@ TEST_F(ClusterCursorManagerTest, StatsRegisterMultipleCursors) { nss, ClusterCursorManager::CursorType::MultiTarget, ClusterCursorManager::CursorLifetime::Mortal, - UserNameIterator())); + boost::none)); ASSERT_EQ(i + 1, getManager()->stats().cursorsMultiTarget); ASSERT_EQ(0U, getManager()->stats().cursorsSingleTarget); } @@ -702,7 +702,7 @@ TEST_F(ClusterCursorManagerTest, StatsRegisterMultipleCursors) { nss, ClusterCursorManager::CursorType::SingleTarget, ClusterCursorManager::CursorLifetime::Mortal, - UserNameIterator())); + boost::none)); ASSERT_EQ(numShardedCursors, getManager()->stats().cursorsMultiTarget); ASSERT_EQ(i + 1, getManager()->stats().cursorsSingleTarget); } @@ -716,7 +716,7 @@ TEST_F(ClusterCursorManagerTest, StatsKillShardedCursor) { nss, ClusterCursorManager::CursorType::MultiTarget, ClusterCursorManager::CursorLifetime::Mortal, - UserNameIterator())); + boost::none)); ASSERT_EQ(1U, getManager()->stats().cursorsMultiTarget); ASSERT_OK(getManager()->killCursor(getOperationContext(), cursorId)); ASSERT_EQ(0U, getManager()->stats().cursorsMultiTarget); @@ -730,7 +730,7 @@ TEST_F(ClusterCursorManagerTest, StatsKillNotShardedCursor) { nss, ClusterCursorManager::CursorType::SingleTarget, ClusterCursorManager::CursorLifetime::Mortal, - UserNameIterator())); + boost::none)); ASSERT_EQ(1U, getManager()->stats().cursorsSingleTarget); ASSERT_OK(getManager()->killCursor(getOperationContext(), cursorId)); ASSERT_EQ(0U, getManager()->stats().cursorsSingleTarget); @@ -744,7 +744,7 @@ TEST_F(ClusterCursorManagerTest, StatsKillPinnedCursor) { nss, ClusterCursorManager::CursorType::MultiTarget, ClusterCursorManager::CursorLifetime::Mortal, - UserNameIterator())); + boost::none)); auto pinnedCursor = getManager()->checkOutCursor(cursorId, getOperationContext(), successAuthChecker); ASSERT_EQ(1U, getManager()->stats().cursorsPinned); @@ -763,7 +763,7 @@ TEST_F(ClusterCursorManagerTest, StatsExhaustShardedCursor) { nss, ClusterCursorManager::CursorType::MultiTarget, ClusterCursorManager::CursorLifetime::Mortal, - UserNameIterator())); + boost::none)); auto pinnedCursor = getManager()->checkOutCursor(cursorId, getOperationContext(), successAuthChecker); ASSERT_OK(pinnedCursor.getStatus()); @@ -781,7 +781,7 @@ TEST_F(ClusterCursorManagerTest, StatsExhaustNotShardedCursor) { nss, ClusterCursorManager::CursorType::SingleTarget, ClusterCursorManager::CursorLifetime::Mortal, - UserNameIterator())); + boost::none)); auto pinnedCursor = getManager()->checkOutCursor(cursorId, getOperationContext(), successAuthChecker); ASSERT_OK(pinnedCursor.getStatus()); @@ -800,7 +800,7 @@ TEST_F(ClusterCursorManagerTest, StatsExhaustPinnedCursor) { nss, ClusterCursorManager::CursorType::SingleTarget, ClusterCursorManager::CursorLifetime::Mortal, - UserNameIterator())); + boost::none)); auto pinnedCursor = getManager()->checkOutCursor(cursorId, getOperationContext(), successAuthChecker); ASSERT_OK(pinnedCursor.getStatus()); @@ -819,7 +819,7 @@ TEST_F(ClusterCursorManagerTest, StatsCheckInWithoutExhaustingPinnedCursor) { nss, ClusterCursorManager::CursorType::SingleTarget, ClusterCursorManager::CursorLifetime::Mortal, - UserNameIterator())); + boost::none)); auto pinnedCursor = getManager()->checkOutCursor(cursorId, getOperationContext(), successAuthChecker); ASSERT_OK(pinnedCursor.getStatus()); @@ -844,7 +844,7 @@ TEST_F(ClusterCursorManagerTest, PinnedCursorReturnCursorNotExhausted) { nss, ClusterCursorManager::CursorType::SingleTarget, ClusterCursorManager::CursorLifetime::Mortal, - UserNameIterator())); + boost::none)); auto registeredCursor = getManager()->checkOutCursor(cursorId, getOperationContext(), successAuthChecker); ASSERT_OK(registeredCursor.getStatus()); @@ -866,7 +866,7 @@ TEST_F(ClusterCursorManagerTest, PinnedCursorReturnCursorExhausted) { nss, ClusterCursorManager::CursorType::SingleTarget, ClusterCursorManager::CursorLifetime::Mortal, - UserNameIterator())); + boost::none)); auto registeredCursor = getManager()->checkOutCursor(cursorId, getOperationContext(), successAuthChecker); ASSERT_OK(registeredCursor.getStatus()); @@ -894,7 +894,7 @@ TEST_F(ClusterCursorManagerTest, PinnedCursorReturnCursorExhaustedWithNonExhaust nss, ClusterCursorManager::CursorType::SingleTarget, ClusterCursorManager::CursorLifetime::Mortal, - UserNameIterator())); + boost::none)); auto registeredCursor = getManager()->checkOutCursor(cursorId, getOperationContext(), successAuthChecker); ASSERT_OK(registeredCursor.getStatus()); @@ -920,7 +920,7 @@ TEST_F(ClusterCursorManagerTest, PinnedCursorMoveAssignmentKill) { nss, ClusterCursorManager::CursorType::SingleTarget, ClusterCursorManager::CursorLifetime::Mortal, - UserNameIterator())); + boost::none)); auto pinnedCursor = getManager()->checkOutCursor(cursorId, getOperationContext(), successAuthChecker); pinnedCursor = ClusterCursorManager::PinnedCursor(); @@ -936,7 +936,7 @@ TEST_F(ClusterCursorManagerTest, PinnedCursorDestructorKill) { nss, ClusterCursorManager::CursorType::SingleTarget, ClusterCursorManager::CursorLifetime::Mortal, - UserNameIterator())); + boost::none)); auto pinnedCursor = getManager()->checkOutCursor(cursorId, getOperationContext(), successAuthChecker); } @@ -954,7 +954,7 @@ TEST_F(ClusterCursorManagerTest, RemotesExhausted) { nss, ClusterCursorManager::CursorType::SingleTarget, ClusterCursorManager::CursorLifetime::Mortal, - UserNameIterator())); + boost::none)); auto pinnedCursor = getManager()->checkOutCursor(cursorId, getOperationContext(), successAuthChecker); ASSERT_OK(pinnedCursor.getStatus()); @@ -970,7 +970,7 @@ TEST_F(ClusterCursorManagerTest, DoNotDestroyKilledPinnedCursors) { nss, ClusterCursorManager::CursorType::SingleTarget, ClusterCursorManager::CursorLifetime::Mortal, - UserNameIterator())); + boost::none)); auto pinnedCursor = getManager()->checkOutCursor(cursorId, getOperationContext(), successAuthChecker); ASSERT_OK(pinnedCursor.getStatus()); @@ -1008,7 +1008,7 @@ TEST_F(ClusterCursorManagerTest, CursorStoresAPIParameters) { nss, ClusterCursorManager::CursorType::SingleTarget, ClusterCursorManager::CursorLifetime::Mortal, - UserNameIterator())); + boost::none)); auto pinnedCursor = assertGet( getManager()->checkOutCursor(cursorId, getOperationContext(), successAuthChecker)); @@ -1024,7 +1024,7 @@ TEST_F(ClusterCursorManagerTest, CannotRegisterCursorDuringShutdown) { nss, ClusterCursorManager::CursorType::SingleTarget, ClusterCursorManager::CursorLifetime::Mortal, - UserNameIterator())); + boost::none)); ASSERT(!isMockCursorKilled(0)); getManager()->shutdown(getOperationContext()); @@ -1037,7 +1037,7 @@ TEST_F(ClusterCursorManagerTest, CannotRegisterCursorDuringShutdown) { nss, ClusterCursorManager::CursorType::SingleTarget, ClusterCursorManager::CursorLifetime::Mortal, - UserNameIterator())); + boost::none)); } TEST_F(ClusterCursorManagerTest, PinnedCursorNotKilledOnShutdown) { @@ -1047,7 +1047,7 @@ TEST_F(ClusterCursorManagerTest, PinnedCursorNotKilledOnShutdown) { nss, ClusterCursorManager::CursorType::SingleTarget, ClusterCursorManager::CursorLifetime::Mortal, - UserNameIterator())); + boost::none)); auto pinnedCursor = getManager()->checkOutCursor(cursorId, getOperationContext(), successAuthChecker); @@ -1069,7 +1069,7 @@ TEST_F(ClusterCursorManagerTest, CannotCheckoutCursorDuringShutdown) { nss, ClusterCursorManager::CursorType::SingleTarget, ClusterCursorManager::CursorLifetime::Mortal, - UserNameIterator())); + boost::none)); ASSERT(!isMockCursorKilled(0)); getManager()->shutdown(getOperationContext()); @@ -1092,7 +1092,7 @@ TEST_F(ClusterCursorManagerTest, CursorsWithoutSessions) { nss, ClusterCursorManager::CursorType::SingleTarget, ClusterCursorManager::CursorLifetime::Mortal, - UserNameIterator())); + boost::none)); // Manager should have no active sessions. LogicalSessionIdSet lsids; @@ -1112,7 +1112,7 @@ TEST_F(ClusterCursorManagerTest, OneCursorWithASession) { nss, ClusterCursorManager::CursorType::SingleTarget, ClusterCursorManager::CursorLifetime::Mortal, - UserNameIterator())); + boost::none)); // Retrieve all sessions active in manager - set should contain just lsid. LogicalSessionIdSet lsids; @@ -1147,7 +1147,7 @@ TEST_F(ClusterCursorManagerTest, GetSessionIdsWhileCheckedOut) { nss, ClusterCursorManager::CursorType::SingleTarget, ClusterCursorManager::CursorLifetime::Mortal, - UserNameIterator())); + boost::none)); // Check the cursor out, then try to append cursors, see that we get one. auto res = getManager()->checkOutCursor(cursorId, getOperationContext(), successAuthChecker); @@ -1169,14 +1169,14 @@ TEST_F(ClusterCursorManagerTest, MultipleCursorsWithSameSession) { nss, ClusterCursorManager::CursorType::SingleTarget, ClusterCursorManager::CursorLifetime::Mortal, - UserNameIterator())); + boost::none)); auto cursorId2 = assertGet(getManager()->registerCursor(getOperationContext(), allocateMockCursor(lsid), nss, ClusterCursorManager::CursorType::SingleTarget, ClusterCursorManager::CursorLifetime::Mortal, - UserNameIterator())); + boost::none)); // Retrieve all sessions - set should contain just lsid. stdx::unordered_set<LogicalSessionId, LogicalSessionIdHash> lsids; @@ -1219,7 +1219,7 @@ TEST_F(ClusterCursorManagerTest, MultipleCursorsMultipleSessions) { nss, ClusterCursorManager::CursorType::SingleTarget, ClusterCursorManager::CursorLifetime::Mortal, - UserNameIterator())); + boost::none)); CursorId cursor2 = assertGet(getManager()->registerCursor(getOperationContext(), @@ -1227,14 +1227,14 @@ TEST_F(ClusterCursorManagerTest, MultipleCursorsMultipleSessions) { nss, ClusterCursorManager::CursorType::SingleTarget, ClusterCursorManager::CursorLifetime::Mortal, - UserNameIterator())); + boost::none)); ASSERT_OK(getManager()->registerCursor(getOperationContext(), allocateMockCursor(), nss, ClusterCursorManager::CursorType::SingleTarget, ClusterCursorManager::CursorLifetime::Mortal, - UserNameIterator())); + boost::none)); // Retrieve all sessions - should be both lsids. LogicalSessionIdSet lsids; @@ -1265,7 +1265,7 @@ TEST_F(ClusterCursorManagerTest, ManyCursorsManySessions) { nss, ClusterCursorManager::CursorType::SingleTarget, ClusterCursorManager::CursorLifetime::Mortal, - UserNameIterator())); + boost::none)); } // Retrieve all sessions. @@ -1281,7 +1281,7 @@ TEST_F(ClusterCursorManagerTest, CheckAuthForKillCursors) { nss, ClusterCursorManager::CursorType::SingleTarget, ClusterCursorManager::CursorLifetime::Mortal, - UserNameIterator())); + boost::none)); ASSERT_EQ(ErrorCodes::CursorNotFound, getManager()->checkAuthForKillCursors( @@ -1301,7 +1301,7 @@ TEST_F(ClusterCursorManagerTest, PinnedCursorReturnsUnderlyingCursorTxnNumber) { nss, ClusterCursorManager::CursorType::SingleTarget, ClusterCursorManager::CursorLifetime::Mortal, - UserNameIterator())); + boost::none)); auto pinnedCursor = getManager()->checkOutCursor(cursorId, getOperationContext(), successAuthChecker); diff --git a/src/mongo/s/query/cluster_find.cpp b/src/mongo/s/query/cluster_find.cpp index 42b0b8154e5..7d6f93623f9 100644 --- a/src/mongo/s/query/cluster_find.cpp +++ b/src/mongo/s/query/cluster_find.cpp @@ -409,11 +409,11 @@ CursorId runQueryWithoutRetrying(OperationContext* opCtx, const auto cursorLifetime = findCommand.getNoCursorTimeout() ? ClusterCursorManager::CursorLifetime::Immortal : ClusterCursorManager::CursorLifetime::Mortal; - auto authUsers = AuthorizationSession::get(opCtx->getClient())->getAuthenticatedUserNames(); + auto authUser = AuthorizationSession::get(opCtx->getClient())->getAuthenticatedUserName(); ccc->incNBatches(); auto cursorId = uassertStatusOK(cursorManager->registerCursor( - opCtx, ccc.releaseCursor(), query.nss(), cursorType, cursorLifetime, authUsers)); + opCtx, ccc.releaseCursor(), query.nss(), cursorType, cursorLifetime, authUser)); // Record the cursorID in CurOp. CurOp::get(opCtx)->debug().cursorid = cursorId; @@ -699,8 +699,8 @@ StatusWith<CursorResponse> ClusterFind::runGetMore(OperationContext* opCtx, auto cursorManager = Grid::get(opCtx)->getCursorManager(); auto authzSession = AuthorizationSession::get(opCtx->getClient()); - auto authChecker = [&authzSession](UserNameIterator userNames) -> Status { - return authzSession->isCoauthorizedWith(userNames) + auto authChecker = [&authzSession](const boost::optional<UserName>& userName) -> Status { + return authzSession->isCoauthorizedWith(userName) ? Status::OK() : Status(ErrorCodes::Unauthorized, "User not authorized to access cursor"); }; diff --git a/src/mongo/s/query/store_possible_cursor.cpp b/src/mongo/s/query/store_possible_cursor.cpp index c778daa9d84..329d47e34db 100644 --- a/src/mongo/s/query/store_possible_cursor.cpp +++ b/src/mongo/s/query/store_possible_cursor.cpp @@ -132,14 +132,14 @@ StatusWith<BSONObj> storePossibleCursor(OperationContext* opCtx, // We don't expect to use this cursor until a subsequent getMore, so detach from the current // OperationContext until then. ccc->detachFromOperationContext(); - auto authUsers = AuthorizationSession::get(opCtx->getClient())->getAuthenticatedUserNames(); + auto authUser = AuthorizationSession::get(opCtx->getClient())->getAuthenticatedUserName(); auto clusterCursorId = cursorManager->registerCursor(opCtx, ccc.releaseCursor(), requestedNss, ClusterCursorManager::CursorType::SingleTarget, ClusterCursorManager::CursorLifetime::Mortal, - authUsers); + authUser); if (!clusterCursorId.isOK()) { return clusterCursorId.getStatus(); } |