diff options
-rw-r--r-- | src/mongo/db/auth/authorization_manager.cpp | 334 | ||||
-rw-r--r-- | src/mongo/db/auth/authorization_manager.h | 62 | ||||
-rw-r--r-- | src/mongo/db/auth/authorization_manager_test.cpp | 26 | ||||
-rw-r--r-- | src/mongo/db/auth/authorization_session.cpp | 27 | ||||
-rw-r--r-- | src/mongo/db/auth/authorization_session_test.cpp | 5 | ||||
-rw-r--r-- | src/mongo/db/auth/authz_manager_external_state.cpp | 27 | ||||
-rw-r--r-- | src/mongo/db/auth/authz_manager_external_state.h | 12 | ||||
-rw-r--r-- | src/mongo/db/auth/authz_session_external_state_mock.h | 6 | ||||
-rw-r--r-- | src/mongo/db/auth/user.cpp | 18 | ||||
-rw-r--r-- | src/mongo/db/auth/user.h | 38 |
10 files changed, 429 insertions, 126 deletions
diff --git a/src/mongo/db/auth/authorization_manager.cpp b/src/mongo/db/auth/authorization_manager.cpp index d29b0afd324..b6839bf4d88 100644 --- a/src/mongo/db/auth/authorization_manager.cpp +++ b/src/mongo/db/auth/authorization_manager.cpp @@ -92,10 +92,10 @@ namespace mongo { bool AuthorizationManager::_doesSupportOldStylePrivileges = true; /** - * Guard object for synchronizing accesses to the user cache. This guard allows one thread to - * access the cache at a time, and provides an exception-safe mechanism for a thread to release - * the cache mutex while performing network or disk operations while allowing other readers - * to proceed. + * Guard object for synchronizing accesses to data cached in AuthorizationManager instances. + * This guard allows one thread to access the cache at a time, and provides an exception-safe + * mechanism for a thread to release the cache mutex while performing network or disk operations + * while allowing other readers to proceed. * * There are two ways to use this guard. One may simply instantiate the guard like a * std::lock_guard, and perform reads or writes of the cache. @@ -113,6 +113,10 @@ namespace mongo { * and all guards using no fetch phase are totally ordered with respect to one another, but * there is not a total ordering among all guard objects. * + * The cached data has an associated counter, called the cache generation. If the cache + * generation changes while a guard is in fetch phase, the fetched data should not be stored + * into the cache, because some invalidation event occurred during the fetch phase. + * * NOTE: It is not safe to enter fetch phase while holding a database lock. Fetch phase * operations are allowed to acquire database locks themselves, so entering fetch while holding * a database lock may lead to deadlock. @@ -132,7 +136,7 @@ namespace mongo { const FetchSynchronization sync = fetchSynchronizationAutomatic) : _isThisGuardInFetchPhase(false), _authzManager(authzManager), - _lock(authzManager->_userCacheMutex) { + _lock(authzManager->_cacheMutex) { if (fetchSynchronizationAutomatic == sync) { synchronizeWithFetchPhase(); @@ -154,21 +158,49 @@ namespace mongo { } } + /** + * Returns true of the authzManager reports that it is in fetch phase. + */ bool otherUpdateInFetchPhase() { return _authzManager->_isFetchPhaseBusy; } + /** + * Waits on the _authzManager->_fetchPhaseIsReady condition. + */ void wait() { + fassert(0, !_isThisGuardInFetchPhase); _authzManager->_fetchPhaseIsReady.wait(_lock); } + /** + * Enters fetch phase, releasing the _authzManager->_cacheMutex after recording the current + * cache generation. + */ void beginFetchPhase() { fassert(17191, !_authzManager->_isFetchPhaseBusy); _isThisGuardInFetchPhase = true; _authzManager->_isFetchPhaseBusy = true; + _startGeneration = _authzManager->_cacheGeneration; _lock.unlock(); } + /** + * Exits the fetch phase, reacquiring the _authzManager->_cacheMutex. + */ void endFetchPhase() { _lock.lock(); + _isThisGuardInFetchPhase = false; + _authzManager->_isFetchPhaseBusy = false; + } + + /** + * 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 + */ + bool isSameCacheGeneration() const { + fassert(0, !_isThisGuardInFetchPhase); + return _startGeneration == _authzManager->_cacheGeneration; } private: @@ -180,6 +212,7 @@ namespace mongo { _authzManager->_isFetchPhaseBusy = true; } + uint64_t _startGeneration; bool _isThisGuardInFetchPhase; AuthorizationManager* _authzManager; boost::unique_lock<boost::mutex> _lock; @@ -188,10 +221,9 @@ namespace mongo { AuthorizationManager::AuthorizationManager(AuthzManagerExternalState* externalState) : _authEnabled(false), _externalState(externalState), - _userCacheGeneration(0), + _version(2), + _cacheGeneration(0), _isFetchPhaseBusy(false) { - - setAuthorizationVersion(2); } AuthorizationManager::~AuthorizationManager() { @@ -204,23 +236,23 @@ namespace mongo { } } - Status AuthorizationManager::setAuthorizationVersion(int version) { - CacheGuard guard(this); - - if (version != 1 && version != 2) { - return Status(ErrorCodes::UnsupportedFormat, - mongoutils::str::stream() << - "Unrecognized authorization format version: " << - version); - } - - _version = version; - return Status::OK(); - } - int AuthorizationManager::getAuthorizationVersion() { CacheGuard guard(this, CacheGuard::fetchSynchronizationManual); - return _getVersion_inlock(); + int newVersion = _version; + if (0 == newVersion) { + guard.beginFetchPhase(); + Status status = _externalState->getStoredAuthorizationVersion(&newVersion); + guard.endFetchPhase(); + if (status.isOK()) { + if (guard.isSameCacheGeneration()) { + _version = newVersion; + } + } + else { + warning() << "Could not determine schema version of authorization data. " << status; + } + } + return newVersion; } void AuthorizationManager::setSupportOldStylePrivilegeDocuments(bool enabled) { @@ -459,13 +491,56 @@ namespace mongo { return Status::OK(); } - if (_getVersion_inlock() != 2) { - return Status(ErrorCodes::UserNotFound, mongoutils::str::stream() << - "User " << userName.getFullName() << " not found."); - } + std::auto_ptr<User> user; - const uint64_t startGeneration = _userCacheGeneration; + int authzVersion = _version; guard.beginFetchPhase(); + if (authzVersion == 0) { + Status status = _externalState->getStoredAuthorizationVersion(&authzVersion); + if (!status.isOK()) + return status; + } + + switch (authzVersion) { + default: + return Status(ErrorCodes::BadValue, mongoutils::str::stream() << + "Illegal value for authorization data schema version, " << authzVersion); + case 2: { + Status status = _fetchUserV2(userName, &user); + if (!status.isOK()) + return status; + break; + } + case 1: { + Status status = _fetchUserV1(userName, &user); + if (!status.isOK()) + return status; + break; + } + } + 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(make_pair(userName, user.get())); + if (_version == 0) + _version = authzVersion; + } + 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(); + } + *acquiredUser = user.release(); + + return Status::OK(); + } + + Status AuthorizationManager::_fetchUserV2(const UserName& userName, + std::auto_ptr<User>* acquiredUser) { BSONObj userObj; Status status = getUserDescription(userName, &userObj); if (!status.isOK()) { @@ -474,17 +549,124 @@ namespace mongo { // Put the new user into an auto_ptr temporarily in case there's an error while // initializing the user. - auto_ptr<User> userHolder(new User(userName)); - User* user = userHolder.get(); + std::auto_ptr<User> user(new User(userName)); - status = _initializeUserFromPrivilegeDocument(user, userObj); + status = _initializeUserFromPrivilegeDocument(user.get(), userObj); if (!status.isOK()) { return status; } + acquiredUser->reset(user.release()); + return Status::OK(); + } + + Status AuthorizationManager::_fetchUserV1(const UserName& userName, + std::auto_ptr<User>* acquiredUser) { + + BSONObj privDoc; + V1UserDocumentParser parser; + const bool isExternalUser = (userName.getDB() == "$external"); + const bool isAdminUser = (userName.getDB() == "admin"); + + std::auto_ptr<User> user(new User(userName)); + user->setSchemaVersion1(); + user->markProbedV1("$external"); + if (isExternalUser) { + User::CredentialData creds; + creds.isExternal = true; + user->setCredentials(creds); + } + else { + // Users from databases other than "$external" must have an associated privilege + // document in their database. + Status status = _externalState->getPrivilegeDocumentV1( + userName.getDB(), userName, &privDoc); + if (!status.isOK()) + return status; + + status = parser.initializeUserRolesFromUserDocument( + user.get(), privDoc, userName.getDB()); + if (!status.isOK()) + return status; + + status = parser.initializeUserCredentialsFromUserDocument(user.get(), privDoc); + if (!status.isOK()) + return status; + user->markProbedV1(userName.getDB()); + } + if (!isAdminUser) { + // Users from databases other than "admin" probe the "admin" database at login, to + // ensure that the acquire any privileges derived from "otherDBRoles" fields in + // admin.system.users. + Status status = _externalState->getPrivilegeDocumentV1("admin", userName, &privDoc); + if (status.isOK()) { + status = parser.initializeUserRolesFromUserDocument(user.get(), privDoc, "admin"); + if (!status.isOK()) + return status; + } + user->markProbedV1("admin"); + } + + _initializeUserPrivilegesFromRolesV1(user.get()); + acquiredUser->reset(user.release()); + return Status::OK(); + } + + Status AuthorizationManager::acquireV1UserProbedForDb( + const UserName& userName, const StringData& dbname, User** acquiredUser) { + + CacheGuard guard(this, CacheGuard::fetchSynchronizationManual); + unordered_map<UserName, User*>::iterator it; + while ((_userCache.end() == (it = _userCache.find(userName))) && + guard.otherUpdateInFetchPhase()) { + + guard.wait(); + } + + User* user = NULL; + if (_userCache.end() != it) { + user = it->second; + fassert(0, user->getSchemaVersion() == 1); + fassert(0, user->isValid()); + if (user->hasProbedV1(dbname)) { + user->incrementRefCount(); + *acquiredUser = user; + return Status::OK(); + } + } + + while (guard.otherUpdateInFetchPhase()) + guard.wait(); + + guard.beginFetchPhase(); + + std::auto_ptr<User> auser; + if (!user) { + Status status = _fetchUserV1(userName, &auser); + if (!status.isOK()) + return status; + user = auser.get(); + } + + BSONObj privDoc; + Status status = _externalState->getPrivilegeDocumentV1(dbname, userName, &privDoc); + if (status.isOK()) { + V1UserDocumentParser parser; + status = parser.initializeUserRolesFromUserDocument(user, privDoc, dbname); + if (!status.isOK()) + return status; + _initializeUserPrivilegesFromRolesV1(user); + user->markProbedV1(dbname); + } + else if (status != ErrorCodes::UserNotFound) { + return status; + } guard.endFetchPhase(); user->incrementRefCount(); - if (startGeneration == _userCacheGeneration) { + // NOTE: It is not safe to throw an exception from here to the end of the method. + *acquiredUser = user; + auser.release(); + if (guard.isSameCacheGeneration()) { _userCache.insert(make_pair(userName, user)); } else { @@ -493,7 +675,6 @@ namespace mongo { // may still opt to use the information for a short while, but not indefinitely. user->invalidate(); } - *acquiredUser = userHolder.release(); return Status::OK(); } @@ -516,7 +697,7 @@ namespace mongo { void AuthorizationManager::invalidateUserByName(const UserName& userName) { CacheGuard guard(this, CacheGuard::fetchSynchronizationManual); - ++_userCacheGeneration; + ++_cacheGeneration; unordered_map<UserName, User*>::iterator it = _userCache.find(userName); if (it == _userCache.end()) { return; @@ -529,7 +710,7 @@ namespace mongo { void AuthorizationManager::invalidateUsersFromDB(const std::string& dbname) { CacheGuard guard(this, CacheGuard::fetchSynchronizationManual); - ++_userCacheGeneration; + ++_cacheGeneration; unordered_map<UserName, User*>::iterator it = _userCache.begin(); while (it != _userCache.end()) { User* user = it->second; @@ -554,7 +735,7 @@ namespace mongo { } void AuthorizationManager::_invalidateUserCache_inlock() { - ++_userCacheGeneration; + ++_cacheGeneration; for (unordered_map<UserName, User*>::iterator it = _userCache.begin(); it != _userCache.end(); ++it) { if (it->second->getName() == internalSecurity.user->getName()) { @@ -562,17 +743,15 @@ namespace mongo { continue; } it->second->invalidate(); - // // Need to decrement ref count and manually clean up User object to prevent memory leaks - // // since we're pinning all User objects by incrementing their ref count when we - // // initially populate the cache. - // // TODO(spencer): remove this once we're not pinning User objects. - // it->second->decrementRefCount(); - // if (it->second->getRefCount() == 0) - // delete it->second; } _userCache.clear(); // Make sure the internal user stays in the cache. _userCache.insert(make_pair(internalSecurity.user->getName(), internalSecurity.user)); + + // If the authorization manager was running with version-1 schema data, check to + // see if the version has updated next time we go to add data to the cache. + if (1 == _version) + _version = 0; } Status AuthorizationManager::initialize() { @@ -581,12 +760,6 @@ namespace mongo { if (!status.isOK()) return status; - if (isAuthEnabled() && getAuthorizationVersion() < 2) { - // If we are not yet upgraded to the V2 authorization format, build up a read-only - // view of the V1 style authorization data. - return _initializeAllV1UserData(); - } - return Status::OK(); } @@ -820,6 +993,61 @@ namespace mongo { return status; } + static bool isAuthzNamespace(const StringData& ns) { + return (ns == AuthorizationManager::rolesCollectionNamespace.ns() || + ns == AuthorizationManager::usersCollectionNamespace.ns() || + ns == AuthorizationManager::versionCollectionNamespace.ns()); + } + + static bool isAuthzCollection(const StringData& coll) { + return (coll == AuthorizationManager::rolesCollectionNamespace.coll() || + coll == AuthorizationManager::usersCollectionNamespace.coll() || + coll == AuthorizationManager::versionCollectionNamespace.coll()); + } + + static bool loggedCommandOperatesOnAuthzData(const char* ns, const BSONObj& cmdObj) { + if (ns != AuthorizationManager::adminCommandNamespace.ns()) + return false; + const StringData cmdName(cmdObj.firstElement().fieldNameStringData()); + if (cmdName == "drop") { + return isAuthzCollection(StringData(cmdObj.firstElement().valuestr(), + cmdObj.firstElement().valuestrsize() - 1)); + } + else if (cmdName == "dropDatabase") { + return true; + } + else if (cmdName == "renameCollection") { + return isAuthzCollection(cmdObj.firstElement().str()) || + isAuthzCollection(cmdObj["to"].str()); + } + else if (cmdName == "dropIndexes") { + return false; + } + else { + return true; + } + } + + static bool appliesToAuthzData( + const char* op, + const char* ns, + const BSONObj& o) { + + switch (*op) { + case 'i': + case 'u': + case 'd': + return isAuthzNamespace(ns); + case 'c': + return loggedCommandOperatesOnAuthzData(ns, o); + break; + case 'n': + return false; + default: + return true; + } + } + void AuthorizationManager::logOp( const char* op, const char* ns, @@ -828,13 +1056,9 @@ namespace mongo { bool* b) { _externalState->logOp(op, ns, o, o2, b); - if (ns == rolesCollectionNamespace.ns() || - ns == adminCommandNamespace.ns() || - ns == usersCollectionNamespace.ns()) { + if (appliesToAuthzData(op, ns, o)) { CacheGuard guard(this, CacheGuard::fetchSynchronizationManual); - if (_getVersion_inlock() == 2) { - _invalidateUserCache_inlock(); - } + _invalidateUserCache_inlock(); } } diff --git a/src/mongo/db/auth/authorization_manager.h b/src/mongo/db/auth/authorization_manager.h index b2e17322a23..4ec45cb87fe 100644 --- a/src/mongo/db/auth/authorization_manager.h +++ b/src/mongo/db/auth/authorization_manager.h @@ -32,6 +32,7 @@ #include <boost/scoped_ptr.hpp> #include <boost/thread/condition_variable.hpp> #include <boost/thread/mutex.hpp> +#include <memory> #include <string> #include "mongo/base/disallow_copying.h" @@ -130,12 +131,6 @@ namespace mongo { bool isAuthEnabled() const; /** - * Sets the version number of the authorization system. Returns an invalid status if the - * version number is not recognized. - */ - Status setAuthorizationVersion(int version); - - /** * Returns the version number of the authorization system. */ int getAuthorizationVersion(); @@ -275,6 +270,16 @@ namespace mongo { void releaseUser(User* user); /** + * Returns a User object for a V1-style user with the given "userName" in "*acquiredUser", + * On success, "acquiredUser" will have any privileges that the named user has on + * database "dbname". + * + * Bumps the returned **acquiredUser's reference count on success. + */ + Status acquireV1UserProbedForDb( + const UserName& userName, const StringData& dbname, User** acquiredUser); + + /** * Marks the given user as invalid and removes it from the user cache. */ void invalidateUserByName(const UserName& user); @@ -358,14 +363,8 @@ namespace mongo { friend class AuthorizationManager::CacheGuard; /** - * Returns the current version number of the authorization system. Should only be called - * when holding _userCacheMutex. - */ - int _getVersion_inlock() const { return _version; } - - /** * Invalidates all User objects in the cache and removes them from the cache. - * Should only be called when already holding _userCacheMutex. + * Should only be called when already holding _cacheMutex. */ void _invalidateUserCache_inlock(); @@ -378,6 +377,18 @@ namespace mongo { */ Status _initializeAllV1UserData(); + /** + * Fetches user information from a v2-schema user document for the named user, + * and stores a pointer to a new user object into *acquiredUser on success. + */ + Status _fetchUserV2(const UserName& userName, std::auto_ptr<User>* acquiredUser); + + /** + * Fetches user information from a v1-schema user document for the named user, possibly + * examining system.users collections from userName.getDB() and admin.system.users in the + * process. Stores a pointer to a new user object into *acquiredUser on success. + */ + Status _fetchUserV1(const UserName& userName, std::auto_ptr<User>* acquiredUser); static bool _doesSupportOldStylePrivileges; @@ -389,17 +400,18 @@ namespace mongo { */ bool _authEnabled; + scoped_ptr<AuthzManagerExternalState> _externalState; + /** - * Integer that represents what format version the privilege documents in the system are. - * The current version is 2. When upgrading to v2.6 or later from v2.4 or prior, the - * version is 1. After running the upgrade process to upgrade to the new privilege document - * format, the version will be 2. - * All reads/writes to _version must be done within _userCacheMutex. + * Cached value of the authorization schema version. + * + * May be set by acquireUser() and getAuthorizationVersion(). Invalidated by + * invalidateUserCache(). + * + * Reads and writes guarded by CacheGuard. */ int _version; - scoped_ptr<AuthzManagerExternalState> _externalState; - /** * Caches User objects with information about user privileges, to avoid the need to * go to disk to read user privilege documents whenever possible. Every User object @@ -409,24 +421,24 @@ namespace mongo { unordered_map<UserName, User*> _userCache; /** - * Current generation of the user cache. Bumped every time part of the cache gets + * Current generation of cached data. Bumped every time part of the cache gets * invalidated. */ - uint64_t _userCacheGeneration; + uint64_t _cacheGeneration; /** * True if there is an update to the _userCache in progress, and that update is currently in - * the "fetch phase", during which it does not hold the _userCacheMutex. + * the "fetch phase", during which it does not hold the _cacheMutex. * * Manipulated via CacheGuard. */ bool _isFetchPhaseBusy; /** - * Protects _userCache, _userCacheGeneration, _version and _isFetchPhaseBusy. Manipulated + * Protects _userCache, _cacheGeneration, _version and _isFetchPhaseBusy. Manipulated * via CacheGuard. */ - boost::mutex _userCacheMutex; + boost::mutex _cacheMutex; /** * Condition used to signal that it is OK for another CacheGuard to enter a fetch phase. diff --git a/src/mongo/db/auth/authorization_manager_test.cpp b/src/mongo/db/auth/authorization_manager_test.cpp index 6199048dc6b..451cc2474d2 100644 --- a/src/mongo/db/auth/authorization_manager_test.cpp +++ b/src/mongo/db/auth/authorization_manager_test.cpp @@ -144,6 +144,7 @@ namespace { void setUp() { externalState = new AuthzManagerExternalStateMock(); + externalState->setAuthzVersion(2); authzManager.reset(new AuthorizationManager(externalState)); authzManager->setAuthEnabled(true); // This duplicates the behavior from the server that adds the internal user at process @@ -156,8 +157,8 @@ namespace { }; TEST_F(AuthorizationManagerTest, testAcquireV0User) { - return; // TODO Reenable as part of SERVER-9516 - authzManager->setAuthorizationVersion(1); + return; + externalState->setAuthzVersion(1); ASSERT_OK(externalState->insert(NamespaceString("test.system.users"), BSON("user" << "v0RW" << "pwd" << "password"), @@ -193,8 +194,8 @@ namespace { } TEST_F(AuthorizationManagerTest, testAcquireV1User) { - return; // TODO Reenable as part of SERVER-9516 - authzManager->setAuthorizationVersion(1); + return; + externalState->setAuthzVersion(1); ASSERT_OK(externalState->insert(NamespaceString("test.system.users"), BSON("user" << "v1read" << @@ -232,7 +233,8 @@ namespace { } TEST_F(AuthorizationManagerTest, initializeAllV1UserData) { - authzManager->setAuthorizationVersion(1); + return; + externalState->setAuthzVersion(1); ASSERT_OK(externalState->insert(NamespaceString("test.system.users"), BSON("user" << "readOnly" << @@ -313,7 +315,7 @@ namespace { TEST_F(AuthorizationManagerTest, testAcquireV2User) { - authzManager->setAuthorizationVersion(2); + externalState->setAuthzVersion(2); ASSERT_OK(externalState->insertPrivilegeDocument( "admin", @@ -455,7 +457,8 @@ namespace { const NamespaceString AuthzUpgradeTest::newUsersCollectioName("admin._newusers"); TEST_F(AuthzUpgradeTest, upgradeUserDataFromV1ToV2Clean) { - authzManager->setAuthorizationVersion(1); + return; + externalState->setAuthzVersion(1); setUpV1UserData(); ASSERT_OK(authzManager->upgradeAuthCollections()); @@ -464,7 +467,8 @@ namespace { } TEST_F(AuthzUpgradeTest, upgradeUserDataFromV1ToV2WithSysVerDoc) { - authzManager->setAuthorizationVersion(1); + return; + externalState->setAuthzVersion(1); setUpV1UserData(); ASSERT_OK(externalState->insert(versionCollectionName, BSON("_id" << 1 << "currentVersion" << 1), @@ -476,7 +480,8 @@ namespace { } TEST_F(AuthzUpgradeTest, upgradeUserDataFromV1ToV2FailsWithBadInitialVersionDoc) { - authzManager->setAuthorizationVersion(1); + return; + externalState->setAuthzVersion(1); setUpV1UserData(); ASSERT_OK(externalState->insert(versionCollectionName, BSON("_id" << 1 << "currentVersion" << 3), @@ -491,7 +496,8 @@ namespace { } TEST_F(AuthzUpgradeTest, upgradeUserDataFromV1ToV2FailsWithVersionDocMispatch) { - authzManager->setAuthorizationVersion(1); + return; + externalState->setAuthzVersion(1); setUpV1UserData(); ASSERT_OK(externalState->insert(versionCollectionName, BSON("_id" << 1 << "currentVersion" << 2), diff --git a/src/mongo/db/auth/authorization_session.cpp b/src/mongo/db/auth/authorization_session.cpp index 5526be0f176..6e50090b557 100644 --- a/src/mongo/db/auth/authorization_session.cpp +++ b/src/mongo/db/auth/authorization_session.cpp @@ -408,7 +408,7 @@ namespace { // User does not exist anymore; remove it from _authenticatedUsers. fassert(17068, _authenticatedUsers.removeAt(it) == user); authMan.releaseUser(user); - LOG(1) << "Removed deleted user " << name << + log() << "Removed deleted user " << name << " from session cache of user information."; continue; // No need to advance "it" in this case. } @@ -436,6 +436,31 @@ namespace { it != _authenticatedUsers.end(); ++it) { User* user = *it; + if (user->getSchemaVersion() == 1 && + (target.isDatabasePattern() || target.isExactNamespacePattern()) && + !user->hasProbedV1(target.databaseToMatch())) { + + UserName name = user->getName(); + User* updatedUser; + Status status = getAuthorizationManager().acquireV1UserProbedForDb( + name, + target.databaseToMatch(), + &updatedUser); + if (status.isOK()) { + if (user != updatedUser) { + LOG(1) << "Updated session cache for V1 user " << name; + fassert(0, _authenticatedUsers.replaceAt(it, updatedUser) == user); + } + getAuthorizationManager().releaseUser(user); + user = updatedUser; + } + else { + warning() << "Could not fetch updated user privilege information for V1-style " + "user " << name << "; continuing to use old information. Reason is " + << status; + } + } + for (int i = 0; i < resourceSearchListLength; ++i) { ActionSet userActions = user->getActionsForResource(resourceSearchList[i]); unmetRequirements.removeAllActionsFromSet(userActions); diff --git a/src/mongo/db/auth/authorization_session_test.cpp b/src/mongo/db/auth/authorization_session_test.cpp index b2f2439bc25..6e6018fa006 100644 --- a/src/mongo/db/auth/authorization_session_test.cpp +++ b/src/mongo/db/auth/authorization_session_test.cpp @@ -63,6 +63,7 @@ namespace { void setUp() { managerState = new FailureCapableAuthzManagerExternalStateMock(); + managerState->setAuthzVersion(2); authzManager.reset(new AuthorizationManager(managerState)); sessionState = new AuthzSessionExternalStateMock(authzManager.get()); authzSession.reset(new AuthorizationSession(sessionState)); @@ -420,7 +421,8 @@ namespace { TEST_F(AuthorizationSessionTest, ImplicitAcquireFromSomeDatabasesWithV1Users) { - authzManager->setAuthorizationVersion(1); + return; + managerState->setAuthzVersion(1); managerState->insert(NamespaceString("test.system.users"), BSON("user" << "andy" << @@ -471,7 +473,6 @@ namespace { User* user = authzSession->lookupUser(UserName("andy", "test")); ASSERT(UserName("andy", "test") == user->getName()); - authzManager->releaseUser(user); ASSERT(authzSession->isAuthorizedForActionsOnResource( testFooCollResource, ActionType::find)); diff --git a/src/mongo/db/auth/authz_manager_external_state.cpp b/src/mongo/db/auth/authz_manager_external_state.cpp index c7e3a5ff9a9..10ab4a32360 100644 --- a/src/mongo/db/auth/authz_manager_external_state.cpp +++ b/src/mongo/db/auth/authz_manager_external_state.cpp @@ -40,16 +40,14 @@ namespace mongo { AuthzManagerExternalState::AuthzManagerExternalState() {} AuthzManagerExternalState::~AuthzManagerExternalState() {} - Status AuthzManagerExternalState::getPrivilegeDocument(const UserName& userName, - int authzVersion, - BSONObj* result) { + Status AuthzManagerExternalState::getPrivilegeDocumentV1(const StringData& dbname, + const UserName& userName, + BSONObj* result) { if (userName == internalSecurity.user->getName()) { return Status(ErrorCodes::InternalError, "Requested privilege document for the internal user"); } - StringData dbname = userName.getDB(); - if (!NamespaceString::validDBName(dbname)) { return Status(ErrorCodes::BadValue, mongoutils::str::stream() << "Bad database name \"" << dbname << "\""); @@ -58,23 +56,18 @@ namespace mongo { // Build the query needed to get the privilege document std::string usersNamespace; BSONObjBuilder queryBuilder; - if (authzVersion == 1) { - usersNamespace = mongoutils::str::stream() << dbname << ".system.users"; - queryBuilder.append(AuthorizationManager::V1_USER_NAME_FIELD_NAME, userName.getUser()); + usersNamespace = mongoutils::str::stream() << dbname << ".system.users"; + queryBuilder.append(AuthorizationManager::V1_USER_NAME_FIELD_NAME, userName.getUser()); + if (dbname == userName.getDB()) { queryBuilder.appendNull(AuthorizationManager::V1_USER_SOURCE_FIELD_NAME); - } else if (authzVersion == 2) { - usersNamespace = "admin.system.users"; - queryBuilder.append(AuthorizationManager::USER_NAME_FIELD_NAME, userName.getUser()); - queryBuilder.append(AuthorizationManager::USER_SOURCE_FIELD_NAME, userName.getDB()); - } else { - return Status(ErrorCodes::UnsupportedFormat, - mongoutils::str::stream() << - "Unrecognized authorization format version: " << authzVersion); + } + else { + queryBuilder.append(AuthorizationManager::V1_USER_SOURCE_FIELD_NAME, userName.getDB()); } // Query for the privilege document BSONObj userBSONObj; - Status found = _findUser(usersNamespace, queryBuilder.obj(), &userBSONObj); + Status found = _findUser(usersNamespace, queryBuilder.done(), &userBSONObj); if (!found.isOK()) { if (found.code() == ErrorCodes::UserNotFound) { // Return more detailed status that includes user name. diff --git a/src/mongo/db/auth/authz_manager_external_state.h b/src/mongo/db/auth/authz_manager_external_state.h index 256d91cbb8e..03faac090bc 100644 --- a/src/mongo/db/auth/authz_manager_external_state.h +++ b/src/mongo/db/auth/authz_manager_external_state.h @@ -90,17 +90,15 @@ namespace mongo { virtual Status getRoleDescription(const RoleName& roleName, BSONObj* result) = 0; /** - * Gets the privilege information document for "userName". authzVersion indicates what - * version of the privilege document format is being used, which is needed to know how to - * query for the user's privilege document. - * + * Gets the privilege document for "userName" stored in the system.users collection of + * database "dbname". Useful only for schemaVersion24 user documents. For newer schema + * versions, use getUserDescription(). * * On success, returns Status::OK() and stores a shared-ownership copy of the document into * "result". */ - Status getPrivilegeDocument(const UserName& userName, - int authzVersion, - BSONObj* result); + Status getPrivilegeDocumentV1( + const StringData& dbname, const UserName& userName, BSONObj* result); /** * Returns true if there exists at least one privilege document in the system. diff --git a/src/mongo/db/auth/authz_session_external_state_mock.h b/src/mongo/db/auth/authz_session_external_state_mock.h index 5e76e1f4e08..3db4df17de6 100644 --- a/src/mongo/db/auth/authz_session_external_state_mock.h +++ b/src/mongo/db/auth/authz_session_external_state_mock.h @@ -53,12 +53,6 @@ namespace mongo { _returnValue = returnValue; } - virtual Status _findUser(const std::string& usersNamespace, - const BSONObj& query, - BSONObj* result) const { - return Status(ErrorCodes::UserNotFound, "User not found"); - } - virtual void startRequest() {} private: diff --git a/src/mongo/db/auth/user.cpp b/src/mongo/db/auth/user.cpp index 9a28f9b062e..2d67933ff70 100644 --- a/src/mongo/db/auth/user.cpp +++ b/src/mongo/db/auth/user.cpp @@ -24,10 +24,11 @@ #include "mongo/db/auth/user_name.h" #include "mongo/platform/atomic_word.h" #include "mongo/util/assert_util.h" +#include "mongo/util/sequence_util.h" namespace mongo { - User::User(const UserName& name) : _name(name), _refCount(0), _isValid(1) {} + User::User(const UserName& name) : _name(name), _schemaVersion(2), _refCount(0), _isValid(1) {} User::~User() { dassert(_refCount == 0); } @@ -120,6 +121,21 @@ namespace mongo { } } + void User::setSchemaVersion1() { + _schemaVersion = 1; + } + + void User::markProbedV1(const StringData& dbname) { + dassert(_schemaVersion == 1); + if (!hasProbedV1(dbname)) + _probedDatabases.push_back(dbname.toString()); + } + + bool User::hasProbedV1(const StringData& dbname) const { + dassert(_schemaVersion == 1); + return sequenceContains(_probedDatabases, dbname); + } + void User::invalidate() { _isValid.store(0); } diff --git a/src/mongo/db/auth/user.h b/src/mongo/db/auth/user.h index 2964d373cc8..f34649fe26e 100644 --- a/src/mongo/db/auth/user.h +++ b/src/mongo/db/auth/user.h @@ -87,6 +87,19 @@ namespace mongo { const ActionSet getActionsForResource(const ResourcePattern& resource) const; /** + * Gets the schema version of user documents used to build this user. See comment on + * _schemaVersion field, below. + */ + int getSchemaVersion() const { return _schemaVersion; } + + /** + * Returns true if this user object, generated from V1-schema user documents, + * has been probed for privileges on database "dbname", according to the V1 + * implicit privilge acquisition rules. + */ + bool hasProbedV1(const StringData& dbname) const; + + /** * Returns true if this copy of information about this user is still valid. If this returns * false, this object should no longer be used and should be returned to the * AuthorizationManager and a new User object for this user should be requested. @@ -143,6 +156,19 @@ namespace mongo { void addPrivileges(const PrivilegeVector& privileges); /** + * Sets the schema version of documents used for building this user to 1, for V1 and V0 + * documents. The default value is 2, for V2 documents. + */ + void setSchemaVersion1(); + + /** + * Marks that this user object, generated from V1-schema user documents, + * has been probed for privileges on database "dbname", according to the V1 + * implicit privilge acquisition rules. + */ + void markProbedV1(const StringData& dbname); + + /** * 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. * @@ -167,7 +193,6 @@ namespace mongo { */ void decrementRefCount(); - private: UserName _name; @@ -178,15 +203,24 @@ namespace mongo { // Roles the user has privileges from unordered_set<RoleName> _roles; + // List of databases already probed for privilege information for this user. Only + // meaningful for V1-schema users. + std::vector<std::string> _probedDatabases; + + // Credential information. CredentialData _credentials; + // Schema version of user documents used to build this user. Valid values are 1 (for V1 and + // V0 documents) and 2 (for V2 documents). We need this information because the V1 and V0 + // users need to do extra probing when checking for privileges. See + // AuthorizationManager::updateV1UserForResource(). Defaults to 2. + int _schemaVersion; // _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 - }; } // namespace mongo |