diff options
author | Ben Caimano <ben.caimano@10gen.com> | 2018-07-31 18:26:07 -0400 |
---|---|---|
committer | Ben Caimano <ben.caimano@10gen.com> | 2018-07-31 18:32:19 -0400 |
commit | 6711ab3bbc02ca98da1af2fe115f07e37bb076e4 (patch) | |
tree | f1859ba3692a982b0afbd06fcb523c830136c25c /src/mongo/db | |
parent | 35757e8ef3134fad1a09cb09a69882929d9ebb76 (diff) | |
download | mongo-6711ab3bbc02ca98da1af2fe115f07e37bb076e4.tar.gz |
Revert "SERVER-35890 refactor User cache into InvalidatingLRUCache and UserHandle"
This reverts commit 78dec3622268ad27bb855eda4c6a4ed345412fd9.
Diffstat (limited to 'src/mongo/db')
22 files changed, 394 insertions, 425 deletions
diff --git a/src/mongo/db/auth/action_types.txt b/src/mongo/db/auth/action_types.txt index c59f4c2318d..d641378e162 100644 --- a/src/mongo/db/auth/action_types.txt +++ b/src/mongo/db/auth/action_types.txt @@ -71,7 +71,6 @@ "killAnySession", "killCursors", "killop", -"listCachedAndActiveUsers", "listCollections", "listCursors", "listDatabases", diff --git a/src/mongo/db/auth/authorization_manager.h b/src/mongo/db/auth/authorization_manager.h index 2c19797aad3..a2f170b62aa 100644 --- a/src/mongo/db/auth/authorization_manager.h +++ b/src/mongo/db/auth/authorization_manager.h @@ -64,7 +64,7 @@ class UserDocumentParser; * Internal secret key info. */ struct AuthInfo { - UserHandle user; + User* user; }; extern AuthInfo internalSecurity; // set at startup and not changed after initialization. @@ -252,14 +252,24 @@ public: std::vector<BSONObj>* result) = 0; /** - * Returns a Status or UserHandle for the given userName. If the user cache already has a - * user object for this user, it returns a handle from the cache, otherwise it reads the - * user document from disk or LDAP - this may block for a long time. - * - * The returned user may be invalid by the time the caller gets access to it. + * Returns the User object for the given userName in the out parameter "acquiredUser". + * If the user cache already has a user object for this user, it increments the refcount + * on that object and gives out a pointer to it. If no user object for this user name + * exists yet in the cache, reads the user's privilege document from disk, builds up + * a User object, sets the refcount to 1, and gives that out. The returned user may + * be invalid by the time the caller gets access to it. + * The AuthorizationManager retains ownership of the returned User object. + * On non-OK Status return values, acquiredUser will not be modified. */ - virtual StatusWith<UserHandle> acquireUser(OperationContext* opCtx, - const UserName& userName) = 0; + virtual Status acquireUser(OperationContext* opCtx, + const UserName& userName, + User** acquiredUser) = 0; + + /** + * Decrements the refcount of the given User object. If the refcount has gone to zero, + * deletes the User. Caller must stop using its pointer to "user" after calling this. + */ + virtual void releaseUser(User* user) = 0; /** * Marks the given user as invalid and removes it from the user cache. @@ -300,17 +310,6 @@ public: const NamespaceString& nss, const BSONObj& obj, const BSONObj* patt) = 0; - - /* - * Represents a user in the user cache. - */ - struct CachedUserInfo { - UserName userName; // The username of the user - bool active; // Whether the user is currently in use by a thread (a thread has - // called acquireUser and still owns the returned shared_ptr) - }; - - virtual std::vector<CachedUserInfo> getUserCacheInfo() const = 0; }; } // namespace mongo diff --git a/src/mongo/db/auth/authorization_manager_impl.cpp b/src/mongo/db/auth/authorization_manager_impl.cpp index be6c356495b..054aa9ff939 100644 --- a/src/mongo/db/auth/authorization_manager_impl.cpp +++ b/src/mongo/db/auth/authorization_manager_impl.cpp @@ -59,7 +59,6 @@ #include "mongo/db/global_settings.h" #include "mongo/db/jsobj.h" #include "mongo/db/mongod_options.h" -#include "mongo/db/server_parameters.h" #include "mongo/platform/compiler.h" #include "mongo/stdx/memory.h" #include "mongo/stdx/mutex.h" @@ -78,12 +77,14 @@ using std::back_inserter; using std::string; using std::vector; + MONGO_INITIALIZER_GENERAL(SetupInternalSecurityUser, ("EndStartupOptionStorage"), ("CreateAuthorizationManager")) (InitializerContext* const context) try { - UserHandle user = std::make_shared<User>(UserName("__system", "local")); + User* user = new User(UserName("__system", "local")); + user->incrementRefCount(); // Pin this user so the ref count never drops below 1. ActionSet allActions; allActions.addAllActions(); PrivilegeVector privileges; @@ -110,8 +111,6 @@ MONGO_INITIALIZER_GENERAL(SetupInternalSecurityUser, } catch (...) { return exceptionToStatus(); } - -MONGO_EXPORT_STARTUP_SERVER_PARAMETER(authorizationManagerCacheSize, int, 100); } // namespace @@ -206,7 +205,7 @@ public: fassert(17191, !_authzManager->_isFetchPhaseBusy); _isThisGuardInFetchPhase = true; _authzManager->_isFetchPhaseBusy = true; - _startGeneration = _authzManager->_fetchGeneration; + _startGeneration = _authzManager->_cacheGeneration; _lock.unlock(); } @@ -225,7 +224,7 @@ public: } /** - * Returns true if _authzManager->_fetchGeneration remained the same while this guard was + * Returns true if _authzManager->_cacheGeneration remained the same while this guard was * in fetch phase. Behavior is undefined if this guard never entered fetch phase. * * If this returns true, do not update the cached data with this @@ -233,7 +232,7 @@ public: bool isSameCacheGeneration() const { fassert(17223, _isThisGuardInFetchPhase); fassert(17231, _lock.owns_lock()); - return _startGeneration == _authzManager->_fetchGeneration; + return _startGeneration == _authzManager->_cacheGeneration; } private: @@ -261,12 +260,18 @@ AuthorizationManagerImpl::AuthorizationManagerImpl( _privilegeDocsExist(false), _externalState(std::move(externalState)), _version(schemaVersionInvalid), - _userCache(authorizationManagerCacheSize, UserCacheInvalidator()), _isFetchPhaseBusy(false) { _updateCacheGeneration_inlock(); } -AuthorizationManagerImpl::~AuthorizationManagerImpl() {} +AuthorizationManagerImpl::~AuthorizationManagerImpl() { + for (stdx::unordered_map<UserName, User*>::iterator it = _userCache.begin(); + it != _userCache.end(); + ++it) { + fassert(17265, it->second != internalSecurity.user); + delete it->second; + } +} std::unique_ptr<AuthorizationSession> AuthorizationManagerImpl::makeAuthorizationSession() { return std::make_unique<AuthorizationSessionImpl>( @@ -308,7 +313,7 @@ Status AuthorizationManagerImpl::getAuthorizationVersion(OperationContext* opCtx OID AuthorizationManagerImpl::getCacheGeneration() { CacheGuard guard(this, CacheGuard::fetchSynchronizationManual); - return _fetchGeneration; + return _cacheGeneration; } void AuthorizationManagerImpl::setAuthEnabled(bool enabled) { @@ -408,30 +413,33 @@ Status AuthorizationManagerImpl::getRoleDescriptionsForDB( opCtx, dbname, privileges, restrictions, showBuiltinRoles, result); } -void AuthorizationManagerImpl::UserCacheInvalidator::operator()(User* user) { - user->_invalidate(); -} - -StatusWith<UserHandle> AuthorizationManagerImpl::acquireUser(OperationContext* opCtx, - const UserName& userName) { +Status AuthorizationManagerImpl::acquireUser(OperationContext* opCtx, + const UserName& userName, + User** acquiredUser) { if (userName == internalSecurity.user->getName()) { - return internalSecurity.user; + *acquiredUser = internalSecurity.user; + return Status::OK(); } + stdx::unordered_map<UserName, User*>::iterator it; + CacheGuard guard(this, CacheGuard::fetchSynchronizationManual); - boost::optional<std::shared_ptr<User>> cachedUser; - while ((boost::none == (cachedUser = _userCache.get(userName))) && + while ((_userCache.end() == (it = _userCache.find(userName))) && guard.otherUpdateInFetchPhase()) { guard.wait(); } - if (cachedUser != boost::none) { - auto ret = *cachedUser; - fassert(16914, ret.get()); - fassert(17003, ret->isValid()); - return ret; + if (it != _userCache.end()) { + fassert(16914, it->second); + fassert(17003, it->second->isValid()); + fassert(17008, it->second->getRefCount() > 0); + it->second->incrementRefCount(); + *acquiredUser = it->second; + return Status::OK(); } + std::unique_ptr<User> user; + int authzVersion = _version; guard.beginFetchPhase(); @@ -440,7 +448,6 @@ StatusWith<UserHandle> AuthorizationManagerImpl::acquireUser(OperationContext* o // after schema upgrades. static const int maxAcquireRetries = 2; Status status = Status::OK(); - std::unique_ptr<User> user; for (int i = 0; i < maxAcquireRetries; ++i) { if (authzVersion == schemaVersionInvalid) { Status status = _externalState->getStoredAuthorizationVersion(opCtx, &authzVersion); @@ -480,50 +487,87 @@ StatusWith<UserHandle> AuthorizationManagerImpl::acquireUser(OperationContext* o guard.endFetchPhase(); + user->incrementRefCount(); + // NOTE: It is not safe to throw an exception from here to the end of the method. if (guard.isSameCacheGeneration()) { + _userCache.insert(std::make_pair(userName, user.get())); if (_version == schemaVersionInvalid) _version = authzVersion; - return _userCache.insertOrAssignAndGet(userName, std::move(user)); } else { // If the cache generation changed while this thread was in fetch mode, the data // associated with the user may now be invalid, so we must mark it as such. The caller // may still opt to use the information for a short while, but not indefinitely. - user->_invalidate(); - return UserHandle(std::move(user)); + user->invalidate(); } + *acquiredUser = user.release(); + + return Status::OK(); } Status AuthorizationManagerImpl::_fetchUserV2(OperationContext* opCtx, const UserName& userName, - std::unique_ptr<User>* out) { + std::unique_ptr<User>* acquiredUser) { BSONObj userObj; Status status = getUserDescription(opCtx, userName, &userObj); if (!status.isOK()) { return status; } - auto user = std::make_unique<User>(userName); + // Put the new user into an unique_ptr temporarily in case there's an error while + // initializing the user. + auto user = stdx::make_unique<User>(userName); status = _initializeUserFromPrivilegeDocument(user.get(), userObj); if (!status.isOK()) { return status; } - - std::swap(*out, user); + acquiredUser->reset(user.release()); return Status::OK(); } +void AuthorizationManagerImpl::releaseUser(User* user) { + if (user == internalSecurity.user) { + return; + } + + CacheGuard guard(this, CacheGuard::fetchSynchronizationManual); + user->decrementRefCount(); + if (user->getRefCount() == 0) { + // If it's been invalidated then it's not in the _userCache anymore. + if (user->isValid()) { + MONGO_COMPILER_VARIABLE_UNUSED bool erased = _userCache.erase(user->getName()); + dassert(erased); + } + delete user; + } +} + void AuthorizationManagerImpl::invalidateUserByName(const UserName& userName) { CacheGuard guard(this, CacheGuard::fetchSynchronizationManual); _updateCacheGeneration_inlock(); - _userCache.invalidate(userName); + stdx::unordered_map<UserName, User*>::iterator it = _userCache.find(userName); + if (it == _userCache.end()) { + return; + } + + User* user = it->second; + _userCache.erase(it); + user->invalidate(); } void AuthorizationManagerImpl::invalidateUsersFromDB(StringData dbname) { CacheGuard guard(this, CacheGuard::fetchSynchronizationManual); _updateCacheGeneration_inlock(); - _userCache.invalidateIf( - [&](const UserName& user, const User*) { return user.getDB() == dbname; }); + stdx::unordered_map<UserName, User*>::iterator it = _userCache.begin(); + while (it != _userCache.end()) { + User* user = it->second; + if (user->getName().getDB() == dbname) { + _userCache.erase(it++); + user->invalidate(); + } else { + ++it; + } + } } void AuthorizationManagerImpl::invalidateUserCache() { @@ -533,7 +577,13 @@ void AuthorizationManagerImpl::invalidateUserCache() { void AuthorizationManagerImpl::_invalidateUserCache_inlock() { _updateCacheGeneration_inlock(); - _userCache.invalidateIf([](const UserName& a, const User*) { return true; }); + for (stdx::unordered_map<UserName, User*>::iterator it = _userCache.begin(); + it != _userCache.end(); + ++it) { + fassert(17266, it->second != internalSecurity.user); + it->second->invalidate(); + } + _userCache.clear(); // Reread the schema version before acquiring the next user. _version = schemaVersionInvalid; @@ -617,7 +667,7 @@ StatusWith<UserName> extractUserNameFromIdString(StringData idstr) { } // namespace void AuthorizationManagerImpl::_updateCacheGeneration_inlock() { - _fetchGeneration = OID::gen(); + _cacheGeneration = OID::gen(); } void AuthorizationManagerImpl::_invalidateRelevantCacheData(const char* op, @@ -663,17 +713,4 @@ void AuthorizationManagerImpl::logOp(OperationContext* opCtx, } } -std::vector<AuthorizationManager::CachedUserInfo> AuthorizationManagerImpl::getUserCacheInfo() - const { - auto cacheData = _userCache.getCacheInfo(); - std::vector<AuthorizationManager::CachedUserInfo> ret; - ret.reserve(cacheData.size()); - std::transform( - cacheData.begin(), cacheData.end(), std::back_inserter(ret), [](const auto& info) { - return AuthorizationManager::CachedUserInfo{info.key, info.active}; - }); - - return ret; -} - } // namespace mongo diff --git a/src/mongo/db/auth/authorization_manager_impl.h b/src/mongo/db/auth/authorization_manager_impl.h index c29f3790d62..4147ed01369 100644 --- a/src/mongo/db/auth/authorization_manager_impl.h +++ b/src/mongo/db/auth/authorization_manager_impl.h @@ -52,7 +52,6 @@ #include "mongo/stdx/functional.h" #include "mongo/stdx/mutex.h" #include "mongo/stdx/unordered_map.h" -#include "mongo/util/invalidating_lru_cache.h" namespace mongo { class AuthorizationSession; @@ -116,7 +115,11 @@ public: bool showBuiltinRoles, std::vector<BSONObj>* result) override; - StatusWith<UserHandle> acquireUser(OperationContext* opCtx, const UserName& userName) override; + Status acquireUser(OperationContext* opCtx, + const UserName& userName, + User** acquiredUser) override; + + void releaseUser(User* user) override; void invalidateUserByName(const UserName& user) override; @@ -134,8 +137,6 @@ public: const BSONObj& obj, const BSONObj* patt) override; - std::vector<CachedUserInfo> getUserCacheInfo() const override; - private: /** * Type used to guard accesses and updates to the user cache. @@ -214,17 +215,13 @@ private: * has a reference count - the AuthorizationManager must not delete a User object in the * cache unless its reference count is zero. */ - struct UserCacheInvalidator { - void operator()(User* user); - }; - - InvalidatingLRUCache<UserName, User, UserCacheInvalidator> _userCache; + stdx::unordered_map<UserName, User*> _userCache; /** * Current generation of cached data. Updated every time part of the cache gets * invalidated. Protected by CacheGuard. */ - OID _fetchGeneration; + OID _cacheGeneration; /** * True if there is an update to the _userCache in progress, and that update is currently in diff --git a/src/mongo/db/auth/authorization_manager_test.cpp b/src/mongo/db/auth/authorization_manager_test.cpp index dc3701e2ecf..e0ba470d4f5 100644 --- a/src/mongo/db/auth/authorization_manager_test.cpp +++ b/src/mongo/db/auth/authorization_manager_test.cpp @@ -148,11 +148,11 @@ TEST_F(AuthorizationManagerTest, testAcquireV2User) { << "admin"))), BSONObj())); - auto swu = authzManager->acquireUser(opCtx.get(), UserName("v2read", "test")); - ASSERT_OK(swu.getStatus()); - auto v2read = std::move(swu.getValue()); + User* v2read; + ASSERT_OK(authzManager->acquireUser(opCtx.get(), UserName("v2read", "test"), &v2read)); ASSERT_EQUALS(UserName("v2read", "test"), v2read->getName()); ASSERT(v2read->isValid()); + ASSERT_EQUALS(1U, v2read->getRefCount()); RoleNameIterator roles = v2read->getRoles(); ASSERT_EQUALS(RoleName("read", "test"), roles.next()); ASSERT_FALSE(roles.more()); @@ -160,12 +160,13 @@ TEST_F(AuthorizationManagerTest, testAcquireV2User) { auto testDBPrivilege = privilegeMap[ResourcePattern::forDatabaseName("test")]; ASSERT(testDBPrivilege.getActions().contains(ActionType::find)); // Make sure user's refCount is 0 at the end of the test to avoid an assertion failure + authzManager->releaseUser(v2read); - swu = authzManager->acquireUser(opCtx.get(), UserName("v2cluster", "admin")); - ASSERT_OK(swu.getStatus()); - auto v2cluster = std::move(swu.getValue()); + User* v2cluster; + ASSERT_OK(authzManager->acquireUser(opCtx.get(), UserName("v2cluster", "admin"), &v2cluster)); ASSERT_EQUALS(UserName("v2cluster", "admin"), v2cluster->getName()); ASSERT(v2cluster->isValid()); + ASSERT_EQUALS(1U, v2cluster->getRefCount()); RoleNameIterator clusterRoles = v2cluster->getRoles(); ASSERT_EQUALS(RoleName("clusterAdmin", "admin"), clusterRoles.next()); ASSERT_FALSE(clusterRoles.more()); @@ -173,6 +174,7 @@ TEST_F(AuthorizationManagerTest, testAcquireV2User) { auto clusterPrivilege = privilegeMap[ResourcePattern::forClusterResource()]; ASSERT(clusterPrivilege.getActions().contains(ActionType::serverStatus)); // Make sure user's refCount is 0 at the end of the test to avoid an assertion failure + authzManager->releaseUser(v2cluster); } #ifdef MONGO_CONFIG_SSL @@ -181,9 +183,9 @@ TEST_F(AuthorizationManagerTest, testLocalX509Authorization) { session, SSLPeerInfo(buildX509Name(), {RoleName("read", "test"), RoleName("readWrite", "test")})); - auto swu = authzManager->acquireUser(opCtx.get(), UserName("CN=mongodb.com", "$external")); - ASSERT_OK(swu.getStatus()); - auto x509User = std::move(swu.getValue()); + User* x509User; + ASSERT_OK( + authzManager->acquireUser(opCtx.get(), UserName("CN=mongodb.com", "$external"), &x509User)); ASSERT(x509User->isValid()); stdx::unordered_set<RoleName> expectedRoles{RoleName("read", "test"), @@ -200,6 +202,9 @@ TEST_F(AuthorizationManagerTest, testLocalX509Authorization) { auto privilegeIt = privileges.find(ResourcePattern::forDatabaseName("test")); ASSERT(privilegeIt != privileges.end()); ASSERT(privilegeIt->second.includesAction(ActionType::insert)); + + + authzManager->releaseUser(x509User); } #endif @@ -208,15 +213,17 @@ TEST_F(AuthorizationManagerTest, testLocalX509AuthorizationInvalidUser) { session, SSLPeerInfo(buildX509Name(), {RoleName("read", "test"), RoleName("write", "test")})); + User* x509User; ASSERT_NOT_OK( - authzManager->acquireUser(opCtx.get(), UserName("CN=10gen.com", "$external")).getStatus()); + authzManager->acquireUser(opCtx.get(), UserName("CN=10gen.com", "$external"), &x509User)); } TEST_F(AuthorizationManagerTest, testLocalX509AuthenticationNoAuthorization) { setX509PeerInfo(session, {}); - ASSERT_NOT_OK(authzManager->acquireUser(opCtx.get(), UserName("CN=mongodb.com", "$external")) - .getStatus()); + User* x509User; + ASSERT_NOT_OK( + authzManager->acquireUser(opCtx.get(), UserName("CN=mongodb.com", "$external"), &x509User)); } /** @@ -313,11 +320,11 @@ TEST_F(AuthorizationManagerTest, testAcquireV2UserWithUnrecognizedActions) { << "insert")))), BSONObj())); - auto swu = authzManager->acquireUser(opCtx.get(), UserName("myUser", "test")); - ASSERT_OK(swu.getStatus()); - auto myUser = std::move(swu.getValue()); + User* myUser; + ASSERT_OK(authzManager->acquireUser(opCtx.get(), UserName("myUser", "test"), &myUser)); ASSERT_EQUALS(UserName("myUser", "test"), myUser->getName()); ASSERT(myUser->isValid()); + ASSERT_EQUALS(1U, myUser->getRefCount()); RoleNameIterator roles = myUser->getRoles(); ASSERT_EQUALS(RoleName("myRole", "test"), roles.next()); ASSERT_FALSE(roles.more()); @@ -329,6 +336,9 @@ TEST_F(AuthorizationManagerTest, testAcquireV2UserWithUnrecognizedActions) { actions.removeAction(ActionType::find); actions.removeAction(ActionType::insert); ASSERT(actions.empty()); + + // Make sure user's refCount is 0 at the end of the test to avoid an assertion failure + authzManager->releaseUser(myUser); } // These tests ensure that the AuthorizationManager registers a diff --git a/src/mongo/db/auth/authorization_session_for_test.cpp b/src/mongo/db/auth/authorization_session_for_test.cpp index 8c4684c1408..93169e7d923 100644 --- a/src/mongo/db/auth/authorization_session_for_test.cpp +++ b/src/mongo/db/auth/authorization_session_for_test.cpp @@ -52,27 +52,28 @@ void AuthorizationSessionForTest::assumePrivilegesForDB(Privilege privilege, Str void AuthorizationSessionForTest::assumePrivilegesForDB(PrivilegeVector privileges, StringData dbName) { - auto user = std::make_shared<User>(UserName("authorizationSessionForTestUser", dbName)); + auto user = stdx::make_unique<User>(UserName("authorizationSessionForTestUser", dbName)); user->addPrivileges(privileges); - _authenticatedUsers.add(user); + _authenticatedUsers.add(user.get()); _testUsers.emplace_back(std::move(user)); _buildAuthenticatedRolesVector(); } 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()); + _testUsers.erase(std::remove_if(_testUsers.begin(), + _testUsers.end(), + [&](const std::unique_ptr<User>& user) { + return dbName == user->getName().getDB(); + }), + _testUsers.end()); } void AuthorizationSessionForTest::revokeAllPrivileges() { _testUsers.erase(std::remove_if(_testUsers.begin(), _testUsers.end(), - [&](const auto& user) { + [&](const std::unique_ptr<User>& user) { _authenticatedUsers.removeByDBName(user->getName().getDB()); return true; }), diff --git a/src/mongo/db/auth/authorization_session_for_test.h b/src/mongo/db/auth/authorization_session_for_test.h index 3d9fe10873f..6e9f63418de 100644 --- a/src/mongo/db/auth/authorization_session_for_test.h +++ b/src/mongo/db/auth/authorization_session_for_test.h @@ -76,6 +76,6 @@ public: void revokeAllPrivileges(); private: - std::vector<UserHandle> _testUsers; + std::vector<std::unique_ptr<User>> _testUsers; }; } // namespace mongo diff --git a/src/mongo/db/auth/authorization_session_impl.cpp b/src/mongo/db/auth/authorization_session_impl.cpp index c1920d36d63..f334a20ab58 100644 --- a/src/mongo/db/auth/authorization_session_impl.cpp +++ b/src/mongo/db/auth/authorization_session_impl.cpp @@ -92,13 +92,47 @@ Status checkAuthForCreateOrModifyView(AuthorizationSession* authzSession, isMongos); } +/** Deleter for User*. + * Will release a User* back to its owning AuthorizationManager on destruction. + * If a borrowing UserSet and the iterator it uses to store the User* is provided, this + * deleter will release the User* from the set if the iterator still points to the deleting User*. + */ +class UserReleaser { +public: + explicit UserReleaser(AuthorizationManager* owner) : _owner(owner), _borrower(nullptr) {} + UserReleaser(AuthorizationManager* owner, UserSet* borrower, UserSet::iterator borrowerIt) + : _owner(owner), _borrower(borrower), _it(borrowerIt) {} + + void operator()(User* user) { + // Remove the user from the borrower if it hasn't already been swapped out. + if (_borrower && *_it == user) { + fassert(40546, _borrower->removeAt(_it) == user); + } + _owner->releaseUser(user); + } + +protected: + AuthorizationManager* _owner; + UserSet* _borrower; + UserSet::iterator _it; +}; +/** Holder for User*s. If this Holder falls out of scope while holding a User*, it will release + * the User* from its AuthorizationManager, and extract it from a UserSet if the set still contains + * it. Use this object to guard User*s which will need to be destroyed in the event of an exception. + */ +using UserHolder = std::unique_ptr<User, UserReleaser>; + } // namespace AuthorizationSessionImpl::AuthorizationSessionImpl( std::unique_ptr<AuthzSessionExternalState> externalState, InstallMockForTestingOrAuthImpl) : _externalState(std::move(externalState)), _impersonationFlag(false) {} -AuthorizationSessionImpl::~AuthorizationSessionImpl() {} +AuthorizationSessionImpl::~AuthorizationSessionImpl() { + for (auto& user : _authenticatedUsers) { + getAuthorizationManager().releaseUser(user); + } +} AuthorizationManager& AuthorizationSessionImpl::getAuthorizationManager() { return _externalState->getAuthorizationManager(); @@ -111,15 +145,16 @@ void AuthorizationSessionImpl::startRequest(OperationContext* opCtx) { Status AuthorizationSessionImpl::addAndAuthorizeUser(OperationContext* opCtx, const UserName& userName) { + User* user; AuthorizationManager* authzManager = AuthorizationManager::get(opCtx->getServiceContext()); - auto swUser = authzManager->acquireUser(opCtx, userName); - if (!swUser.isOK()) { - return swUser.getStatus(); + Status status = authzManager->acquireUser(opCtx, userName, &user); + if (!status.isOK()) { + return status; } - auto user = std::move(swUser.getValue()); + UserHolder userHolder(user, UserReleaser(authzManager)); - const auto& restrictionSet = user->getRestrictions(); + const auto& restrictionSet = userHolder->getRestrictions(); if (opCtx->getClient() == nullptr) { return Status(ErrorCodes::AuthenticationFailed, "Unable to evaluate restrictions, OperationContext has no Client"); @@ -133,7 +168,9 @@ Status AuthorizationSessionImpl::addAndAuthorizeUser(OperationContext* opCtx, return AuthorizationManager::authenticationFailedStatus; } - _authenticatedUsers.add(std::move(user)); + // Calling add() on the UserSet may return a user that was replaced because it was from the + // same database. + userHolder.reset(_authenticatedUsers.add(userHolder.release())); // If there are any users and roles in the impersonation data, clear it out. clearImpersonatedUserData(); @@ -143,7 +180,7 @@ Status AuthorizationSessionImpl::addAndAuthorizeUser(OperationContext* opCtx, } User* AuthorizationSessionImpl::lookupUser(const UserName& name) { - return _authenticatedUsers.lookup(name).get(); + return _authenticatedUsers.lookup(name); } User* AuthorizationSessionImpl::getSingleUser() { @@ -163,7 +200,10 @@ User* AuthorizationSessionImpl::getSingleUser() { } void AuthorizationSessionImpl::logoutDatabase(StringData dbname) { - _authenticatedUsers.removeByDBName(dbname); + User* removedUser = _authenticatedUsers.removeByDBName(dbname); + if (removedUser) { + getAuthorizationManager().releaseUser(removedUser); + } clearImpersonatedUserData(); _buildAuthenticatedRolesVector(); } @@ -601,7 +641,7 @@ bool AuthorizationSessionImpl::isAuthorizedToCreateRole( // 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) { + for (const User* const user : _authenticatedUsers) { if (user->hasRole(args.roleName)) { return true; } @@ -773,27 +813,25 @@ bool AuthorizationSessionImpl::isAuthenticated() { void AuthorizationSessionImpl::_refreshUserInfoAsNeeded(OperationContext* opCtx) { AuthorizationManager& authMan = getAuthorizationManager(); UserSet::iterator it = _authenticatedUsers.begin(); - while (it != _authenticatedUsers.end()) { - auto& user = *it; - if (!user->isValid()) { - // The user is invalid, so make sure that we erase it from _authenticateUsers at the - // end of this block. - auto removeGuard = MakeGuard([&] { _authenticatedUsers.removeAt(it++); }); + User* user = *it; + if (!user->isValid()) { // Make a good faith effort to acquire an up-to-date user object, since the one // we've cached is marked "out-of-date." UserName name = user->getName(); - UserHandle updatedUser; + User* updatedUser; - auto swUser = authMan.acquireUser(opCtx, name); - auto& status = swUser.getStatus(); + Status status = authMan.acquireUser(opCtx, name, &updatedUser); switch (status.code()) { case ErrorCodes::OK: { - updatedUser = std::move(swUser.getValue()); + + // Verify the updated user object's authentication restrictions. + UserHolder userHolder(user, UserReleaser(&authMan, &_authenticatedUsers, it)); + UserHolder updatedUserHolder(updatedUser, UserReleaser(&authMan)); try { const auto& restrictionSet = - updatedUser->getRestrictions(); // Owned by updatedUser + updatedUserHolder->getRestrictions(); // Owned by updatedUser invariant(opCtx->getClient()); Status restrictionStatus = restrictionSet.validate( RestrictionEnvironment::get(*opCtx->getClient())); @@ -813,19 +851,24 @@ void AuthorizationSessionImpl::_refreshUserInfoAsNeeded(OperationContext* opCtx) } // Success! Replace the old User object with the updated one. - removeGuard.Dismiss(); - _authenticatedUsers.replaceAt(it, std::move(updatedUser)); + fassert(17067, + _authenticatedUsers.replaceAt(it, updatedUserHolder.release()) == + userHolder.get()); LOG(1) << "Updated session cache of user information for " << name; break; } case ErrorCodes::UserNotFound: { // User does not exist anymore; remove it from _authenticatedUsers. + fassert(17068, _authenticatedUsers.removeAt(it) == user); + authMan.releaseUser(user); log() << "Removed deleted user " << name << " from session cache of user information."; continue; // No need to advance "it" in this case. } case ErrorCodes::UnsupportedFormat: { // An auth subsystem has explicitly indicated a failure. + fassert(40555, _authenticatedUsers.removeAt(it) == user); + authMan.releaseUser(user); log() << "Removed user " << name << " from session cache of user information because of refresh failure:" << " '" << status << "'."; @@ -837,7 +880,6 @@ void AuthorizationSessionImpl::_refreshUserInfoAsNeeded(OperationContext* opCtx) warning() << "Could not fetch updated user privilege information for " << name << "; continuing to use old information. Reason is " << redact(status); - removeGuard.Dismiss(); break; } } @@ -913,7 +955,7 @@ bool AuthorizationSessionImpl::isAuthorizedForAnyActionOnResource(const Resource buildResourceSearchList(resource, resourceSearchList.data()); for (int i = 0; i < resourceSearchListLength; ++i) { - for (const auto& user : _authenticatedUsers) { + for (const User* user : _authenticatedUsers) { if (user->hasActionsForResource(resourceSearchList[i])) { return true; } @@ -947,14 +989,15 @@ bool AuthorizationSessionImpl::_isAuthorizedForPrivilege(const Privilege& privil } } - for (const auto& user : _authenticatedUsers) { + for (UserSet::iterator it = _authenticatedUsers.begin(); it != _authenticatedUsers.end(); + ++it) { + User* user = *it; for (int i = 0; i < resourceSearchListLength; ++i) { ActionSet userActions = user->getActionsForResource(resourceSearchList[i]); unmetRequirements.removeAllActionsFromSet(userActions); - if (unmetRequirements.empty()) { + if (unmetRequirements.empty()) return true; - } } } diff --git a/src/mongo/db/auth/role_graph_builtin_roles.cpp b/src/mongo/db/auth/role_graph_builtin_roles.cpp index 90c6a6e97f2..a243ba3baf6 100644 --- a/src/mongo/db/auth/role_graph_builtin_roles.cpp +++ b/src/mongo/db/auth/role_graph_builtin_roles.cpp @@ -362,9 +362,7 @@ void addUserAdminAnyDbPrivileges(PrivilegeVector* privileges) { Privilege(ResourcePattern::forClusterResource(), ActionType::invalidateUserCache)); Privilege::addPrivilegeToPrivilegeVector( privileges, Privilege(ResourcePattern::forClusterResource(), ActionType::viewUser)); - Privilege::addPrivilegeToPrivilegeVector( - privileges, - Privilege(ResourcePattern::forAnyNormalResource(), ActionType::listCachedAndActiveUsers)); + ActionSet readRoleAndIndexActions; readRoleAndIndexActions += readRoleActions; diff --git a/src/mongo/db/auth/sasl_mechanism_registry.cpp b/src/mongo/db/auth/sasl_mechanism_registry.cpp index 49d95c7c64e..cb588b9b829 100644 --- a/src/mongo/db/auth/sasl_mechanism_registry.cpp +++ b/src/mongo/db/auth/sasl_mechanism_registry.cpp @@ -84,10 +84,14 @@ void SASLServerMechanismRegistry::advertiseMechanismNamesForUser(OperationContex AuthorizationManager* authManager = AuthorizationManager::get(opCtx->getServiceContext()); - UserHandle user; - const auto swUser = authManager->acquireUser(opCtx, userName); - if (!swUser.isOK()) { - auto& status = swUser.getStatus(); + User* user = nullptr; + const auto status = authManager->acquireUser(opCtx, userName, &user); + auto guard = MakeGuard([authManager, &user] { + if (user) { + authManager->releaseUser(user); + } + }); + if (!status.isOK()) { if (status.code() == ErrorCodes::UserNotFound) { log() << "Supported SASL mechanisms requested for unknown user '" << userName << "'"; @@ -96,7 +100,6 @@ void SASLServerMechanismRegistry::advertiseMechanismNamesForUser(OperationContex uassertStatusOK(status); } - user = std::move(swUser.getValue()); BSONArrayBuilder mechanismsBuilder; auto& map = _getMapRef(userName.getDB()); @@ -108,7 +111,7 @@ void SASLServerMechanismRegistry::advertiseMechanismNamesForUser(OperationContex continue; } - if (factoryIt.second->canMakeMechanismForUser(user.get())) { + if (factoryIt.second->canMakeMechanismForUser(user)) { mechanismsBuilder << factoryIt.first; } } diff --git a/src/mongo/db/auth/sasl_plain_server_conversation.cpp b/src/mongo/db/auth/sasl_plain_server_conversation.cpp index 4dd64e4f11e..7d89c2eaeb8 100644 --- a/src/mongo/db/auth/sasl_plain_server_conversation.cpp +++ b/src/mongo/db/auth/sasl_plain_server_conversation.cpp @@ -122,16 +122,17 @@ StatusWith<std::tuple<bool, std::string>> SASLPlainServerMechanism::stepImpl( mongoutils::str::stream() << "Incorrectly formatted PLAIN client message"); } + User* userObj; // The authentication database is also the source database for the user. - auto swUser = authManager->acquireUser( - opCtx, UserName(ServerMechanismBase::_principalName, _authenticationDatabase)); + Status status = authManager->acquireUser( + opCtx, UserName(ServerMechanismBase::_principalName, _authenticationDatabase), &userObj); - if (!swUser.isOK()) { - return swUser.getStatus(); + if (!status.isOK()) { + return status; } - auto userObj = std::move(swUser.getValue()); const auto creds = userObj->getCredentials(); + authManager->releaseUser(userObj); const auto sha256Status = trySCRAM<SHA256Block>(creds, pwd->c_str()); if (!sha256Status.isOK()) { diff --git a/src/mongo/db/auth/sasl_scram_server_conversation.cpp b/src/mongo/db/auth/sasl_scram_server_conversation.cpp index cab83885352..d3ded3c88f8 100644 --- a/src/mongo/db/auth/sasl_scram_server_conversation.cpp +++ b/src/mongo/db/auth/sasl_scram_server_conversation.cpp @@ -191,17 +191,19 @@ StatusWith<std::tuple<bool, std::string>> SaslSCRAMServerMechanism<Policy>::_fir } // The authentication database is also the source database for the user. + User* userObj; auto authManager = AuthorizationManager::get(opCtx->getServiceContext()); - auto swUser = authManager->acquireUser(opCtx, user); - if (!swUser.isOK()) { - return swUser.getStatus(); + Status status = authManager->acquireUser(opCtx, user, &userObj); + if (!status.isOK()) { + return status; } - auto userObj = std::move(swUser.getValue()); User::CredentialData credentials = userObj->getCredentials(); UserName userName = userObj->getName(); + authManager->releaseUser(userObj); + _scramCredentials = credentials.scram<HashBlock>(); if (!_scramCredentials.isValid()) { diff --git a/src/mongo/db/auth/user.cpp b/src/mongo/db/auth/user.cpp index 4e25d8ab5ea..ade182208b9 100644 --- a/src/mongo/db/auth/user.cpp +++ b/src/mongo/db/auth/user.cpp @@ -51,7 +51,12 @@ SHA256Block computeDigest(const UserName& name) { } // namespace -User::User(const UserName& name) : _name(name), _digest(computeDigest(_name)) {} +User::User(const UserName& name) + : _name(name), _digest(computeDigest(_name)), _refCount(0), _isValid(1) {} + +User::~User() { + dassert(_refCount == 0); +} template <> User::SCRAMCredentials<SHA1Block>& User::CredentialData::scram<SHA1Block>() { @@ -88,9 +93,12 @@ const User::CredentialData& User::getCredentials() const { } bool User::isValid() const { - return _isValid.loadRelaxed(); + return _isValid.loadRelaxed() == 1; } +uint32_t User::getRefCount() const { + return _refCount; +} const ActionSet User::getActionsForResource(const ResourcePattern& resource) const { stdx::unordered_map<ResourcePattern, Privilege>::const_iterator it = _privileges.find(resource); @@ -162,8 +170,16 @@ void User::setRestrictions(RestrictionDocuments restrictions)& { _restrictions = std::move(restrictions); } -void User::_invalidate() { - _isValid.store(false); +void User::invalidate() { + _isValid.store(0); } +void User::incrementRefCount() { + ++_refCount; +} + +void User::decrementRefCount() { + dassert(_refCount > 0); + --_refCount; +} } // namespace mongo diff --git a/src/mongo/db/auth/user.h b/src/mongo/db/auth/user.h index a22bdca7231..b974a0a3a7c 100644 --- a/src/mongo/db/auth/user.h +++ b/src/mongo/db/auth/user.h @@ -101,6 +101,7 @@ public: typedef stdx::unordered_map<ResourcePattern, Privilege> ResourcePrivilegeMap; explicit User(const UserName& name); + ~User(); /** * Returns the user name for this user. @@ -161,6 +162,12 @@ public: */ bool isValid() const; + /** + * This returns the reference count for this User. The AuthorizationManager should be the + * only caller of this. + */ + uint32_t getRefCount() const; + // Mutators below. Mutation functions should *only* be called by the AuthorizationManager /** @@ -217,15 +224,30 @@ public: } void getRestrictions() && = delete; -protected: - friend class AuthorizationManagerImpl; /** * Marks this instance of the User object as invalid, most likely because information about * the user has been updated and needs to be reloaded from the AuthorizationManager. * * This method should *only* be called by the AuthorizationManager. */ - void _invalidate(); + void invalidate(); + + /** + * Increments the reference count for this User object, which records how many threads have + * a reference to it. + * + * This method should *only* be called by the AuthorizationManager. + */ + void incrementRefCount(); + + /** + * Decrements the reference count for this User object, which records how many threads have + * a reference to it. Once the reference count goes to zero, the AuthorizationManager is + * allowed to destroy this instance. + * + * This method should *only* be called by the AuthorizationManager. + */ + void decrementRefCount(); private: UserName _name; @@ -248,10 +270,11 @@ private: // Restrictions which must be met by a Client in order to authenticate as this user. RestrictionDocuments _restrictions; - // Indicates whether the user has been marked as invalid by the AuthorizationManager. - AtomicBool _isValid{true}; + // _refCount and _isInvalidated are modified exclusively by the AuthorizationManager + // _isInvalidated can be read by any consumer of User, but _refCount can only be + // meaningfully read by the AuthorizationManager, as _refCount is guarded by the AM's _lock + uint32_t _refCount; + AtomicUInt32 _isValid; // Using as a boolean }; -using UserHandle = std::shared_ptr<User>; - } // namespace mongo diff --git a/src/mongo/db/auth/user_set.cpp b/src/mongo/db/auth/user_set.cpp index c54a340cd10..fe8d273a2ca 100644 --- a/src/mongo/db/auth/user_set.cpp +++ b/src/mongo/db/auth/user_set.cpp @@ -27,8 +27,6 @@ #include "mongo/db/auth/user_set.h" -#include <algorithm> -#include <iostream> #include <string> #include <vector> @@ -42,8 +40,7 @@ class UserSetNameIteratorImpl : public UserNameIterator::Impl { MONGO_DISALLOW_COPYING(UserSetNameIteratorImpl); public: - UserSetNameIteratorImpl(const UserSet::const_iterator& begin, - const UserSet::const_iterator& end) + UserSetNameIteratorImpl(const UserSet::iterator& begin, const UserSet::iterator& end) : _curr(begin), _end(end) {} virtual ~UserSetNameIteratorImpl() {} virtual bool more() const { @@ -60,58 +57,78 @@ public: } private: - UserSet::const_iterator _curr; - UserSet::const_iterator _end; + UserSet::iterator _curr; + UserSet::iterator _end; }; } // namespace -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)); +UserSet::UserSet() : _users(), _usersEnd(_users.end()) {} +UserSet::~UserSet() {} + +User* UserSet::add(User* user) { + for (mutable_iterator it = mbegin(); it != mend(); ++it) { + User* current = *it; + if (current->getName().getDB() == user->getName().getDB()) { + // There can be only one user per database. + *it = user; + return current; + } + } + if (_usersEnd == _users.end()) { + _users.push_back(user); + _usersEnd = _users.end(); } else { - *it = std::move(user); + *_usersEnd = user; + ++_usersEnd; } + return NULL; } -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); +User* UserSet::removeByDBName(StringData dbname) { + for (iterator it = begin(); it != end(); ++it) { + User* current = *it; + if (current->getName().getDB() == dbname) { + return removeAt(it); + } } + return NULL; } -void UserSet::replaceAt(iterator it, UserHandle replacement) { - *it = std::move(replacement); +User* UserSet::replaceAt(iterator it, User* replacement) { + size_t offset = it - begin(); + User* old = _users[offset]; + _users[offset] = replacement; + return old; } -void UserSet::removeAt(iterator it) { - _users.erase(it); +User* UserSet::removeAt(iterator it) { + size_t offset = it - begin(); + User* old = _users[offset]; + --_usersEnd; + _users[offset] = *_usersEnd; + *_usersEnd = NULL; + return old; } -UserHandle UserSet::lookup(const UserName& name) const { - auto it = std::find_if(_users.begin(), _users.end(), [&](const auto& user) { - invariant(user); - return user->getName() == name; - }); - - return (it != _users.end()) ? *it : nullptr; +User* UserSet::lookup(const UserName& name) const { + User* user = lookupByDBName(name.getDB()); + if (user && user->getName() == name) { + return user; + } + return NULL; } -UserHandle UserSet::lookupByDBName(StringData dbname) const { - auto it = std::find_if(_users.begin(), _users.end(), [&](const auto& user) { - invariant(user); - return user->getName().getDB() == dbname; - }); - return (it != _users.end()) ? *it : nullptr; +User* UserSet::lookupByDBName(StringData dbname) const { + for (iterator it = begin(); it != end(); ++it) { + User* current = *it; + if (current->getName().getDB() == dbname) { + return current; + } + } + return NULL; } UserNameIterator UserSet::getNames() const { - return UserNameIterator( - std::make_unique<UserSetNameIteratorImpl>(_users.cbegin(), _users.cend())); + return UserNameIterator(std::make_unique<UserSetNameIteratorImpl>(begin(), end())); } } // namespace mongo diff --git a/src/mongo/db/auth/user_set.h b/src/mongo/db/auth/user_set.h index 3b1472c6c8f..9ad0ab03202 100644 --- a/src/mongo/db/auth/user_set.h +++ b/src/mongo/db/auth/user_set.h @@ -35,7 +35,7 @@ #include "mongo/db/auth/authorization_manager.h" #include "mongo/db/auth/user.h" #include "mongo/db/auth/user_name.h" -#include "mongo/stdx/list.h" + namespace mongo { @@ -48,14 +48,16 @@ class UserSet { MONGO_DISALLOW_COPYING(UserSet); public: - using iterator = stdx::list<UserHandle>::iterator; - using const_iterator = stdx::list<UserHandle>::const_iterator; + typedef std::vector<User*>::const_iterator iterator; - UserSet() = default; + UserSet(); + ~UserSet(); /** * Adds a User to the UserSet. * + * The UserSet does not take ownership of the User. + * * 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 @@ -63,54 +65,64 @@ public: * * Invalidates any outstanding iterators or NameIterators. */ - void add(UserHandle user); + User* add(User* 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); + User* replaceAt(iterator it, User* 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); + User* 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); + User* 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; + User* 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; + User* 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() { + iterator begin() const { return _users.begin(); } - iterator end() { - return _users.end(); + iterator end() const { + return _usersEnd; } private: + typedef std::vector<User*>::iterator mutable_iterator; + + mutable_iterator mbegin() { + return _users.begin(); + } + mutable_iterator mend() { + return _usersEnd; + } + // The UserSet maintains ownership of the Users in it, and is responsible for // returning them to the AuthorizationManager when done with them. - stdx::list<UserHandle> _users; + std::vector<User*> _users; + std::vector<User*>::iterator _usersEnd; }; } // namespace mongo diff --git a/src/mongo/db/auth/user_set_test.cpp b/src/mongo/db/auth/user_set_test.cpp index d8797dcc049..d8c4290d7c8 100644 --- a/src/mongo/db/auth/user_set_test.cpp +++ b/src/mongo/db/auth/user_set_test.cpp @@ -42,9 +42,13 @@ namespace { TEST(UserSetTest, BasicTest) { UserSet set; - UserHandle p1 = std::make_shared<User>(UserName("Bob", "test")); - UserHandle p2 = std::make_shared<User>(UserName("George", "test")); - UserHandle p3 = std::make_shared<User>(UserName("Bob", "test2")); + User* p1 = new User(UserName("Bob", "test")); + User* p2 = new User(UserName("George", "test")); + User* p3 = new User(UserName("Bob", "test2")); + + const std::unique_ptr<User> delp1(p1); + const std::unique_ptr<User> delp2(p2); + const std::unique_ptr<User> delp3(p3); ASSERT_NULL(set.lookup(UserName("Bob", "test"))); ASSERT_NULL(set.lookup(UserName("George", "test"))); @@ -52,7 +56,7 @@ TEST(UserSetTest, BasicTest) { ASSERT_NULL(set.lookupByDBName("test")); ASSERT_NULL(set.lookupByDBName("test2")); - set.add(p1); + ASSERT_NULL(set.add(p1)); ASSERT_EQUALS(p1, set.lookup(UserName("Bob", "test"))); ASSERT_EQUALS(p1, set.lookupByDBName("test")); @@ -61,7 +65,7 @@ TEST(UserSetTest, BasicTest) { ASSERT_NULL(set.lookupByDBName("test2")); // This should not replace the existing user "Bob" because they are different databases - set.add(p3); + ASSERT_NULL(set.add(p3)); ASSERT_EQUALS(p1, set.lookup(UserName("Bob", "test"))); ASSERT_EQUALS(p1, set.lookupByDBName("test")); @@ -69,16 +73,18 @@ TEST(UserSetTest, BasicTest) { 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 + User* replaced = set.add(p2); // This should replace Bob since they're on the same database + ASSERT_EQUALS(replaced, p1); 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); + User* removed = set.removeByDBName("test"); + ASSERT_EQUALS(removed, p2); ASSERT_NULL(set.lookup(UserName("Bob", "test"))); ASSERT_NULL(set.lookup(UserName("George", "test"))); ASSERT_NULL(set.lookupByDBName("test")); @@ -96,8 +102,8 @@ TEST(UserSetTest, IterateNames) { UserNameIterator iter = pset.getNames(); ASSERT(!iter.more()); - UserHandle user = std::make_shared<User>(UserName("bob", "test")); - pset.add(std::move(user)); + std::unique_ptr<User> user(new User(UserName("bob", "test"))); + ASSERT_NULL(pset.add(user.get())); iter = pset.getNames(); ASSERT(iter.more()); diff --git a/src/mongo/db/commands/user_management_commands.cpp b/src/mongo/db/commands/user_management_commands.cpp index 2fca9a9973f..82823e3cfcc 100644 --- a/src/mongo/db/commands/user_management_commands.cpp +++ b/src/mongo/db/commands/user_management_commands.cpp @@ -147,17 +147,17 @@ Status getCurrentUserRoles(OperationContext* opCtx, AuthorizationManager* authzManager, const UserName& userName, stdx::unordered_set<RoleName>* roles) { + User* user; authzManager->invalidateUserByName(userName); // Need to make sure cache entry is up to date - auto swUser = authzManager->acquireUser(opCtx, userName); - if (!swUser.isOK()) { - return swUser.getStatus(); + Status status = authzManager->acquireUser(opCtx, userName, &user); + if (!status.isOK()) { + return status; } - auto user = std::move(swUser.getValue()); - RoleNameIterator rolesIt = user->getRoles(); while (rolesIt.more()) { roles->insert(rolesIt.next()); } + authzManager->releaseUser(user); return Status::OK(); } diff --git a/src/mongo/db/kill_sessions.cpp b/src/mongo/db/kill_sessions.cpp index 1d83b587d89..1ac38112c3d 100644 --- a/src/mongo/db/kill_sessions.cpp +++ b/src/mongo/db/kill_sessions.cpp @@ -110,10 +110,12 @@ KillAllSessionsByPattern makeKillAllSessionsByPattern(OperationContext* opCtx, auto authMgr = AuthorizationManager::get(opCtx->getServiceContext()); + User* user; UserName un(kasu.getUser(), kasu.getDb()); - auto user = uassertStatusOK(authMgr->acquireUser(opCtx, un)); + uassertStatusOK(authMgr->acquireUser(opCtx, un, &user)); kasbp.setUid(user->getDigest()); + authMgr->releaseUser(user); return kasbp; } diff --git a/src/mongo/db/pipeline/SConscript b/src/mongo/db/pipeline/SConscript index 0195c38f596..a9bad067648 100644 --- a/src/mongo/db/pipeline/SConscript +++ b/src/mongo/db/pipeline/SConscript @@ -287,7 +287,6 @@ pipelineeEnv.Library( 'document_source_internal_inhibit_optimization.cpp', 'document_source_internal_split_pipeline.cpp', 'document_source_limit.cpp', - 'document_source_list_cached_and_active_users.cpp', 'document_source_list_local_cursors.cpp', 'document_source_list_local_sessions.cpp', 'document_source_list_sessions.cpp', diff --git a/src/mongo/db/pipeline/document_source_list_cached_and_active_users.cpp b/src/mongo/db/pipeline/document_source_list_cached_and_active_users.cpp deleted file mode 100644 index 0c9fd53fefe..00000000000 --- a/src/mongo/db/pipeline/document_source_list_cached_and_active_users.cpp +++ /dev/null @@ -1,83 +0,0 @@ -/** - * Copyright (C) 2018 MongoDB Inc. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - * - * As a special exception, the copyright holders give permission to link the - * code of portions of this program with the OpenSSL library under certain - * conditions as described in each individual source file and distribute - * linked combinations including the program with the OpenSSL library. You - * must comply with the GNU Affero General Public License in all respects - * for all of the code used other than as permitted herein. If you modify - * file(s) with this exception, you may extend this exception to your - * version of the file(s), but you are not obligated to do so. If you do not - * wish to do so, delete this exception statement from your version. If you - * delete this exception statement from all source files in the program, - * then also delete it in the license file. - */ - -#include "mongo/platform/basic.h" - -#include "mongo/db/pipeline/document_source_list_cached_and_active_users.h" - -#include "mongo/db/auth/authorization_session.h" -#include "mongo/db/auth/user_name.h" -#include "mongo/db/commands/test_commands_enabled.h" -#include "mongo/db/logical_session_id_helpers.h" - -namespace mongo { - -REGISTER_TEST_DOCUMENT_SOURCE(listCachedAndActiveUsers, - DocumentSourceListCachedAndActiveUsers::LiteParsed::parse, - DocumentSourceListCachedAndActiveUsers::createFromBson); - -const char* DocumentSourceListCachedAndActiveUsers::kStageName = "$listCachedAndActiveUsers"; - -DocumentSource::GetNextResult DocumentSourceListCachedAndActiveUsers::getNext() { - pExpCtx->checkForInterrupt(); - - if (!_users.empty()) { - const auto info = std::move(_users.back()); - _users.pop_back(); - return Document(BSON("username" << info.userName.getUser() << "db" << info.userName.getDB() - << "active" - << info.active)); - } - - return GetNextResult::makeEOF(); -} - -boost::intrusive_ptr<DocumentSource> DocumentSourceListCachedAndActiveUsers::createFromBson( - BSONElement spec, const boost::intrusive_ptr<ExpressionContext>& pExpCtx) { - - uassert( - ErrorCodes::InvalidNamespace, - str::stream() << kStageName - << " must be run against the database with {aggregate: 1}, not a collection", - pExpCtx->ns.isCollectionlessAggregateNS()); - - uassert(ErrorCodes::BadValue, - str::stream() << kStageName << " must be run as { " << kStageName << ": {}}", - spec.isABSONObj() && spec.Obj().isEmpty()); - - return new DocumentSourceListCachedAndActiveUsers(pExpCtx); -} - -DocumentSourceListCachedAndActiveUsers::DocumentSourceListCachedAndActiveUsers( - const boost::intrusive_ptr<ExpressionContext>& pExpCtx) - : DocumentSource(pExpCtx), _users() { - auto authMgr = AuthorizationManager::get(pExpCtx->opCtx->getServiceContext()); - _users = authMgr->getUserCacheInfo(); -} - -} // namespace mongo diff --git a/src/mongo/db/pipeline/document_source_list_cached_and_active_users.h b/src/mongo/db/pipeline/document_source_list_cached_and_active_users.h deleted file mode 100644 index c46fe59cbaa..00000000000 --- a/src/mongo/db/pipeline/document_source_list_cached_and_active_users.h +++ /dev/null @@ -1,113 +0,0 @@ -/** - * Copyright (C) 2018 MongoDB Inc. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - * - * As a special exception, the copyright holders give permission to link the - * code of portions of this program with the OpenSSL library under certain - * conditions as described in each individual source file and distribute - * linked combinations including the program with the OpenSSL library. You - * must comply with the GNU Affero General Public License in all respects - * for all of the code used other than as permitted herein. If you modify - * file(s) with this exception, you may extend this exception to your - * version of the file(s), but you are not obligated to do so. If you do not - * wish to do so, delete this exception statement from your version. If you - * delete this exception statement from all source files in the program, - * then also delete it in the license file. - */ - -#pragma once - -#include "mongo/bson/bsonmisc.h" -#include "mongo/bson/bsonobj.h" -#include "mongo/db/auth/authorization_manager.h" -#include "mongo/db/pipeline/document_source.h" -#include "mongo/db/pipeline/lite_parsed_document_source.h" - -namespace mongo { - -/* - * This implements an aggregation document source that lists the active/cached users in the - * authorization manager. It is intended for diagnostic and reporting purposes. - */ -class DocumentSourceListCachedAndActiveUsers final : public DocumentSource { -public: - static const char* kStageName; - - class LiteParsed final : public LiteParsedDocumentSource { - public: - static std::unique_ptr<LiteParsed> parse(const AggregationRequest& request, - const BSONElement& spec) { - return stdx::make_unique<LiteParsed>(); - } - - stdx::unordered_set<NamespaceString> getInvolvedNamespaces() const final { - return stdx::unordered_set<NamespaceString>(); - } - - PrivilegeVector requiredPrivileges(bool isMongos) const final { - return {Privilege(ResourcePattern::forAnyNormalResource(), - ActionType::listCachedAndActiveUsers)}; - } - - bool isInitialSource() const final { - return true; - } - - bool allowedToForwardFromMongos() const final { - return false; - } - - void assertSupportsReadConcern(const repl::ReadConcernArgs& readConcern) const { - uassert(ErrorCodes::InvalidOptions, - str::stream() << "Aggregation stage " << kStageName << " cannot run with a " - << "readConcern other than 'local', or in a multi-document " - << "transaction. Current readConcern: " - << readConcern.toString(), - readConcern.getLevel() == repl::ReadConcernLevel::kLocalReadConcern); - } - }; - - GetNextResult getNext() final; - - const char* getSourceName() const final { - return kStageName; - } - - Value serialize(boost::optional<ExplainOptions::Verbosity> explain = boost::none) const final { - return Value(Document{{getSourceName(), Document{}}}); - } - - StageConstraints constraints(Pipeline::SplitState pipeState) const final { - StageConstraints constraints(StreamType::kStreaming, - PositionRequirement::kFirst, - HostTypeRequirement::kLocalOnly, - DiskUseRequirement::kNoDiskUse, - FacetRequirement::kNotAllowed, - TransactionRequirement::kNotAllowed); - - constraints.isIndependentOfAnyCollection = true; - constraints.requiresInputDocSource = false; - return constraints; - } - - static boost::intrusive_ptr<DocumentSource> createFromBson( - BSONElement elem, const boost::intrusive_ptr<ExpressionContext>& pExpCtx); - -private: - DocumentSourceListCachedAndActiveUsers(const boost::intrusive_ptr<ExpressionContext>& pExpCtx); - - std::vector<AuthorizationManager::CachedUserInfo> _users; -}; - -} // namespace mongo |