Permalink
Browse files

SERVER-7125 Implicit privilege acquisition.

With this patch, a user from one databases granted privileges in another
database automatically acquires the privileges on the latter database when
attempting to perform operations thereon.
  • Loading branch information...
1 parent 0950dfc commit 5f7f17708b5b2de1c6b6625376abb78a957e10c7 @andy10gen andy10gen committed Dec 17, 2012
@@ -0,0 +1,90 @@
+// Test implicit privilege acquisition.
+//
+// TODO: Rewrite user document creation portion of test when addUser shell helper is updated.
+
+// Raises an exception if "status" is not a GetLastError object indicating success.
+function assertGLEOK(status) {
+ assert(status.ok && status.err === null,
+ "Expected OK status object; found " + tojson(status));
+}
+
+// Raises an exception if "status" is not a GetLastError object indicating failure.
+function assertGLENotOK(status) {
+ assert(status.ok && status.err !== null,
+ "Expected not-OK status object; found " + tojson(status));
+}
+
+// Asserts that inserting "obj" into "collection" succeeds.
+function assertInsertSucceeds(collection, obj) {
+ collection.insert(obj);
+ assertGLEOK(collection.getDB().getLastErrorObj());
+}
+
+// Asserts that inserting "obj" into "collection" fails.
+function assertInsertFails(collection, obj) {
+ collection.insert(obj);
+ assertGLENotOK(collection.getDB().getLastErrorObj());
+}
+
+// Runs the function "action" with database objects for every database named in "dbNames", using
+// "conn" as the connection object.
+function withDbs(conn, dbNames, action) {
+ var dbs = [];
+ var i;
+ for (i = 0; i < dbNames.length; ++i) {
+ dbs.push(conn.getDB(dbNames[i]));
+ }
+ action.apply(null, dbs);
+}
+
+var conn = MongoRunner.runMongod({ auth: "", smallfiles: "" });
+var admin = conn.getDB("admin");
+var test = conn.getDB("test");
+var test2 = conn.getDB("test2");
+
+assertInsertSucceeds(admin.system.users,
+ { user: 'root',
+ pwd: hex_md5('root:mongo:a'),
+ roles: ["clusterAdmin",
+ "serverAdmin",
+ "readWriteAnyDatabase",
+ "dbAdminAnyDatabase",
+ "userAdminAnyDatabase"]
+ });
+
+var andyUserDocumentTestDb = {
+ user: "andy",
+ pwd: hex_md5("andy:mongo:a"),
+ roles: [ "readWrite" ]
+};
+
+var andyUserDocumentTest2Db = {
+ user: "andy",
+ userSource: "test",
+ roles: [ "read" ]
+};
+
+assertInsertFails(test.foo, {});
+assertInsertFails(test.system.users, andyUserDocumentTestDb);
+assert.throws(function() { test.foo.findOne(); });
+assert.throws(function() { test2.foo.findOne(); } );
+
+assert(admin.auth('root', 'a'));
+assertInsertSucceeds(test.system.users, andyUserDocumentTestDb);
+assertInsertSucceeds(test2.system.users, andyUserDocumentTest2Db);
+assertInsertSucceeds(test.foo, {_id: 0});
+assertInsertSucceeds(test2.foo, {_id: 0});
+
+admin.logout();
+
+assert(test.auth('andy', 'a'));
+assertInsertSucceeds(test.foo, {_id: 1});
+assertInsertFails(test2.foo, {_id: 1});
+assert.eq(test.foo.findOne({_id: 1})._id, 1);
+assert.eq(test2.foo.findOne({_id: 0})._id, 0);
+assert(test.logout());
+assertInsertFails(test.foo, {});
+assertInsertFails(test.system.users, andyUserDocumentTestDb);
+assert.throws(function() { test.foo.findOne(); });
+assert.throws(function() { test2.foo.findOne(); } );
+
@@ -52,9 +52,9 @@ namespace mongo {
//
// On success, returns Status::OK() and stores a shared-ownership copy of the document into
// "result".
- virtual Status getPrivilegeDocument(const std::string& dbname,
- const PrincipalName& principalName,
- BSONObj* result);
+ Status getPrivilegeDocument(const std::string& dbname,
+ const PrincipalName& principalName,
+ BSONObj* result);
protected:
AuthExternalState(); // This class should never be instantiated directly.
@@ -39,15 +39,9 @@ namespace mongo {
_returnValue = returnValue;
}
- virtual Status getPrivilegeDocument(const string& dbname,
- const PrincipalName& user,
- BSONObj* result) {
- return Status(ErrorCodes::InternalError, "Not Implemented!");
- }
-
virtual bool _findUser(const std::string& usersNamespace,
- const BSONObj& query,
- BSONObj* result) const {
+ const BSONObj& query,
+ BSONObj* result) const {
return false;
}
@@ -32,6 +32,8 @@
#include "mongo/db/jsobj.h"
#include "mongo/db/namespacestring.h"
#include "mongo/db/security_common.h"
+#include "mongo/util/assert_util.h"
+#include "mongo/util/log.h"
#include "mongo/util/mongoutils/str.h"
namespace mongo {
@@ -357,6 +359,27 @@ namespace {
void AuthorizationManager::addAuthorizedPrincipal(Principal* principal) {
_authenticatedPrincipals.add(principal);
+ if (!principal->isImplicitPrivilegeAcquisitionEnabled())
+ return;
+ _acquirePrivilegesForPrincipalFromDatabase(ADMIN_DBNAME, principal->getName());
+ principal->markDatabaseAsProbed(ADMIN_DBNAME);
+ const std::string dbname = principal->getName().getDB().toString();
+ _acquirePrivilegesForPrincipalFromDatabase(dbname, principal->getName());
+ principal->markDatabaseAsProbed(dbname);
+ }
+
+ void AuthorizationManager::_acquirePrivilegesForPrincipalFromDatabase(
+ const std::string& dbname, const PrincipalName& principal) {
+
+ BSONObj privilegeDocument;
+ Status status = getPrivilegeDocument(dbname, principal, &privilegeDocument);
+ if (status.isOK()) {
+ status = acquirePrivilegesFromPrivilegeDocument(dbname, principal, privilegeDocument);
+ }
+ if (!status.isOK() && status != ErrorCodes::UserNotFound) {
+ log() << "Privilege acquisition failed for " << principal << " in database " <<
+ dbname << ": " << status.reason() << " (" << status.codeString() << ")" << endl;
+ }
}
Principal* AuthorizationManager::lookupPrincipal(const PrincipalName& name) {
@@ -675,15 +698,22 @@ namespace {
Status AuthorizationManager::checkAuthForUpdate(const std::string& ns, bool upsert) {
NamespaceString namespaceString(ns);
- if (!checkAuthorization(ns, ActionType::update)) {
- return Status(ErrorCodes::Unauthorized,
- mongoutils::str::stream() << "not authorized for update on " << ns,
- 0);
+ if (!upsert) {
+ if (!checkAuthorization(ns, ActionType::update)) {
+ return Status(ErrorCodes::Unauthorized,
+ mongoutils::str::stream() << "not authorized for update on " << ns,
+ 0);
+ }
}
- if (upsert && !checkAuthorization(ns, ActionType::insert)) {
- return Status(ErrorCodes::Unauthorized,
- mongoutils::str::stream() << "not authorized for upsert on " << ns,
- 0);
+ else {
+ ActionSet required;
+ required.addAction(ActionType::update);
+ required.addAction(ActionType::insert);
+ if (!checkAuthorization(ns, required)) {
+ return Status(ErrorCodes::Unauthorized,
+ mongoutils::str::stream() << "not authorized for upsert on " << ns,
+ 0);
+ }
}
return Status::OK();
}
@@ -724,27 +754,43 @@ namespace {
if (_externalState->shouldIgnoreAuthChecks())
return Status::OK();
- Privilege modifiedPrivilege = _modifyPrivilegeForSpecialCases(privilege);
- if (!_acquiredPrivileges.hasPrivilege(modifiedPrivilege))
- return Status(ErrorCodes::Unauthorized, "unauthorized", 0);
-
- return Status::OK();
+ return _probeForPrivilege(privilege);
}
Status AuthorizationManager::checkAuthForPrivileges(const vector<Privilege>& privileges) {
if (_externalState->shouldIgnoreAuthChecks())
return Status::OK();
- vector<Privilege> modifiedPrivileges;
- for (vector<Privilege>::const_iterator it = privileges.begin(); it != privileges.end();
- ++it) {
- modifiedPrivileges.push_back(_modifyPrivilegeForSpecialCases(*it));
+ for (size_t i = 0; i < privileges.size(); ++i) {
+ Status status = _probeForPrivilege(privileges[i]);
+ if (!status.isOK())
+ return status;
}
- if (!_acquiredPrivileges.hasPrivileges(modifiedPrivileges))
- return Status(ErrorCodes::Unauthorized, "unauthorized", 0);
-
return Status::OK();
}
+ Status AuthorizationManager::_probeForPrivilege(const Privilege& privilege) {
+ Privilege modifiedPrivilege = _modifyPrivilegeForSpecialCases(privilege);
+ if (_acquiredPrivileges.hasPrivilege(modifiedPrivilege))
+ return Status::OK();
+
+ std::string dbname = nsToDatabase(modifiedPrivilege.getResource());
+ for (PrincipalSet::iterator iter = _authenticatedPrincipals.begin(),
+ end = _authenticatedPrincipals.end();
+ iter != end; ++iter) {
+
+ Principal* principal = *iter;
+ if (!principal->isImplicitPrivilegeAcquisitionEnabled())
+ continue;
+ if (principal->isDatabaseProbed(dbname))
+ continue;
+ _acquirePrivilegesForPrincipalFromDatabase(dbname, principal->getName());
+ principal->markDatabaseAsProbed(dbname);
+ if (_acquiredPrivileges.hasPrivilege(modifiedPrivilege))
+ return Status::OK();
+ }
+ return Status(ErrorCodes::Unauthorized, "unauthorized", 0);
+ }
+
} // namespace mongo
@@ -78,7 +78,7 @@ namespace mongo {
// TODO: try to eliminate the need for this call.
void startRequest();
- // Takes ownership of the principal (by putting into _authenticatedPrincipals).
+ // Adds "principal" to the authorization manager, and takes ownership of it.
void addAuthorizedPrincipal(Principal* principal);
// Returns the authenticated principal with the given name. Returns NULL
@@ -169,6 +169,14 @@ namespace mongo {
static ActionSet getAllUserActions();
private:
+ // Finds the set of privileges attributed to "principal" in database "dbname",
+ // and adds them to the set of acquired privileges.
+ void _acquirePrivilegesForPrincipalFromDatabase(const std::string& dbname,
+ const PrincipalName& principal);
+
+ // Checks to see if the given privilege is allowed, performing implicit privilege
+ // acquisition if enabled and necessary to resolve the privilege.
+ Status _probeForPrivilege(const Privilege& privilege);
// Parses the old-style (pre 2.4) privilege document and returns a PrivilegeSet of all the
// Privileges that the privilege document grants.
@@ -21,7 +21,9 @@
#include "mongo/db/auth/auth_external_state_mock.h"
#include "mongo/db/auth/authorization_manager.h"
#include "mongo/db/jsobj.h"
+#include "mongo/db/namespacestring.h"
#include "mongo/unittest/unittest.h"
+#include "mongo/util/map_util.h"
#define ASSERT_NULL(EXPR) ASSERT_FALSE(EXPR)
#define ASSERT_NON_NULL(EXPR) ASSERT_TRUE(EXPR)
@@ -470,5 +472,99 @@ namespace {
"roles" << BSONArrayBuilder().arr())));
}
+
+ class AuthExternalStateImplictPriv : public AuthExternalStateMock {
+ public:
+ virtual bool _findUser(const string& usersNamespace,
+ const BSONObj& query,
+ BSONObj* result) const {
+
+ NamespaceString nsstring(usersNamespace);
+ std::string user = query[AuthorizationManager::USER_NAME_FIELD_NAME].String();
+ std::string userSource;
+ if (!query[AuthorizationManager::USER_SOURCE_FIELD_NAME].trueValue()) {
+ userSource = nsstring.db;
+ }
+ else {
+ userSource = query[AuthorizationManager::USER_SOURCE_FIELD_NAME].String();
+ }
+ *result = mapFindWithDefault(_privilegeDocs,
+ std::make_pair(nsstring.db,
+ PrincipalName(user, userSource)),
+ BSON("invalid" << 1));
+ return !(*result)["invalid"].trueValue();
+ }
+
+ void addPrivilegeDocument(const string& dbname,
+ const PrincipalName& user,
+ const BSONObj& doc) {
+
+ ASSERT(_privilegeDocs.insert(std::make_pair(std::make_pair(dbname, user),
+ doc.getOwned())).second);
+ }
+
+ private:
+ std::map<std::pair<std::string, PrincipalName>, BSONObj > _privilegeDocs;
+ };
+
+ class ImplicitPriviligesTest : public ::mongo::unittest::Test {
+ public:
+ AuthExternalStateImplictPriv* state;
+ scoped_ptr<AuthorizationManager> authman;
+
+ void setUp() {
+ state = new AuthExternalStateImplictPriv;
+ authman.reset(new AuthorizationManager(state));
+ }
+ };
+
+ TEST_F(ImplicitPriviligesTest, ImplicitAcquireFromSomeDatabases) {
+ state->addPrivilegeDocument("test", PrincipalName("andy", "test"),
+ BSON("user" << "andy" <<
+ "pwd" << "a" <<
+ "roles" << BSON_ARRAY("readWrite")));
+ state->addPrivilegeDocument("test2", PrincipalName("andy", "test"),
+ BSON("user" << "andy" <<
+ "userSource" << "test" <<
+ "roles" << BSON_ARRAY("read")));
+ state->addPrivilegeDocument("admin", PrincipalName("andy", "test"),
+ BSON("user" << "andy" <<
+ "userSource" << "test" <<
+ "roles" << BSON_ARRAY("serverAdmin") <<
+ "otherDBRoles" << BSON("test3" << BSON_ARRAY("dbAdmin"))));
+
+ ASSERT(!authman->checkAuthorization("test.foo", ActionType::find));
+ ASSERT(!authman->checkAuthorization("test.foo", ActionType::insert));
+ ASSERT(!authman->checkAuthorization("test.foo", ActionType::collMod));
+ ASSERT(!authman->checkAuthorization("test2.foo", ActionType::find));
+ ASSERT(!authman->checkAuthorization("test2.foo", ActionType::insert));
+ ASSERT(!authman->checkAuthorization("test2.foo", ActionType::collMod));
+ ASSERT(!authman->checkAuthorization("test3.foo", ActionType::find));
+ ASSERT(!authman->checkAuthorization("test3.foo", ActionType::insert));
+ ASSERT(!authman->checkAuthorization("test3.foo", ActionType::collMod));
+ ASSERT(!authman->checkAuthorization("admin.foo", ActionType::find));
+ ASSERT(!authman->checkAuthorization("admin.foo", ActionType::insert));
+ ASSERT(!authman->checkAuthorization("admin.foo", ActionType::collMod));
+ ASSERT(!authman->checkAuthorization("$SERVER", ActionType::shutdown));
+
+ Principal* principal = new Principal(PrincipalName("andy", "test"));
+ principal->setImplicitPrivilegeAcquisition(true);
+ authman->addAuthorizedPrincipal(principal);
+
+ ASSERT(authman->checkAuthorization("test.foo", ActionType::find));
+ ASSERT(authman->checkAuthorization("test.foo", ActionType::insert));
+ ASSERT(!authman->checkAuthorization("test.foo", ActionType::collMod));
+ ASSERT(authman->checkAuthorization("test2.foo", ActionType::find));
+ ASSERT(!authman->checkAuthorization("test2.foo", ActionType::insert));
+ ASSERT(!authman->checkAuthorization("test2.foo", ActionType::collMod));
+ ASSERT(!authman->checkAuthorization("test3.foo", ActionType::find));
+ ASSERT(!authman->checkAuthorization("test3.foo", ActionType::insert));
+ ASSERT(authman->checkAuthorization("test3.foo", ActionType::collMod));
+ ASSERT(!authman->checkAuthorization("admin.foo", ActionType::find));
+ ASSERT(!authman->checkAuthorization("admin.foo", ActionType::insert));
+ ASSERT(!authman->checkAuthorization("admin.foo", ActionType::collMod));
+ ASSERT(authman->checkAuthorization("$SERVER", ActionType::shutdown));
+ }
+
} // namespace
} // namespace mongo
Oops, something went wrong. Retry.

0 comments on commit 5f7f177

Please sign in to comment.