summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndy Schwerin <schwerin@10gen.com>2013-10-25 14:17:44 -0400
committerAndy Schwerin <schwerin@10gen.com>2013-10-25 15:10:43 -0400
commitea84e033a8c61d492e322cd7de69331aa09058d9 (patch)
treef367e642af896e8b2463f6816c3c49279140cba6
parent5ab631f4f4e64d8c336474f42d61a87d547804ef (diff)
downloadmongo-ea84e033a8c61d492e322cd7de69331aa09058d9.tar.gz
SERVER-9516 Support schemaV24 users, for use prior to auth schema upgrade.
To enable this support, one must change the _version field when constructing the AuthorizationManager, which is not possible in this change set. A separate change will introduce proper version detection and writing of admin.system.version, at which point this functionality can be enabled.
-rw-r--r--src/mongo/db/auth/authorization_manager.cpp334
-rw-r--r--src/mongo/db/auth/authorization_manager.h62
-rw-r--r--src/mongo/db/auth/authorization_manager_test.cpp26
-rw-r--r--src/mongo/db/auth/authorization_session.cpp27
-rw-r--r--src/mongo/db/auth/authorization_session_test.cpp5
-rw-r--r--src/mongo/db/auth/authz_manager_external_state.cpp27
-rw-r--r--src/mongo/db/auth/authz_manager_external_state.h12
-rw-r--r--src/mongo/db/auth/authz_session_external_state_mock.h6
-rw-r--r--src/mongo/db/auth/user.cpp18
-rw-r--r--src/mongo/db/auth/user.h38
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