diff options
-rw-r--r-- | jstests/role_management_helpers.js | 29 | ||||
-rw-r--r-- | src/mongo/db/auth/authorization_manager.cpp | 16 | ||||
-rw-r--r-- | src/mongo/db/auth/authorization_manager.h | 27 | ||||
-rw-r--r-- | src/mongo/db/auth/authz_manager_external_state.h | 29 | ||||
-rw-r--r-- | src/mongo/db/auth/authz_manager_external_state_local.cpp | 47 | ||||
-rw-r--r-- | src/mongo/db/auth/authz_manager_external_state_local.h | 11 | ||||
-rw-r--r-- | src/mongo/db/auth/authz_manager_external_state_s.cpp | 33 | ||||
-rw-r--r-- | src/mongo/db/auth/authz_manager_external_state_s.h | 8 | ||||
-rw-r--r-- | src/mongo/db/auth/role_graph.cpp | 16 | ||||
-rw-r--r-- | src/mongo/db/auth/role_graph.h | 16 | ||||
-rw-r--r-- | src/mongo/db/auth/role_graph_builtin_roles.cpp | 23 | ||||
-rw-r--r-- | src/mongo/db/auth/role_graph_test.cpp | 50 | ||||
-rw-r--r-- | src/mongo/db/auth/role_name.h | 5 | ||||
-rw-r--r-- | src/mongo/db/auth/user_management_commands_parser.cpp | 28 | ||||
-rw-r--r-- | src/mongo/db/auth/user_management_commands_parser.h | 12 | ||||
-rw-r--r-- | src/mongo/db/commands/user_management_commands.cpp | 83 | ||||
-rw-r--r-- | src/mongo/shell/db.js | 17 |
17 files changed, 382 insertions, 68 deletions
diff --git a/jstests/role_management_helpers.js b/jstests/role_management_helpers.js index 3bf773e480a..08403a209da 100644 --- a/jstests/role_management_helpers.js +++ b/jstests/role_management_helpers.js @@ -45,16 +45,31 @@ function assertHasPrivilege(privilegeArray, privilege) { // Test getRole var roleObj = db.getRole("roleA"); assert.eq(0, roleObj.roles.length); + assert.eq(null, roleObj.privileges); + roleObj = db.getRole("roleA", {showPrivileges: true}); assert.eq(1, roleObj.privileges.length); assertHasPrivilege(roleObj.privileges, {resource: {db:db.getName(), collection:"foo"}, actions:['find']}); - roleObj = db.getRole("roleB"); + roleObj = db.getRole("roleB", {showPrivileges: true}); assert.eq(1, roleObj.privileges.length); // inherited from roleA assertHasPrivilege(roleObj.privileges, {resource: {db:db.getName(), collection:"foo"}, actions:['find']}); assert.eq(1, roleObj.roles.length); assertHasRole(roleObj.roles, "roleA", db.getName()); + // Test getRoles + var roles = db.getRoles(); + assert.eq(3, roles.length); + printjson(roles); + assert(roles[0].role == 'roleA' || roles[1].role == 'roleA' || roles[2].role == 'roleA'); + assert(roles[0].role == 'roleB' || roles[1].role == 'roleB' || roles[2].role == 'roleB'); + assert(roles[0].role == 'roleC' || roles[1].role == 'roleC' || roles[2].role == 'roleC'); + assert.eq(null, roles[0].privileges); + var roles = db.getRoles({showPrivileges: true, showBuiltinRoles: true}); + assert.eq(8, roles.length); + assert.neq(null, roles[0].privileges); + + // Granting roles to nonexistent role fails assert.throws(function() { db.grantRolesToRole("fakeRole", ['dbAdmin']); }); // Granting roles to built-in role fails @@ -62,14 +77,14 @@ function assertHasPrivilege(privilegeArray, privilege) { // Granting non-existant role fails assert.throws(function() { db.grantRolesToRole("roleB", ['dbAdmin', 'fakeRole']); }); - roleObj = db.getRole("roleB"); + roleObj = db.getRole("roleB", {showPrivileges: true}); assert.eq(1, roleObj.privileges.length); assert.eq(1, roleObj.roles.length); assertHasRole(roleObj.roles, "roleA", db.getName()); // Granting a role you already have is no problem db.grantRolesToRole("roleB", ['readWrite', 'roleC']); - roleObj = db.getRole("roleB"); + roleObj = db.getRole("roleB", {showPrivileges: true}); assert.gt(roleObj.privileges.length, 1); // Got privileges from readWrite role assert.eq(3, roleObj.roles.length); assertHasRole(roleObj.roles, "readWrite", db.getName()); @@ -78,7 +93,7 @@ function assertHasPrivilege(privilegeArray, privilege) { // Revoking roles the role doesn't have is fine db.revokeRolesFromRole("roleB", ['roleA', 'readWrite', 'dbAdmin']); - roleObj = db.getRole("roleB"); + roleObj = db.getRole("roleB", {showPrivileges: true}); assert.eq(0, roleObj.privileges.length); assert.eq(1, roleObj.roles.length); assertHasRole(roleObj.roles, "roleC", db.getName()); @@ -87,7 +102,7 @@ function assertHasPrivilege(privilegeArray, privilege) { db.grantPrivilegesToRole("roleA", [{resource: {db:db.getName(), collection:""}, actions:['dropDatabase']}, {resource: {db:db.getName(), collection:"foo"}, actions:['insert']}]); - roleObj = db.getRole("roleA"); + roleObj = db.getRole("roleA", {showPrivileges: true}); assert.eq(0, roleObj.roles.length); assert.eq(2, roleObj.privileges.length); assertHasPrivilege(roleObj.privileges, @@ -99,7 +114,7 @@ function assertHasPrivilege(privilegeArray, privilege) { db.updateRole("roleA", {roles:['roleB'], privileges:[{resource: {db: db.getName(), collection:"foo"}, actions:['find']}]}); - roleObj = db.getRole("roleA"); + roleObj = db.getRole("roleA", {showPrivileges: true}); assert.eq(1, roleObj.roles.length); assertHasRole(roleObj.roles, "roleB", db.getName()); assert.eq(1, roleObj.privileges.length); @@ -109,7 +124,7 @@ function assertHasPrivilege(privilegeArray, privilege) { // Test dropRole db.dropRole('roleC'); assert.throws(function() {db.getRole('roleC')}); - roleObj = db.getRole("roleB"); + roleObj = db.getRole("roleB", {showPrivileges: true}); assert.eq(0, roleObj.privileges.length); assert.eq(0, roleObj.roles.length); diff --git a/src/mongo/db/auth/authorization_manager.cpp b/src/mongo/db/auth/authorization_manager.cpp index d21f5f9a9f7..19ecbd1ad81 100644 --- a/src/mongo/db/auth/authorization_manager.cpp +++ b/src/mongo/db/auth/authorization_manager.cpp @@ -530,8 +530,20 @@ namespace mongo { return _externalState->getUserDescription(userName, result); } - Status AuthorizationManager::getRoleDescription(const RoleName& roleName, BSONObj* result) { - return _externalState->getRoleDescription(roleName, result); + Status AuthorizationManager::getRoleDescription(const RoleName& roleName, + bool showPrivileges, + BSONObj* result) { + return _externalState->getRoleDescription(roleName, showPrivileges, result); + } + + Status AuthorizationManager::getRoleDescriptionsForDB(const std::string dbname, + bool showPrivileges, + bool showBuiltinRoles, + vector<BSONObj>* result) { + return _externalState->getRoleDescriptionsForDB(dbname, + showPrivileges, + showBuiltinRoles, + result); } Status AuthorizationManager::acquireUser(const UserName& userName, User** acquiredUser) { diff --git a/src/mongo/db/auth/authorization_manager.h b/src/mongo/db/auth/authorization_manager.h index 6f078ca15e6..976dd7aa232 100644 --- a/src/mongo/db/auth/authorization_manager.h +++ b/src/mongo/db/auth/authorization_manager.h @@ -290,15 +290,34 @@ namespace mongo { /** * Writes into "result" a document describing the named role and returns Status::OK(). The - * description includes the role's in which the named role has membership, a full list of - * the role's privileges, and a full list of the roles of which the named role is a member, - * including those roles memberships held implicitly through other roles (indirect roles). + * description includes the roles in which the named role has membership and a full list of + * the roles of which the named role is a member, including those roles memberships held + * implicitly through other roles (indirect roles). If "showPrivileges" is true, then the + * description documents will also include a full list of the role's privileges. * In the event that some of this information is inconsistent, the document will contain a * "warnings" array, with string messages describing inconsistencies. * * If the role does not exist, returns ErrorCodes::RoleNotFound. */ - Status getRoleDescription(const RoleName& roleName, BSONObj* result); + Status getRoleDescription(const RoleName& roleName, bool showPrivileges, BSONObj* result); + + /** + * Writes into "result" documents describing the roles that are defined on the given + * database. Each role description document includes the other roles in which the role has + * membership and a full list of the roles of which the named role is a member, + * including those roles memberships held implicitly through other roles (indirect roles). + * If showPrivileges is true, then the description documents will also include a full list + * of the role's privileges. If showBuiltinRoles is true, then the result array will + * contain description documents for all the builtin roles for the given database, if it + * is false the result will just include user defined roles. + * In the event that some of the information in a given role description is inconsistent, + * the document will contain a "warnings" array, with string messages describing + * inconsistencies. + */ + Status getRoleDescriptionsForDB(const std::string dbname, + bool showPrivileges, + bool showBuiltinRoles, + vector<BSONObj>* result); /** * Returns the User object for the given userName in the out parameter "acquiredUser". diff --git a/src/mongo/db/auth/authz_manager_external_state.h b/src/mongo/db/auth/authz_manager_external_state.h index a885c4589be..f233d8dc39c 100644 --- a/src/mongo/db/auth/authz_manager_external_state.h +++ b/src/mongo/db/auth/authz_manager_external_state.h @@ -79,15 +79,36 @@ namespace mongo { /** * Writes into "result" a document describing the named role and returns Status::OK(). The - * description includes the role's in which the named role has membership, a full list of - * the role's privileges, and a full list of the roles of which the named role is a member, - * including those roles memberships held implicitly through other roles (indirect roles). + * description includes the roles in which the named role has membership and a full list of + * the roles of which the named role is a member, including those roles memberships held + * implicitly through other roles (indirect roles). If "showPrivileges" is true, then the + * description documents will also include a full list of the role's privileges. * In the event that some of this information is inconsistent, the document will contain a * "warnings" array, with string messages describing inconsistencies. * * If the role does not exist, returns ErrorCodes::RoleNotFound. */ - virtual Status getRoleDescription(const RoleName& roleName, BSONObj* result) = 0; + virtual Status getRoleDescription(const RoleName& roleName, + bool showPrivileges, + BSONObj* result) = 0; + + /** + * Writes into "result" documents describing the roles that are defined on the given + * database. Each role description document includes the other roles in which the role has + * membership and a full list of the roles of which the named role is a member, + * including those roles memberships held implicitly through other roles (indirect roles). + * If showPrivileges is true, then the description documents will also include a full list + * of the role's privileges. If showBuiltinRoles is true, then the result array will + * contain description documents for all the builtin roles for the given database, if it + * is false the result will just include user defined roles. + * In the event that some of the information in a given role description is inconsistent, + * the document will contain a "warnings" array, with string messages describing + * inconsistencies. + */ + virtual Status getRoleDescriptionsForDB(const std::string dbname, + bool showPrivileges, + bool showBuiltinRoles, + vector<BSONObj>* result) = 0; /** * Gets the privilege document for "userName" stored in the system.users collection of diff --git a/src/mongo/db/auth/authz_manager_external_state_local.cpp b/src/mongo/db/auth/authz_manager_external_state_local.cpp index 13cf991bda7..458612d2acd 100644 --- a/src/mongo/db/auth/authz_manager_external_state_local.cpp +++ b/src/mongo/db/auth/authz_manager_external_state_local.cpp @@ -204,10 +204,16 @@ namespace { return Status::OK(); } - Status AuthzManagerExternalStateLocal::getRoleDescription( - const RoleName& roleName, - BSONObj* result) { + Status AuthzManagerExternalStateLocal::getRoleDescription(const RoleName& roleName, + bool showPrivileges, + BSONObj* result) { boost::lock_guard<boost::mutex> lk(_roleGraphMutex); + return _getRoleDescription_inlock(roleName, showPrivileges, result); + } + + Status AuthzManagerExternalStateLocal::_getRoleDescription_inlock(const RoleName& roleName, + bool showPrivileges, + BSONObj* result) { if (!_roleGraph.roleExists(roleName)) return Status(ErrorCodes::RoleNotFound, "No role named " + roleName.toString()); @@ -221,17 +227,23 @@ namespace { mutablebson::Element indirectRolesElement = resultDoc.makeElementArray("indirectRoles"); fassert(17165, resultDoc.root().pushBack(indirectRolesElement)); mutablebson::Element privilegesElement = resultDoc.makeElementArray("privileges"); - fassert(17166, resultDoc.root().pushBack(privilegesElement)); + if (showPrivileges) { + fassert(17166, resultDoc.root().pushBack(privilegesElement)); + } + fassert(17267, + resultDoc.root().appendBool("isBuiltin", _roleGraph.isBuiltinRole(roleName))); mutablebson::Element warningsElement = resultDoc.makeElementArray("warnings"); addRoleNameObjectsToArrayElement(rolesElement, _roleGraph.getDirectSubordinates(roleName)); if (_roleGraphState == roleGraphStateConsistent) { addRoleNameObjectsToArrayElement( indirectRolesElement, _roleGraph.getIndirectSubordinates(roleName)); - addPrivilegeObjectsOrWarningsToArrayElement( - privilegesElement, warningsElement, _roleGraph.getAllPrivileges(roleName)); + if (showPrivileges) { + addPrivilegeObjectsOrWarningsToArrayElement( + privilegesElement, warningsElement, _roleGraph.getAllPrivileges(roleName)); + } } - else { + else if (showPrivileges) { warningsElement.appendString( "", "Role graph state inconsistent; only direct privileges available."); addPrivilegeObjectsOrWarningsToArrayElement( @@ -244,6 +256,27 @@ namespace { return Status::OK(); } + Status AuthzManagerExternalStateLocal::getRoleDescriptionsForDB(const std::string dbname, + bool showPrivileges, + bool showBuiltinRoles, + vector<BSONObj>* result) { + boost::lock_guard<boost::mutex> lk(_roleGraphMutex); + + for (RoleNameIterator it = _roleGraph.getRolesForDatabase(dbname); + it.more(); it.next()) { + if (!showBuiltinRoles && _roleGraph.isBuiltinRole(it.get())) { + continue; + } + BSONObj roleDoc; + Status status = _getRoleDescription_inlock(it.get(), showPrivileges, &roleDoc); + if (!status.isOK()) { + return status; + } + result->push_back(roleDoc); + } + return Status::OK(); + } + namespace { /** diff --git a/src/mongo/db/auth/authz_manager_external_state_local.h b/src/mongo/db/auth/authz_manager_external_state_local.h index 14016dacbc5..f9c02dbd2ce 100644 --- a/src/mongo/db/auth/authz_manager_external_state_local.h +++ b/src/mongo/db/auth/authz_manager_external_state_local.h @@ -55,7 +55,13 @@ namespace mongo { virtual Status getStoredAuthorizationVersion(int* outVersion); virtual Status getUserDescription(const UserName& userName, BSONObj* result); - virtual Status getRoleDescription(const RoleName& roleName, BSONObj* result); + virtual Status getRoleDescription(const RoleName& roleName, + bool showPrivileges, + BSONObj* result); + virtual Status getRoleDescriptionsForDB(const std::string dbname, + bool showPrivileges, + bool showBuiltinRoles, + vector<BSONObj>* result); virtual void logOp( const char* op, @@ -84,6 +90,9 @@ namespace mongo { */ virtual Status _getUserDocument(const UserName& userName, BSONObj* result) = 0; + Status _getRoleDescription_inlock(const RoleName& roleName, + bool showPrivileges, + BSONObj* result); /** * Eventually consistent, in-memory representation of all roles in the system (both * user-defined and built-in). Synchronized via _roleGraphMutex. diff --git a/src/mongo/db/auth/authz_manager_external_state_s.cpp b/src/mongo/db/auth/authz_manager_external_state_s.cpp index 450356c7aa8..926992a273d 100644 --- a/src/mongo/db/auth/authz_manager_external_state_s.cpp +++ b/src/mongo/db/auth/authz_manager_external_state_s.cpp @@ -146,6 +146,7 @@ namespace mongo { } Status AuthzManagerExternalStateMongos::getRoleDescription(const RoleName& roleName, + bool showPrivileges, BSONObj* result) { try { scoped_ptr<ScopedDbConnection> conn(getConnectionForAuthzCollection( @@ -157,7 +158,8 @@ namespace mongo { BSON_ARRAY(BSON(AuthorizationManager::ROLE_NAME_FIELD_NAME << roleName.getRole() << AuthorizationManager::ROLE_SOURCE_FIELD_NAME << - roleName.getDB()))), + roleName.getDB())) << + "showPrivileges" << showPrivileges), cmdResult); if (!cmdResult["ok"].trueValue()) { int code = cmdResult["code"].numberInt(); @@ -172,6 +174,35 @@ namespace mongo { } } + Status AuthzManagerExternalStateMongos::getRoleDescriptionsForDB(const std::string dbname, + bool showPrivileges, + bool showBuiltinRoles, + vector<BSONObj>* result) { + try { + scoped_ptr<ScopedDbConnection> conn(getConnectionForAuthzCollection( + AuthorizationManager::rolesCollectionNamespace)); + BSONObj cmdResult; + conn->get()->runCommand( + dbname, + BSON("rolesInfo" << 1 << + "showPrivileges" << showPrivileges << + "showBuiltinRoles" << showBuiltinRoles), + cmdResult); + if (!cmdResult["ok"].trueValue()) { + int code = cmdResult["code"].numberInt(); + if (code == 0) code = ErrorCodes::UnknownError; + return Status(ErrorCodes::Error(code), cmdResult["errmsg"].str()); + } + for (BSONObjIterator it(cmdResult["roles"].Obj()); it.more(); it.next()) { + result->push_back((*it).Obj().getOwned()); + } + conn->done(); + return Status::OK(); + } catch (const DBException& e) { + return e.toStatus(); + } + } + Status AuthzManagerExternalStateMongos::findOne( const NamespaceString& collectionName, const BSONObj& query, diff --git a/src/mongo/db/auth/authz_manager_external_state_s.h b/src/mongo/db/auth/authz_manager_external_state_s.h index 3341bf19d68..4c17443be52 100644 --- a/src/mongo/db/auth/authz_manager_external_state_s.h +++ b/src/mongo/db/auth/authz_manager_external_state_s.h @@ -54,7 +54,13 @@ namespace mongo { virtual Status initialize(); virtual Status getStoredAuthorizationVersion(int* outVersion); virtual Status getUserDescription(const UserName& userName, BSONObj* result); - virtual Status getRoleDescription(const RoleName& roleName, BSONObj* result); + virtual Status getRoleDescription(const RoleName& roleName, + bool showPrivileges, + BSONObj* result); + virtual Status getRoleDescriptionsForDB(const std::string dbname, + bool showPrivileges, + bool showBuiltinRoles, + vector<BSONObj>* result); virtual Status getAllDatabaseNames(std::vector<std::string>* dbnames); diff --git a/src/mongo/db/auth/role_graph.cpp b/src/mongo/db/auth/role_graph.cpp index 8937874ca1d..7202c0c19d5 100644 --- a/src/mongo/db/auth/role_graph.cpp +++ b/src/mongo/db/auth/role_graph.cpp @@ -29,6 +29,7 @@ #include "mongo/db/auth/role_graph.h" #include <algorithm> +#include <set> #include <vector> #include "mongo/db/auth/privilege.h" @@ -47,7 +48,8 @@ namespace { _roleToIndirectSubordinates(other._roleToIndirectSubordinates), _roleToMembers(other._roleToMembers), _directPrivilegesForRole(other._directPrivilegesForRole), - _allPrivilegesForRole(other._allPrivilegesForRole) {} + _allPrivilegesForRole(other._allPrivilegesForRole), + _allRoles(other._allRoles) {} RoleGraph::~RoleGraph() {}; void RoleGraph::swap(RoleGraph& other) { @@ -57,6 +59,7 @@ namespace { swap(this->_roleToMembers, other._roleToMembers); swap(this->_directPrivilegesForRole, other._directPrivilegesForRole); swap(this->_allPrivilegesForRole, other._allPrivilegesForRole); + swap(this->_allRoles, other._allRoles); } void swap(RoleGraph& lhs, RoleGraph& rhs) { @@ -103,6 +106,7 @@ namespace { _roleToMembers[role]; _directPrivilegesForRole[role]; _allPrivilegesForRole[role]; + _allRoles.insert(role); } Status RoleGraph::deleteRole(const RoleName& role) { @@ -136,6 +140,7 @@ namespace { _roleToMembers.erase(role); _directPrivilegesForRole.erase(role); _allPrivilegesForRole.erase(role); + _allRoles.erase(role); return Status::OK(); } @@ -527,5 +532,14 @@ namespace { return Status::OK(); } + RoleNameIterator RoleGraph::getRolesForDatabase(const std::string& dbname) { + _createBuiltinRolesForDBIfNeeded(dbname); + + std::set<RoleName>::const_iterator lower = _allRoles.lower_bound(RoleName("", dbname)); + std::string afterDB = dbname; + afterDB.push_back('\0'); + std::set<RoleName>::const_iterator upper = _allRoles.lower_bound(RoleName("", afterDB)); + return makeRoleNameIterator(lower, upper); + } } // namespace mongo diff --git a/src/mongo/db/auth/role_graph.h b/src/mongo/db/auth/role_graph.h index e6ee302f051..4d7de167a15 100644 --- a/src/mongo/db/auth/role_graph.h +++ b/src/mongo/db/auth/role_graph.h @@ -29,6 +29,7 @@ #pragma once #include <algorithm> +#include <set> #include <vector> #include "mongo/base/status.h" @@ -88,7 +89,7 @@ namespace mongo { RoleNameIterator getDirectMembers(const RoleName& role); /** - * Returns an iterator over the RoleNames of the "subordninates" of the given role. + * Returns an iterator over the RoleNames of the "subordinates" of the given role. * Subordinate roles are the roles that this role has been granted directly (roles * that have been granted transitively through another role are not included). These are * the "children" of this node in the graph. @@ -103,6 +104,12 @@ namespace mongo { RoleNameIterator getIndirectSubordinates(const RoleName& role); /** + * Returns an iterator that can be used to get a full list of roles (in lexicographical + * order) that are defined on the given database. + */ + RoleNameIterator getRolesForDatabase(const std::string& dbname); + + /** * Returns a vector of the privileges that the given role has been directly granted. * Privileges that have been granted transitively through this role's subordinate roles are * not included. @@ -266,6 +273,12 @@ namespace mongo { void _createBuiltinRoleIfNeeded(const RoleName& role); /** + * Adds the built-in roles for the given database name to the role graph if they aren't + * already present. + */ + void _createBuiltinRolesForDBIfNeeded(const std::string& dbname); + + /** * Returns whether or not the given role exists strictly within the role graph. */ bool _roleExistsDontCreateBuiltin(const RoleName& role); @@ -293,6 +306,7 @@ namespace mongo { EdgeSet _roleToMembers; RolePrivilegeMap _directPrivilegesForRole; RolePrivilegeMap _allPrivilegesForRole; + set<RoleName> _allRoles; }; void swap(RoleGraph& lhs, RoleGraph& rhs); diff --git a/src/mongo/db/auth/role_graph_builtin_roles.cpp b/src/mongo/db/auth/role_graph_builtin_roles.cpp index 54d5671b741..45b70a9fbb4 100644 --- a/src/mongo/db/auth/role_graph_builtin_roles.cpp +++ b/src/mongo/db/auth/role_graph_builtin_roles.cpp @@ -709,6 +709,29 @@ namespace { return false; } + void RoleGraph::_createBuiltinRolesForDBIfNeeded(const std::string& dbname) { + _createBuiltinRoleIfNeeded(RoleName(BUILTIN_ROLE_READ, dbname)); + _createBuiltinRoleIfNeeded(RoleName(BUILTIN_ROLE_READ_WRITE, dbname)); + _createBuiltinRoleIfNeeded(RoleName(BUILTIN_ROLE_USER_ADMIN, dbname)); + _createBuiltinRoleIfNeeded(RoleName(BUILTIN_ROLE_DB_ADMIN, dbname)); + _createBuiltinRoleIfNeeded(RoleName(BUILTIN_ROLE_DB_OWNER, dbname)); + + if (dbname == "admin") { + _createBuiltinRoleIfNeeded(RoleName(BUILTIN_ROLE_READ_ANY_DB, dbname)); + _createBuiltinRoleIfNeeded(RoleName(BUILTIN_ROLE_READ_WRITE_ANY_DB, dbname)); + _createBuiltinRoleIfNeeded(RoleName(BUILTIN_ROLE_USER_ADMIN_ANY_DB, dbname)); + _createBuiltinRoleIfNeeded(RoleName(BUILTIN_ROLE_DB_ADMIN_ANY_DB, dbname)); + _createBuiltinRoleIfNeeded(RoleName(BUILTIN_ROLE_CLUSTER_MONITOR, dbname)); + _createBuiltinRoleIfNeeded(RoleName(BUILTIN_ROLE_HOST_MANAGEMENT, dbname)); + _createBuiltinRoleIfNeeded(RoleName(BUILTIN_ROLE_CLUSTER_MANAGEMENT, dbname)); + _createBuiltinRoleIfNeeded(RoleName(BUILTIN_ROLE_CLUSTER_ADMIN, dbname)); + _createBuiltinRoleIfNeeded(RoleName(BUILTIN_ROLE_BACKUP, dbname)); + _createBuiltinRoleIfNeeded(RoleName(BUILTIN_ROLE_RESTORE, dbname)); + _createBuiltinRoleIfNeeded(RoleName(BUILTIN_ROLE_ROOT, dbname)); + _createBuiltinRoleIfNeeded(RoleName(BUILTIN_ROLE_INTERNAL, dbname)); + } + } + void RoleGraph::_createBuiltinRoleIfNeeded(const RoleName& role) { if (!isBuiltinRole(role) || _roleExistsDontCreateBuiltin(role)) { return; diff --git a/src/mongo/db/auth/role_graph_test.cpp b/src/mongo/db/auth/role_graph_test.cpp index e4e34f302c2..4ace76a761f 100644 --- a/src/mongo/db/auth/role_graph_test.cpp +++ b/src/mongo/db/auth/role_graph_test.cpp @@ -35,6 +35,7 @@ #include "mongo/db/auth/role_graph.h" #include "mongo/unittest/unittest.h" #include "mongo/util/mongoutils/str.h" +#include "mongo/util/sequence_util.h" namespace mongo { namespace { @@ -672,5 +673,54 @@ namespace { ASSERT(!graph.roleExists(RoleName("MyRole", "admin"))); } + TEST(RoleGraphTest, getRolesForDatabase) { + RoleGraph graph; + graph.createRole(RoleName("myRole", "test")); + // Make sure that a role on "test2" doesn't show up in the roles list for "test" + graph.createRole(RoleName("anotherRole", "test2")); + graph.createRole(RoleName("myAdminRole", "admin")); + + // Non-admin DB with no user-defined roles + RoleNameIterator it = graph.getRolesForDatabase("fakedb"); + ASSERT_EQUALS(RoleName("dbAdmin", "fakedb"), it.next()); + ASSERT_EQUALS(RoleName("dbOwner", "fakedb"), it.next()); + ASSERT_EQUALS(RoleName("read", "fakedb"), it.next()); + ASSERT_EQUALS(RoleName("readWrite", "fakedb"), it.next()); + ASSERT_EQUALS(RoleName("userAdmin", "fakedb"), it.next()); + ASSERT_FALSE(it.more()); + + // Non-admin DB with a user-defined role + it = graph.getRolesForDatabase("test"); + ASSERT_EQUALS(RoleName("dbAdmin", "test"), it.next()); + ASSERT_EQUALS(RoleName("dbOwner", "test"), it.next()); + ASSERT_EQUALS(RoleName("myRole", "test"), it.next()); + ASSERT_EQUALS(RoleName("read", "test"), it.next()); + ASSERT_EQUALS(RoleName("readWrite", "test"), it.next()); + ASSERT_EQUALS(RoleName("userAdmin", "test"), it.next()); + ASSERT_FALSE(it.more()); + + // Admin DB + it = graph.getRolesForDatabase("admin"); + ASSERT_EQUALS(RoleName("__system", "admin"), it.next()); + ASSERT_EQUALS(RoleName("backup", "admin"), it.next()); + ASSERT_EQUALS(RoleName("clusterAdmin", "admin"), it.next()); + ASSERT_EQUALS(RoleName("clusterManager", "admin"), it.next()); + ASSERT_EQUALS(RoleName("clusterMonitor", "admin"), it.next()); + ASSERT_EQUALS(RoleName("dbAdmin", "admin"), it.next()); + ASSERT_EQUALS(RoleName("dbAdminAnyDatabase", "admin"), it.next()); + ASSERT_EQUALS(RoleName("dbOwner", "admin"), it.next()); + ASSERT_EQUALS(RoleName("hostManager", "admin"), it.next()); + ASSERT_EQUALS(RoleName("myAdminRole", "admin"), it.next()); + ASSERT_EQUALS(RoleName("read", "admin"), it.next()); + ASSERT_EQUALS(RoleName("readAnyDatabase", "admin"), it.next()); + ASSERT_EQUALS(RoleName("readWrite", "admin"), it.next()); + ASSERT_EQUALS(RoleName("readWriteAnyDatabase", "admin"), it.next()); + ASSERT_EQUALS(RoleName("restore", "admin"), it.next()); + ASSERT_EQUALS(RoleName("root", "admin"), it.next()); + ASSERT_EQUALS(RoleName("userAdmin", "admin"), it.next()); + ASSERT_EQUALS(RoleName("userAdminAnyDatabase", "admin"), it.next()); + ASSERT_FALSE(it.more()); + } + } // namespace } // namespace mongo diff --git a/src/mongo/db/auth/role_name.h b/src/mongo/db/auth/role_name.h index 7f7de642c9b..63f7da816c7 100644 --- a/src/mongo/db/auth/role_name.h +++ b/src/mongo/db/auth/role_name.h @@ -89,7 +89,10 @@ namespace mongo { } static inline bool operator<(const RoleName& lhs, const RoleName& rhs) { - return lhs.getFullName() < rhs.getFullName(); + if (lhs.getDB() == rhs.getDB()) { + return lhs.getRole() < rhs.getRole(); + } + return lhs.getDB() < rhs.getDB(); } std::ostream& operator<<(std::ostream& os, const RoleName& name); diff --git a/src/mongo/db/auth/user_management_commands_parser.cpp b/src/mongo/db/auth/user_management_commands_parser.cpp index eb79a95f64e..6ad54f13724 100644 --- a/src/mongo/db/auth/user_management_commands_parser.cpp +++ b/src/mongo/db/auth/user_management_commands_parser.cpp @@ -398,19 +398,23 @@ namespace auth { Status parseRolesInfoCommand(const BSONObj& cmdObj, const StringData& dbname, - std::vector<RoleName>* parsedRoleNames) { + RolesInfoArgs* parsedArgs) { unordered_set<std::string> validFieldNames; validFieldNames.insert("rolesInfo"); + validFieldNames.insert("showPrivileges"); + validFieldNames.insert("showBuiltinRoles"); Status status = _checkNoExtraFields(cmdObj, "rolesInfo", validFieldNames); if (!status.isOK()) { return status; } - if (cmdObj["rolesInfo"].type() == Array) { + if (cmdObj["rolesInfo"].numberInt() == 1) { + parsedArgs->allForDB = true; + } else if (cmdObj["rolesInfo"].type() == Array) { status = parseRoleNamesFromBSONArray(BSONArray(cmdObj["rolesInfo"].Obj()), dbname, - parsedRoleNames); + &parsedArgs->roleNames); if (!status.isOK()) { return status; } @@ -424,7 +428,23 @@ namespace auth { if (!status.isOK()) { return status; } - parsedRoleNames->push_back(name); + parsedArgs->roleNames.push_back(name); + } + + status = bsonExtractBooleanFieldWithDefault(cmdObj, + "showPrivileges", + false, + &parsedArgs->showPrivileges); + if (!status.isOK()) { + return status; + } + + status = bsonExtractBooleanFieldWithDefault(cmdObj, + "showBuiltinRoles", + false, + &parsedArgs->showBuiltinRoles); + if (!status.isOK()) { + return status; } return Status::OK(); diff --git a/src/mongo/db/auth/user_management_commands_parser.h b/src/mongo/db/auth/user_management_commands_parser.h index 60d92e6ae13..d5e10f7574d 100644 --- a/src/mongo/db/auth/user_management_commands_parser.h +++ b/src/mongo/db/auth/user_management_commands_parser.h @@ -114,13 +114,21 @@ namespace auth { const StringData& dbname, UsersInfoArgs* parsedArgs); + struct RolesInfoArgs { + std::vector<RoleName> roleNames; + bool allForDB; + bool showPrivileges; + bool showBuiltinRoles; + RolesInfoArgs() : allForDB(false), showPrivileges(false), showBuiltinRoles(false) {} + }; + /** * Takes a command object describing an invocation of the "rolesInfo" command and parses out - * the role names requested into the "parsedRoleNames" output param. + * the arguments into the "parsedArgs" output param. */ Status parseRolesInfoCommand(const BSONObj& cmdObj, const StringData& dbname, - std::vector<RoleName>* parsedRoleNames); + RolesInfoArgs* parsedArgs); struct CreateOrUpdateRoleArgs { RoleName roleName; diff --git a/src/mongo/db/commands/user_management_commands.cpp b/src/mongo/db/commands/user_management_commands.cpp index 6340059db25..25ce3e41d75 100644 --- a/src/mongo/db/commands/user_management_commands.cpp +++ b/src/mongo/db/commands/user_management_commands.cpp @@ -214,7 +214,7 @@ namespace mongo { } BSONObj roleToAddDoc; - Status status = authzManager->getRoleDescription(roleToAdd, &roleToAddDoc); + Status status = authzManager->getRoleDescription(roleToAdd, false, &roleToAddDoc); if (status == ErrorCodes::RoleNotFound) { return Status(ErrorCodes::RoleNotFound, "Cannot grant nonexistent role " + roleToAdd.toString()); @@ -433,7 +433,7 @@ namespace mongo { // Role existence has to be checked after acquiring the update lock for (size_t i = 0; i < args.roles.size(); ++i) { BSONObj ignored; - status = authzManager->getRoleDescription(args.roles[i], &ignored); + status = authzManager->getRoleDescription(args.roles[i], false, &ignored); if (!status.isOK()) { addStatus(status, result); return false; @@ -585,7 +585,7 @@ namespace mongo { if (args.hasRoles) { for (size_t i = 0; i < args.roles.size(); ++i) { BSONObj ignored; - status = authzManager->getRoleDescription(args.roles[i], &ignored); + status = authzManager->getRoleDescription(args.roles[i], false, &ignored); if (!status.isOK()) { addStatus(status, result); return false; @@ -893,7 +893,7 @@ namespace mongo { for (vector<RoleName>::iterator it = roles.begin(); it != roles.end(); ++it) { RoleName& roleName = *it; BSONObj roleDoc; - status = authzManager->getRoleDescription(roleName, &roleDoc); + status = authzManager->getRoleDescription(roleName, false, &roleDoc); if (!status.isOK()) { addStatus(status, result); return false; @@ -1006,7 +1006,7 @@ namespace mongo { for (vector<RoleName>::iterator it = roles.begin(); it != roles.end(); ++it) { RoleName& roleName = *it; BSONObj roleDoc; - status = authzManager->getRoleDescription(roleName, &roleDoc); + status = authzManager->getRoleDescription(roleName, false, &roleDoc); if (!status.isOK()) { addStatus(status, result); return false; @@ -1440,7 +1440,7 @@ namespace mongo { // Role existence has to be checked after acquiring the update lock BSONObj ignored; - status = authzManager->getRoleDescription(args.roleName, &ignored); + status = authzManager->getRoleDescription(args.roleName, false, &ignored); if (!status.isOK()) { addStatus(status, result); return false; @@ -1571,7 +1571,7 @@ namespace mongo { } BSONObj roleDoc; - status = authzManager->getRoleDescription(roleName, &roleDoc); + status = authzManager->getRoleDescription(roleName, true, &roleDoc); if (!status.isOK()) { addStatus(status, result); return false; @@ -1718,7 +1718,7 @@ namespace mongo { } BSONObj roleDoc; - status = authzManager->getRoleDescription(roleName, &roleDoc); + status = authzManager->getRoleDescription(roleName, true, &roleDoc); if (!status.isOK()) { addStatus(status, result); return false; @@ -1873,7 +1873,7 @@ namespace mongo { // Role existence has to be checked after acquiring the update lock BSONObj roleDoc; - status = authzManager->getRoleDescription(roleName, &roleDoc); + status = authzManager->getRoleDescription(roleName, false, &roleDoc); if (!status.isOK()) { addStatus(status, result); return false; @@ -2003,7 +2003,7 @@ namespace mongo { } BSONObj roleDoc; - status = authzManager->getRoleDescription(roleName, &roleDoc); + status = authzManager->getRoleDescription(roleName, false, &roleDoc); if (!status.isOK()) { addStatus(status, result); return false; @@ -2130,7 +2130,7 @@ namespace mongo { } BSONObj roleDoc; - status = authzManager->getRoleDescription(roleName, &roleDoc); + status = authzManager->getRoleDescription(roleName, false, &roleDoc); if (!status.isOK()) { addStatus(status, result); return false; @@ -2382,24 +2382,33 @@ namespace mongo { const std::string& dbname, const BSONObj& cmdObj) { AuthorizationSession* authzSession = client->getAuthorizationSession(); - std::vector<RoleName> roleNames; - Status status = auth::parseRolesInfoCommand(cmdObj, dbname, &roleNames); + auth::RolesInfoArgs args; + Status status = auth::parseRolesInfoCommand(cmdObj, dbname, &args); if (!status.isOK()) { return status; } - for (size_t i = 0; i < roleNames.size(); ++i) { - if (authzSession->isAuthenticatedAsUserWithRole(roleNames[i])) { - continue; // Can always see roles that you are a member of - } - + if (args.allForDB) { if (!authzSession->isAuthorizedForActionsOnResource( - ResourcePattern::forDatabaseName(roleNames[i].getDB()), - ActionType::viewRole)) { + ResourcePattern::forDatabaseName(dbname), ActionType::viewRole)) { return Status(ErrorCodes::Unauthorized, str::stream() << "Not authorized to view roles from the " << dbname << " database"); } + } else { + for (size_t i = 0; i < args.roleNames.size(); ++i) { + if (authzSession->isAuthenticatedAsUserWithRole(args.roleNames[i])) { + continue; // Can always see roles that you are a member of + } + + if (!authzSession->isAuthorizedForActionsOnResource( + ResourcePattern::forDatabaseName(args.roleNames[i].getDB()), + ActionType::viewRole)) { + return Status(ErrorCodes::Unauthorized, + str::stream() << "Not authorized to view roles from the " << + args.roleNames[i].getDB() << " database"); + } + } } return Status::OK(); @@ -2412,8 +2421,8 @@ namespace mongo { BSONObjBuilder& result, bool fromRepl) { - std::vector<RoleName> roleNames; - Status status = auth::parseRolesInfoCommand(cmdObj, dbname, &roleNames); + auth::RolesInfoArgs args; + Status status = auth::parseRolesInfoCommand(cmdObj, dbname, &args); if (!status.isOK()) { addStatus(status, result); return false; @@ -2426,18 +2435,32 @@ namespace mongo { } BSONArrayBuilder rolesArrayBuilder; - for (size_t i = 0; i < roleNames.size(); ++i) { - BSONObj roleDetails; - status = getGlobalAuthorizationManager()->getRoleDescription( - roleNames[i], &roleDetails); - if (status.code() == ErrorCodes::RoleNotFound) { - continue; - } + if (args.allForDB) { + std::vector<BSONObj> rolesDocs; + status = getGlobalAuthorizationManager()->getRoleDescriptionsForDB( + dbname, args.showPrivileges, args.showBuiltinRoles, &rolesDocs); if (!status.isOK()) { addStatus(status, result); return false; } - rolesArrayBuilder.append(roleDetails); + + for (size_t i = 0; i < rolesDocs.size(); ++i) { + rolesArrayBuilder.append(rolesDocs[i]); + } + } else { + for (size_t i = 0; i < args.roleNames.size(); ++i) { + BSONObj roleDetails; + status = getGlobalAuthorizationManager()->getRoleDescription( + args.roleNames[i], args.showPrivileges, &roleDetails); + if (status.code() == ErrorCodes::RoleNotFound) { + continue; + } + if (!status.isOK()) { + addStatus(status, result); + return false; + } + rolesArrayBuilder.append(roleDetails); + } } result.append("roles", rolesArrayBuilder.arr()); return true; diff --git a/src/mongo/shell/db.js b/src/mongo/shell/db.js index 1a32c091258..c74a65e2bf7 100644 --- a/src/mongo/shell/db.js +++ b/src/mongo/shell/db.js @@ -1373,11 +1373,13 @@ DB.prototype.revokePrivilegesFromRole = function(rolename, privileges, writeConc } } -DB.prototype.getRole = function(rolename) { +DB.prototype.getRole = function(rolename, args) { if (typeof rolename != "string") { throw Error("Role name for getRole shell helper must be a string"); } - var res = this.runCommand({rolesInfo: rolename}); + var cmdObj = {rolesInfo:rolename}; + Object.extend(cmdObj, args); + var res = this.runCommand(cmdObj); if (!res.ok) { throw Error(res.errmsg); } @@ -1388,4 +1390,15 @@ DB.prototype.getRole = function(rolename) { return res.roles[0]; } +DB.prototype.getRoles = function(args) { + var cmdObj = {rolesInfo:1}; + Object.extend(cmdObj, args); + var res = this.runCommand(cmdObj); + if (!res.ok) { + throw Error(res.errmsg); + } + + return res.roles; +} + }()); |