diff options
author | Spencer T Brody <spencer@10gen.com> | 2013-09-30 18:40:39 -0400 |
---|---|---|
committer | Spencer T Brody <spencer@10gen.com> | 2013-10-04 17:17:51 -0400 |
commit | d09e608691aae000f3176b27cc67a7900229cd1e (patch) | |
tree | 8740c59feef65ab28b7b8dc55fc85996a71acaed /src/mongo/db | |
parent | e0f70cdb67a36c831a23b452e7b9fba41f512ae0 (diff) | |
download | mongo-d09e608691aae000f3176b27cc67a7900229cd1e.tar.gz |
SERVER-9515 Implement several remaining role manipulation commands
Diffstat (limited to 'src/mongo/db')
-rw-r--r-- | src/mongo/db/auth/authorization_manager.cpp | 42 | ||||
-rw-r--r-- | src/mongo/db/auth/authorization_manager.h | 28 | ||||
-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_test.cpp | 134 | ||||
-rw-r--r-- | src/mongo/db/auth/user_management_commands_parser.cpp | 133 | ||||
-rw-r--r-- | src/mongo/db/auth/user_management_commands_parser.h | 30 | ||||
-rw-r--r-- | src/mongo/db/commands/user_management_commands.cpp | 628 |
8 files changed, 822 insertions, 205 deletions
diff --git a/src/mongo/db/auth/authorization_manager.cpp b/src/mongo/db/auth/authorization_manager.cpp index a1f60eaf956..9b0b07c9086 100644 --- a/src/mongo/db/auth/authorization_manager.cpp +++ b/src/mongo/db/auth/authorization_manager.cpp @@ -163,6 +163,19 @@ namespace mongo { return _externalState->removePrivilegeDocuments(query, writeConcern, numRemoved); } + Status AuthorizationManager::removeRoleDocuments(const BSONObj& query, + const BSONObj& writeConcern, + int* numRemoved) const { + Status status = _externalState->remove(NamespaceString("admin.system.roles"), + query, + writeConcern, + numRemoved); + if (status.code() == ErrorCodes::UnknownError) { + return Status(ErrorCodes::RoleModificationFailed, status.reason()); + } + return status; + } + Status AuthorizationManager::insertRoleDocument(const BSONObj& roleObj, const BSONObj& writeConcern) const { Status status = _externalState->insert(NamespaceString("admin.system.roles"), @@ -215,6 +228,22 @@ namespace mongo { return _externalState->query(collectionName, query, resultProcessor); } + Status AuthorizationManager::updateAuthzDocuments(const NamespaceString& collectionName, + const BSONObj& query, + const BSONObj& updatePattern, + bool upsert, + bool multi, + const BSONObj& writeConcern, + int* numUpdated) const { + return _externalState->update(collectionName, + query, + updatePattern, + upsert, + multi, + writeConcern, + numUpdated); + } + bool AuthorizationManager::roleExists(const RoleName& role) { boost::lock_guard<boost::mutex> lk(_lock); return _roleGraph.roleExists(role); @@ -230,6 +259,12 @@ namespace mongo { return _roleGraph.getDirectPrivileges(role); } + std::vector<RoleName> AuthorizationManager::getSubordinateRolesForRole( + const RoleName& role) { + boost::lock_guard<boost::mutex> lk(_lock); + return _roleGraph.getDirectSubordinates(role); + } + Status AuthorizationManager::getBSONForPrivileges(const PrivilegeVector& privileges, mutablebson::Element resultArray) { for (PrivilegeVector::const_iterator it = privileges.begin(); @@ -270,9 +305,10 @@ namespace mongo { // Build roles array mutablebson::Element rolesArrayElement = result.getDocument().makeElementArray("roles"); result.pushBack(rolesArrayElement); - RoleNameIterator nameIt = graph->getDirectSubordinates(roleName); - while (nameIt.more()) { - const RoleName& subRole = nameIt.next(); + const std::vector<RoleName> roles = graph->getDirectSubordinates(roleName); + for (std::vector<RoleName>::const_iterator it = roles.begin(); + it != roles.end(); ++it) { + const RoleName& subRole = *it; mutablebson::Element roleObj = result.getDocument().makeElementObject(""); roleObj.appendString("name", subRole.getRole()); roleObj.appendString("source", subRole.getDB()); diff --git a/src/mongo/db/auth/authorization_manager.h b/src/mongo/db/auth/authorization_manager.h index 5220ee11a45..1f9a7ef2200 100644 --- a/src/mongo/db/auth/authorization_manager.h +++ b/src/mongo/db/auth/authorization_manager.h @@ -183,6 +183,29 @@ namespace mongo { const BSONObj& writeConcern) const; /** + * Updates documents matching "query" according to "updatePattern" in "collectionName". + * Should only be called on collections with authorization documents in them + * (ie admin.system.users and admin.system.roles). + */ + Status updateAuthzDocuments(const NamespaceString& collectionName, + const BSONObj& query, + const BSONObj& updatePattern, + bool upsert, + bool multi, + const BSONObj& writeConcern, + int* numUpdated) const; + + /* + * Removes roles matching the given query. + * Writes into *numRemoved the number of role documents that were modified. + * 'writeConcern' contains the arguments to be passed to getLastError to block for + * successful completion of the write. + */ + Status removeRoleDocuments(const BSONObj& query, + const BSONObj& writeConcern, + int* numRemoved) const; + + /** * Finds all documents matching "query" in "collectionName". For each document returned, * calls the function resultProcessor on it. * Should only be called on collections with authorization documents in them @@ -257,6 +280,11 @@ namespace mongo { PrivilegeVector getDirectPrivilegesForRole(const RoleName& role); /** + * Returns the direct subordinate roles of the given role. + */ + std::vector<RoleName> getSubordinateRolesForRole(const RoleName& role); + + /** * Initializes the authorization manager. Depending on what version the authorization * system is at, this may involve building up the user cache and/or the roles graph. * This function should be called once at startup and never again after that. diff --git a/src/mongo/db/auth/role_graph.cpp b/src/mongo/db/auth/role_graph.cpp index d42354aa577..a010326f57c 100644 --- a/src/mongo/db/auth/role_graph.cpp +++ b/src/mongo/db/auth/role_graph.cpp @@ -40,6 +40,7 @@ namespace mongo { namespace { PrivilegeVector emptyPrivilegeVector; + std::vector<RoleName> emptyRoleVector; // RoleNameIterator for iterating over an unordered_set of RoleNames. class RoleNameSetIterator : public RoleNameIterator::Impl { @@ -75,6 +76,7 @@ namespace { unordered_set<RoleName>::const_iterator _begin; unordered_set<RoleName>::const_iterator _end; }; + } // namespace RoleGraph::RoleGraph() {}; @@ -180,11 +182,10 @@ namespace { return Status::OK(); } - RoleNameIterator RoleGraph::getDirectSubordinates(const RoleName& role) { + const std::vector<RoleName>& RoleGraph::getDirectSubordinates(const RoleName& role) { if (!roleExists(role)) - return RoleNameIterator(NULL); - const std::vector<RoleName>& edges = _roleToSubordinates.find(role)->second; - return RoleNameIterator(new RoleNameVectorIterator(edges.begin(), edges.end())); + return emptyRoleVector; + return _roleToSubordinates.find(role)->second; } RoleNameIterator RoleGraph::getIndirectSubordinates(const RoleName& role) { @@ -194,11 +195,10 @@ namespace { return RoleNameIterator(new RoleNameSetIterator(subs.begin(), subs.end())); } - RoleNameIterator RoleGraph::getDirectMembers(const RoleName& role) { + const std::vector<RoleName>& RoleGraph::getDirectMembers(const RoleName& role) { if (!roleExists(role)) - return RoleNameIterator(NULL); - const std::vector<RoleName>& edges = _roleToMembers.find(role)->second; - return RoleNameIterator(new RoleNameVectorIterator(edges.begin(), edges.end())); + return emptyRoleVector; + return _roleToMembers.find(role)->second; } const PrivilegeVector& RoleGraph::getDirectPrivileges(const RoleName& role) { diff --git a/src/mongo/db/auth/role_graph.h b/src/mongo/db/auth/role_graph.h index b7dcbbaa80f..f30f62a104d 100644 --- a/src/mongo/db/auth/role_graph.h +++ b/src/mongo/db/auth/role_graph.h @@ -71,22 +71,20 @@ namespace mongo { static void generateUniversalPrivileges(PrivilegeVector* privileges); /** - * Returns an iterator that can be used to get a list of the members of the given role. + * Returns a vector of the RoleNames of the "members" of the given role. * Members of a role are roles that have been granted this role directly (roles that are * members transitively through another role are not included). These are the "parents" of - * this node in the graph. The iterator is valid until the next call to addRole or - * removeRole. + * this node in the graph. */ - RoleNameIterator getDirectMembers(const RoleName& role); + const std::vector<RoleName>& getDirectMembers(const RoleName& role); /** - * Returns an iterator that can be used to get a list of "subordinate" roles of the given - * role. Subordinate roles are the roles that this role has been granted directly (roles + * Returns a vector of the RoleNames of the "subordninates" 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. The iterator is valid until the next call to - * addRole or removeRole. + * the "children" of this node in the graph. */ - RoleNameIterator getDirectSubordinates(const RoleName& role); + const std::vector<RoleName>& getDirectSubordinates(const RoleName& role); /** * Returns an iterator that can be used to get a full list of roles that this role inherits diff --git a/src/mongo/db/auth/role_graph_test.cpp b/src/mongo/db/auth/role_graph_test.cpp index e4e34f302c2..42d933fc76e 100644 --- a/src/mongo/db/auth/role_graph_test.cpp +++ b/src/mongo/db/auth/role_graph_test.cpp @@ -52,32 +52,27 @@ namespace { ASSERT_OK(graph.createRole(roleB)); ASSERT_OK(graph.createRole(roleC)); - RoleNameIterator it; - it = graph.getDirectSubordinates(roleA); - ASSERT_FALSE(it.more()); - it = graph.getDirectMembers(roleA); - ASSERT_FALSE(it.more()); + std::vector<RoleName> roles = graph.getDirectSubordinates(roleA); + ASSERT_EQUALS(0U, roles.size()); + roles = graph.getDirectMembers(roleA); + ASSERT_EQUALS(0U, roles.size()); ASSERT_OK(graph.addRoleToRole(roleA, roleB)); // A -> B - it = graph.getDirectSubordinates(roleA); - ASSERT_TRUE(it.more()); - // should not advance the iterator - ASSERT_EQUALS(it.get().getFullName(), roleB.getFullName()); - ASSERT_EQUALS(it.get().getFullName(), roleB.getFullName()); - ASSERT_EQUALS(it.next().getFullName(), roleB.getFullName()); - ASSERT_FALSE(it.more()); + roles = graph.getDirectSubordinates(roleA); + ASSERT_EQUALS(1U, roles.size()); + ASSERT_EQUALS(roles[0].getFullName(), roleB.getFullName()); - it = graph.getDirectMembers(roleA); - ASSERT_FALSE(it.more()); + roles = graph.getDirectMembers(roleA); + ASSERT_EQUALS(0U, roles.size()); - it = graph.getDirectMembers(roleB); - ASSERT_EQUALS(it.next().getFullName(), roleA.getFullName()); - ASSERT_FALSE(it.more()); + roles = graph.getDirectMembers(roleB); + ASSERT_EQUALS(1U, roles.size()); + ASSERT_EQUALS(roles[0].getFullName(), roleA.getFullName()); - it = graph.getDirectSubordinates(roleB); - ASSERT_FALSE(it.more()); + roles= graph.getDirectSubordinates(roleB); + ASSERT_EQUALS(0U, roles.size()); ASSERT_OK(graph.addRoleToRole(roleA, roleC)); ASSERT_OK(graph.addRoleToRole(roleB, roleC)); @@ -96,20 +91,9 @@ namespace { * D */ - - it = graph.getDirectSubordinates(roleA); // should be roleB and roleC, order doesn't matter - RoleName cur = it.next(); - if (cur == roleB) { - ASSERT_EQUALS(it.next().getFullName(), roleC.getFullName()); - } else if (cur == roleC) { - ASSERT_EQUALS(it.next().getFullName(), roleB.getFullName()); - } else { - FAIL(mongoutils::str::stream() << "unexpected role returned: " << cur.getFullName()); - } - ASSERT_FALSE(it.more()); - + // Check indirect roles for roleA. should have roleB, roleC and roleD, order doesn't matter. ASSERT_OK(graph.recomputePrivilegeData()); - it = graph.getIndirectSubordinates(roleA); // should have roleB, roleC and roleD + RoleNameIterator it = graph.getIndirectSubordinates(roleA); bool hasB = false; bool hasC = false; bool hasD = false; @@ -133,39 +117,32 @@ namespace { ASSERT(hasC); ASSERT(hasD); - it = graph.getDirectSubordinates(roleB); // should be roleC and roleD, order doesn't matter - cur = it.next(); - if (cur == roleC) { - ASSERT_EQUALS(it.next().getFullName(), roleD.getFullName()); - } else if (cur == roleD) { - ASSERT_EQUALS(it.next().getFullName(), roleC.getFullName()); - } else { - FAIL(mongoutils::str::stream() << "unexpected role returned: " << cur.getFullName()); - } - ASSERT_FALSE(it.more()); - - it = graph.getDirectSubordinates(roleC); - ASSERT_FALSE(it.more()); - - it = graph.getDirectMembers(roleA); - ASSERT_FALSE(it.more()); - - it = graph.getDirectMembers(roleB); - ASSERT_EQUALS(it.next().getFullName(), roleA.getFullName()); - ASSERT_FALSE(it.more()); - - it = graph.getDirectMembers(roleC); // should be role A and role B, order doesn't matter - cur = it.next(); - if (cur == roleA) { - ASSERT_EQUALS(it.next().getFullName(), roleB.getFullName()); - } else if (cur == roleB) { - ASSERT_EQUALS(it.next().getFullName(), roleA.getFullName()); - } else { - FAIL(mongoutils::str::stream() << "unexpected role returned: " << cur.getFullName()); - } - ASSERT_FALSE(it.more()); + roles = graph.getDirectSubordinates(roleA); // should be roleB then roleC + ASSERT_EQUALS(2U, roles.size()); + ASSERT_EQUALS(roleB.getFullName(), roles[0].getFullName()); + ASSERT_EQUALS(roleC.getFullName(), roles[1].getFullName()); + + roles = graph.getDirectSubordinates(roleB); // should be roleC then roleD + ASSERT_EQUALS(2U, roles.size()); + ASSERT_EQUALS(roleC.getFullName(), roles[0].getFullName()); + ASSERT_EQUALS(roleD.getFullName(), roles[1].getFullName()); + + roles = graph.getDirectSubordinates(roleC); + ASSERT_EQUALS(0U, roles.size()); + + roles = graph.getDirectMembers(roleA); + ASSERT_EQUALS(0U, roles.size()); + + roles = graph.getDirectMembers(roleB); + ASSERT_EQUALS(1U, roles.size()); + ASSERT_EQUALS(roleA.getFullName(), roles[0].getFullName()); + + roles = graph.getDirectMembers(roleC); // should be role A then role B + ASSERT_EQUALS(2U, roles.size()); + ASSERT_EQUALS(roleA.getFullName(), roles[0].getFullName()); + ASSERT_EQUALS(roleB.getFullName(), roles[1].getFullName()); - // Now remove roleD from roleB and make sure graph is update correctly + // Now remove roleD from roleB and make sure graph is updated correctly ASSERT_OK(graph.removeRoleFromRole(roleB, roleD)); /* @@ -175,23 +152,23 @@ namespace { * v v * B -> C */ - it = graph.getDirectSubordinates(roleB); // should be just roleC - ASSERT_EQUALS(it.next().getFullName(), roleC.getFullName()); - ASSERT_FALSE(it.more()); + roles = graph.getDirectSubordinates(roleB); // should be just roleC + ASSERT_EQUALS(1U, roles.size()); + ASSERT_EQUALS(roleC.getFullName(), roles[0].getFullName()); - it = graph.getDirectSubordinates(roleD); // should be empty - ASSERT_FALSE(it.more()); + roles = graph.getDirectSubordinates(roleD); // should be empty + ASSERT_EQUALS(0U, roles.size()); // Now delete roleB entirely and make sure that the other roles are updated properly ASSERT_OK(graph.deleteRole(roleB)); ASSERT_NOT_OK(graph.deleteRole(roleB)); - it = graph.getDirectSubordinates(roleA); - ASSERT_EQUALS(it.next().getFullName(), roleC.getFullName()); - ASSERT_FALSE(it.more()); - it = graph.getDirectMembers(roleC); - ASSERT_EQUALS(it.next().getFullName(), roleA.getFullName()); - ASSERT_FALSE(it.more()); + roles = graph.getDirectSubordinates(roleA); + ASSERT_EQUALS(1U, roles.size()); + ASSERT_EQUALS(roleC.getFullName(), roles[0].getFullName()); + roles = graph.getDirectMembers(roleC); + ASSERT_EQUALS(1U, roles.size()); + ASSERT_EQUALS(roleA.getFullName(), roles[0].getFullName()); } const ResourcePattern collectionAFooResource(ResourcePattern::forExactNamespace( @@ -526,10 +503,9 @@ namespace { // properly. swap(tempGraph, graph); - RoleNameIterator it = graph.getDirectSubordinates(roleB); - ASSERT_TRUE(it.more()); - ASSERT_EQUALS(it.next().getFullName(), roleC.getFullName()); - ASSERT_FALSE(it.more()); + std::vector<RoleName> roles = graph.getDirectSubordinates(roleB); + ASSERT_EQUALS(1U, roles.size()); + ASSERT_EQUALS(roleC.getFullName(), roles[0].getFullName()); graph.getAllPrivileges(roleA); // should have privileges from roleB *and* role C PrivilegeVector privileges = graph.getAllPrivileges(roleA); diff --git a/src/mongo/db/auth/user_management_commands_parser.cpp b/src/mongo/db/auth/user_management_commands_parser.cpp index 2678cbd9b0b..88b8c90bee6 100644 --- a/src/mongo/db/auth/user_management_commands_parser.cpp +++ b/src/mongo/db/auth/user_management_commands_parser.cpp @@ -35,7 +35,6 @@ #include "mongo/bson/util/bson_extract.h" #include "mongo/client/auth_helpers.h" #include "mongo/db/auth/action_type.h" -#include "mongo/db/auth/authorization_manager.h" #include "mongo/db/auth/privilege.h" #include "mongo/db/auth/privilege_parser.h" #include "mongo/db/auth/user_document_parser.h" @@ -176,7 +175,7 @@ namespace auth { BSONObj* parsedWriteConcern) { unordered_set<std::string> validFieldNames; validFieldNames.insert(cmdName.toString()); - validFieldNames.insert("roles"); + validFieldNames.insert(rolesFieldName.toString()); validFieldNames.insert("writeConcern"); Status status = _checkNoExtraFields(cmdObj, cmdName, validFieldNames); @@ -189,15 +188,13 @@ namespace auth { return status; } - std::string userNameStr; - status = bsonExtractStringField(cmdObj, cmdName, &userNameStr); + status = bsonExtractStringField(cmdObj, cmdName, parsedName); if (!status.isOK()) { return status; } - *parsedName = userNameStr; BSONElement rolesElement; - status = bsonExtractTypedField(cmdObj, "roles", Array, &rolesElement); + status = bsonExtractTypedField(cmdObj, rolesFieldName, Array, &rolesElement); if (!status.isOK()) { return status; } @@ -212,85 +209,12 @@ namespace auth { if (!parsedRoleNames->size()) { return Status(ErrorCodes::BadValue, - mongoutils::str::stream() << cmdName << " command requires a non-empty" << - " roles array"); + mongoutils::str::stream() << cmdName << " command requires a non-empty \"" + << rolesFieldName << "\" array"); } return Status::OK(); } - /** - * Validates that the roles array described by rolesElement is valid. - * Also returns a new roles array (via the modifiedRolesArray output param) where any roles - * from the input array that were listed as strings have been expanded to a full role document. - * If includePossessionBools is true then the expanded roles documents will have "hasRole" - * and "canDelegate" boolean fields (in addition to the "name" and "source" fields which are - * there either way). - */ - Status _validateAndModifyRolesArray(const BSONElement& rolesElement, - const std::string& dbname, - AuthorizationManager* authzManager, - bool includePossessionBools, - BSONArray* modifiedRolesArray) { - BSONArrayBuilder rolesBuilder; - - for (BSONObjIterator it(rolesElement.Obj()); it.more(); it.next()) { - BSONElement element = *it; - if (element.type() == String) { - RoleName roleName(element.String(), dbname); - if (!authzManager->roleExists(roleName)) { - return Status(ErrorCodes::RoleNotFound, - mongoutils::str::stream() << roleName.toString() << - " does not name an existing role"); - } - - if (includePossessionBools) { - rolesBuilder.append(BSON("name" << element.String() << - "source" << dbname << - "hasRole" << true << - "canDelegate" << false)); - } else { - rolesBuilder.append(BSON("name" << element.String() << - "source" << dbname)); - } - } else if (element.type() == Object) { - // Check that the role object is valid - V2UserDocumentParser parser; - BSONObj roleObj = element.Obj(); - Status status = parser.checkValidRoleObject(roleObj, includePossessionBools); - if (!status.isOK()) { - return status; - } - - // Check that the role actually exists - std::string roleNameString; - std::string roleSource; - status = bsonExtractStringField(roleObj, "name", &roleNameString); - if (!status.isOK()) { - return status; - } - status = bsonExtractStringField(roleObj, "source", &roleSource); - if (!status.isOK()) { - return status; - } - - RoleName roleName(roleNameString, roleSource); - if (!authzManager->roleExists(roleName)) { - return Status(ErrorCodes::RoleNotFound, - mongoutils::str::stream() << roleName.toString() << - " does not name an existing role"); - } - - rolesBuilder.append(element); - } else { - return Status(ErrorCodes::UnsupportedFormat, - "Values in 'roles' array must be sub-documents or strings"); - } - } - - *modifiedRolesArray = rolesBuilder.arr(); - return Status::OK(); - } - Status parseCreateOrUpdateUserCommands(const BSONObj& cmdObj, const StringData& cmdName, const std::string& dbname, @@ -591,5 +515,52 @@ namespace auth { return Status::OK(); } + Status parseRemoveRoleCommand(const BSONObj& cmdObj, + const std::string& dbname, + RoleName* parsedRoleName, + BSONObj* parsedWriteConcern) { + unordered_set<std::string> validFieldNames; + validFieldNames.insert("removeRole"); + validFieldNames.insert("writeConcern"); + + Status status = _checkNoExtraFields(cmdObj, "removeRole", validFieldNames); + if (!status.isOK()) { + return status; + } + + std::string user; + status = bsonExtractStringField(cmdObj, "removeRole", &user); + if (!status.isOK()) { + return status; + } + + status = _extractWriteConcern(cmdObj, parsedWriteConcern); + if (!status.isOK()) { + return status; + } + + *parsedRoleName = RoleName(user, dbname); + return Status::OK(); + } + + Status parseRemoveRolesFromDatabaseCommand(const BSONObj& cmdObj, + const std::string& dbname, + BSONObj* parsedWriteConcern) { + unordered_set<std::string> validFieldNames; + validFieldNames.insert("removeRolesFromDatabase"); + validFieldNames.insert("writeConcern"); + + Status status = _checkNoExtraFields(cmdObj, "removeRolesFromDatabase", validFieldNames); + if (!status.isOK()) { + return status; + } + + status = _extractWriteConcern(cmdObj, parsedWriteConcern); + if (!status.isOK()) { + return status; + } + + return Status::OK(); + } } // namespace auth } // namespace mongo diff --git a/src/mongo/db/auth/user_management_commands_parser.h b/src/mongo/db/auth/user_management_commands_parser.h index d1bca97c5ad..4d49ebb77a3 100644 --- a/src/mongo/db/auth/user_management_commands_parser.h +++ b/src/mongo/db/auth/user_management_commands_parser.h @@ -39,9 +39,6 @@ #include "mongo/db/jsobj.h" namespace mongo { - - class AuthorizationManager; - namespace auth { struct CreateOrUpdateUserArgs { @@ -69,9 +66,11 @@ namespace auth { /** * Takes a command object describing an invocation of one of "grantRolesToUser", - * "revokeRolesFromUser", "grantDelegateRolesToUser", and "revokeDelegateRolesFromUser" (which - * command it is is specified in the "cmdName" argument), and parses out the user name of the - * user being modified, the roles being granted or revoked, and the write concern to use. + * "revokeRolesFromUser", "grantDelegateRolesToUser", "revokeDelegateRolesFromUser", + * "grantRolesToRole", and "revokeRolesFromRoles" (which command it is is specified in the + * "cmdName" argument), and parses out (into the parsedName out param) the user/role name of + * the user/roles being modified, the roles being granted or revoked, and the write concern to + * use. */ Status parseRolePossessionManipulationCommands(const BSONObj& cmdObj, const StringData& cmdName, @@ -83,7 +82,7 @@ namespace auth { /** * Takes a command object describing an invocation of the "removeUser" command and parses out - * the userName of the user to be removed and the writeConcern. + * the UserName of the user to be removed and the writeConcern. * Also validates the input and returns a non-ok Status if there is anything wrong. */ Status parseAndValidateRemoveUserCommand(const BSONObj& cmdObj, @@ -144,5 +143,22 @@ namespace auth { PrivilegeVector* parsedPrivileges, BSONObj* parsedWriteConcern); + /** + * Takes a command object describing an invocation of the "removeRole" command and parses out + * the RoleName of the role to be removed and the writeConcern. + */ + Status parseRemoveRoleCommand(const BSONObj& cmdObj, + const std::string& dbname, + RoleName* parsedRoleName, + BSONObj* parsedWriteConcern); + + /** + * Takes a command object describing an invocation of the "removeRolesFromDatabase" command and + * parses out the write concern. + */ + Status parseRemoveRolesFromDatabaseCommand(const BSONObj& cmdObj, + const std::string& dbname, + BSONObj* parsedWriteConcern); + } // namespace auth } // namespace mongo diff --git a/src/mongo/db/commands/user_management_commands.cpp b/src/mongo/db/commands/user_management_commands.cpp index d778e705ac1..826cd0ff10c 100644 --- a/src/mongo/db/commands/user_management_commands.cpp +++ b/src/mongo/db/commands/user_management_commands.cpp @@ -53,7 +53,7 @@ namespace mongo { - using mongoutils::str::stream; + namespace str = mongoutils::str; static void addStatus(const Status& status, BSONObjBuilder& builder) { builder.append("ok", status.isOK() ? 1.0: 0.0); @@ -87,15 +87,15 @@ namespace mongo { } // Should only be called inside the AuthzUpdateLock - static Status getBSONForRoleVectorIfRolesExist(const std::vector<RoleName>& roles, - AuthorizationManager* authzManager, - BSONArray* result) { + static Status rolesVectorToBSONArrayIfRolesExist(const std::vector<RoleName>& roles, + AuthorizationManager* authzManager, + BSONArray* result) { BSONArrayBuilder rolesArrayBuilder; for (std::vector<RoleName>::const_iterator it = roles.begin(); it != roles.end(); ++it) { const RoleName& role = *it; if (!authzManager->roleExists(role)) { return Status(ErrorCodes::RoleNotFound, - mongoutils::str::stream() << role.getFullName() << + str::stream() << role.getFullName() << " does not name an existing role"); } rolesArrayBuilder.append(BSON("name" << role.getRole() << "source" << role.getDB())); @@ -114,7 +114,7 @@ namespace mongo { const User::RoleData& role = *it; if (!authzManager->roleExists(role.name)) { return Status(ErrorCodes::RoleNotFound, - mongoutils::str::stream() << it->name.getFullName() << + str::stream() << it->name.getFullName() << " does not name an existing role"); } if (!role.hasRole && !role.canDelegate) { @@ -234,7 +234,7 @@ namespace mongo { BSONObjBuilder userObjBuilder; userObjBuilder.append("_id", - stream() << args.userName.getDB() << "." << + str::stream() << args.userName.getDB() << "." << args.userName.getUser()); userObjBuilder.append(AuthorizationManager::USER_NAME_FIELD_NAME, args.userName.getUser()); @@ -459,7 +459,7 @@ namespace mongo { if (numUpdated == 0) { addStatus(Status(ErrorCodes::UserNotFound, - mongoutils::str::stream() << "User '" << userName.getFullName() << + str::stream() << "User '" << userName.getFullName() << "' not found"), result); return false; @@ -612,7 +612,7 @@ namespace mongo { RoleName& roleName = *it; if (!authzManager->roleExists(roleName)) { addStatus(Status(ErrorCodes::RoleNotFound, - mongoutils::str::stream() << roleName.getFullName() << + str::stream() << roleName.getFullName() << " does not name an existing role"), result); return false; @@ -710,7 +710,7 @@ namespace mongo { RoleName& roleName = *it; if (!authzManager->roleExists(roleName)) { addStatus(Status(ErrorCodes::RoleNotFound, - mongoutils::str::stream() << roleName.getFullName() << + str::stream() << roleName.getFullName() << " does not name an existing role"), result); return false; @@ -816,7 +816,7 @@ namespace mongo { RoleName& roleName = *it; if (!authzManager->roleExists(roleName)) { addStatus(Status(ErrorCodes::RoleNotFound, - mongoutils::str::stream() << roleName.getFullName() << + str::stream() << roleName.getFullName() << " does not name an existing role"), result); return false; @@ -915,7 +915,7 @@ namespace mongo { RoleName& roleName = *it; if (!authzManager->roleExists(roleName)) { addStatus(Status(ErrorCodes::RoleNotFound, - mongoutils::str::stream() << roleName.getFullName() << + str::stream() << roleName.getFullName() << " does not name an existing role"), result); return false; @@ -1089,7 +1089,7 @@ namespace mongo { BSONObjBuilder roleObjBuilder; - roleObjBuilder.append("_id", stream() << args.roleName.getDB() << "." << + roleObjBuilder.append("_id", str::stream() << args.roleName.getDB() << "." << args.roleName.getRole()); roleObjBuilder.append(AuthorizationManager::ROLE_NAME_FIELD_NAME, args.roleName.getRole()); @@ -1114,7 +1114,7 @@ namespace mongo { // Role existence has to be checked after acquiring the update lock BSONArray roles; - status = getBSONForRoleVectorIfRolesExist(args.roles, authzManager, &roles); + status = rolesVectorToBSONArrayIfRolesExist(args.roles, authzManager, &roles); if (!status.isOK()) { addStatus(status, result); return false; @@ -1131,10 +1131,107 @@ namespace mongo { } cmdCreateRole; - class CmdGrantPrivilegeToRole: public Command { + class CmdUpdateRole: public Command { public: - CmdGrantPrivilegeToRole() : Command("grantPrivilegesToRole") {} + CmdUpdateRole() : Command("updateRole") {} + + virtual bool logTheOp() { + return false; + } + + virtual bool slaveOk() const { + return false; + } + + virtual LockType locktype() const { + return NONE; + } + + virtual void help(stringstream& ss) const { + ss << "Used to update a role" << endl; + } + + virtual void addRequiredPrivileges(const std::string& dbname, + const BSONObj& cmdObj, + std::vector<Privilege>* out) { + // TODO: update this with the new rules around user creation in 2.6. + ActionSet actions; + actions.addAction(ActionType::userAdmin); + out->push_back(Privilege(ResourcePattern::forDatabaseName(dbname), actions)); + } + + bool run(const string& dbname, + BSONObj& cmdObj, + int options, + string& errmsg, + BSONObjBuilder& result, + bool fromRepl) { + auth::CreateOrUpdateRoleArgs args; + Status status = auth::parseCreateOrUpdateRoleCommands(cmdObj, + "createRole", + dbname, + &args); + if (!status.isOK()) { + addStatus(status, result); + return false; + } + + if (!args.hasPrivileges && !args.hasRoles) { + addStatus(Status(ErrorCodes::BadValue, + "Must specify at least one field to update in updateRole"), + result); + return false; + } + + BSONObjBuilder updateSetBuilder; + + if (args.hasPrivileges) { + BSONArray privileges; + status = privilegeVectorToBSONArray(args.privileges, &privileges); + if (!status.isOK()) { + addStatus(status, result); + return false; + } + updateSetBuilder.append("privileges", privileges); + } + + AuthorizationManager* authzManager = getGlobalAuthorizationManager(); + AuthzDocumentsUpdateGuard updateGuard(authzManager); + if (!updateGuard.tryLock("Update role")) { + addStatus(Status(ErrorCodes::LockBusy, "Could not lock auth data update lock."), + result); + return false; + } + + // Role existence has to be checked after acquiring the update lock + if (args.hasRoles) { + BSONArray roles; + status = rolesVectorToBSONArrayIfRolesExist(args.roles, authzManager, &roles); + if (!status.isOK()) { + addStatus(status, result); + return false; + } + + updateSetBuilder.append("roles", roles); + } + + status = authzManager->updateRoleDocument(args.roleName, + BSON("$set" << updateSetBuilder.done()), + args.writeConcern); + if (!status.isOK()) { + addStatus(status, result); + return false; + } + + return true; + } + } cmdUpdateRole; + + class CmdGrantPrivilegesToRole: public Command { + public: + + CmdGrantPrivilegesToRole() : Command("grantPrivilegesToRole") {} virtual bool logTheOp() { return false; @@ -1192,7 +1289,7 @@ namespace mongo { if (!authzManager->roleExists(roleName)) { addStatus(Status(ErrorCodes::RoleNotFound, - mongoutils::str::stream() << roleName.getFullName() << + str::stream() << roleName.getFullName() << " does not name an existing role"), result); return false; @@ -1200,7 +1297,7 @@ namespace mongo { if (authzManager->isBuiltinRole(roleName)) { addStatus(Status(ErrorCodes::InvalidRoleModification, - mongoutils::str::stream() << roleName.getFullName() << + str::stream() << roleName.getFullName() << " is a built-in role and cannot be modified."), result); return false; @@ -1248,6 +1345,501 @@ namespace mongo { } cmdGrantPrivilegesToRole; + class CmdRevokePrivilegesFromRole: public Command { + public: + + CmdRevokePrivilegesFromRole() : Command("revokePrivilegesFromRole") {} + + virtual bool logTheOp() { + return false; + } + + virtual bool slaveOk() const { + return false; + } + + virtual LockType locktype() const { + return NONE; + } + + virtual void help(stringstream& ss) const { + ss << "Revokes privileges from a role" << endl; + } + + virtual void addRequiredPrivileges(const std::string& dbname, + const BSONObj& cmdObj, + std::vector<Privilege>* out) { + // TODO: update this with the new rules around user creation in 2.6. + ActionSet actions; + actions.addAction(ActionType::userAdmin); + out->push_back(Privilege(ResourcePattern::forDatabaseName(dbname), actions)); + } + + bool run(const string& dbname, + BSONObj& cmdObj, + int options, + string& errmsg, + BSONObjBuilder& result, + bool fromRepl) { + AuthorizationManager* authzManager = getGlobalAuthorizationManager(); + AuthzDocumentsUpdateGuard updateGuard(authzManager); + if (!updateGuard.tryLock("Revoke privileges from role")) { + addStatus(Status(ErrorCodes::LockBusy, "Could not lock auth data update lock."), + result); + return false; + } + + RoleName roleName; + PrivilegeVector privilegesToRemove; + BSONObj writeConcern; + Status status = auth::parseAndValidateRolePrivilegeManipulationCommands( + cmdObj, + "revokePrivilegesFromRole", + dbname, + &roleName, + &privilegesToRemove, + &writeConcern); + if (!status.isOK()) { + addStatus(status, result); + return false; + } + + if (!authzManager->roleExists(roleName)) { + addStatus(Status(ErrorCodes::RoleNotFound, + str::stream() << roleName.getFullName() << + " does not name an existing role"), + result); + return false; + } + + if (authzManager->isBuiltinRole(roleName)) { + addStatus(Status(ErrorCodes::InvalidRoleModification, + str::stream() << roleName.getFullName() << + " is a built-in role and cannot be modified."), + result); + return false; + } + + PrivilegeVector privileges = authzManager->getDirectPrivilegesForRole(roleName); + for (PrivilegeVector::iterator itToRm = privilegesToRemove.begin(); + itToRm != privilegesToRemove.end(); ++itToRm) { + for (PrivilegeVector::iterator curIt = privileges.begin(); + curIt != privileges.end(); ++curIt) { + if (curIt->getResourcePattern() == itToRm->getResourcePattern()) { + curIt->removeActions(itToRm->getActions()); + if (curIt->getActions().empty()) { + privileges.erase(curIt); + } + break; + } + } + } + + // Build up update modifier object to $set privileges. + mutablebson::Document updateObj; + mutablebson::Element setElement = updateObj.makeElementObject("$set"); + status = updateObj.root().pushBack(setElement); + if (!status.isOK()) { + addStatus(status, result); + return false; + } + mutablebson::Element privilegesElement = updateObj.makeElementArray("privileges"); + status = setElement.pushBack(privilegesElement); + if (!status.isOK()) { + addStatus(status, result); + return false; + } + status = authzManager->getBSONForPrivileges(privileges, privilegesElement); + if (!status.isOK()) { + addStatus(status, result); + return false; + } + + BSONObjBuilder updateBSONBuilder; + updateObj.writeTo(&updateBSONBuilder); + status = authzManager->updateRoleDocument( + roleName, + updateBSONBuilder.done(), + writeConcern); + if (!status.isOK()) { + addStatus(status, result); + return false; + } + + return true; + } + + } cmdRevokePrivilegesFromRole; + + class CmdGrantRolesToRole: public Command { + public: + + CmdGrantRolesToRole() : Command("grantRolesToRole") {} + + virtual bool logTheOp() { + return false; + } + + virtual bool slaveOk() const { + return false; + } + + virtual LockType locktype() const { + return NONE; + } + + virtual void help(stringstream& ss) const { + ss << "Grants roles to another role." << endl; + } + + virtual void addRequiredPrivileges(const std::string& dbname, + const BSONObj& cmdObj, + std::vector<Privilege>* out) { + // TODO: update this with the new rules around user creation in 2.6. + ActionSet actions; + actions.addAction(ActionType::userAdmin); + out->push_back(Privilege(ResourcePattern::forDatabaseName(dbname), actions)); + } + + bool run(const string& dbname, + BSONObj& cmdObj, + int options, + string& errmsg, + BSONObjBuilder& result, + bool fromRepl) { + AuthorizationManager* authzManager = getGlobalAuthorizationManager(); + AuthzDocumentsUpdateGuard updateGuard(authzManager); + if (!updateGuard.tryLock("Grant roles to role")) { + addStatus(Status(ErrorCodes::LockBusy, "Could not lock auth data update lock."), + result); + return false; + } + + std::string roleNameString; + std::vector<RoleName> rolesToAdd; + BSONObj writeConcern; + Status status = auth::parseRolePossessionManipulationCommands(cmdObj, + "grantRolesToRole", + "grantedRoles", + dbname, + &roleNameString, + &rolesToAdd, + &writeConcern); + if (!status.isOK()) { + addStatus(status, result); + return false; + } + + RoleName roleName(roleNameString, dbname); + if (!authzManager->roleExists(roleName)) { + addStatus(Status(ErrorCodes::RoleNotFound, + str::stream() << roleName.getFullName() << + " does not name an existing role"), + result); + return false; + } + + if (authzManager->isBuiltinRole(roleName)) { + addStatus(Status(ErrorCodes::InvalidRoleModification, + str::stream() << roleName.getFullName() << + " is a built-in role and cannot be modified."), + result); + return false; + } + + // TODO(spencer): Make sure that this update doesn't introduce a cycle + std::vector<RoleName> roles = authzManager->getSubordinateRolesForRole(roleName); + for (vector<RoleName>::iterator it = rolesToAdd.begin(); it != rolesToAdd.end(); ++it) { + RoleName& roleToAdd = *it; + if (std::find(roles.begin(), roles.end(), roleToAdd) == roles.end()) { + // Only add role if it's not already present + roles.push_back(roleToAdd); + } + } + + BSONArray newRolesBSONArray; + status = rolesVectorToBSONArrayIfRolesExist(roles, + authzManager, + &newRolesBSONArray); + if (!status.isOK()) { + addStatus(status, result); + return false; + } + status = authzManager->updateRoleDocument( + roleName, BSON("$set" << BSON("roles" << newRolesBSONArray)), writeConcern); + if (!status.isOK()) { + addStatus(status, result); + return false; + } + return true; + } + + } cmdGrantRolesToRole; + + class CmdRevokeRolesFromRole: public Command { + public: + + CmdRevokeRolesFromRole() : Command("revokeRolesFromRole") {} + + virtual bool logTheOp() { + return false; + } + + virtual bool slaveOk() const { + return false; + } + + virtual LockType locktype() const { + return NONE; + } + + virtual void help(stringstream& ss) const { + ss << "Revokes roles from another role." << endl; + } + + virtual void addRequiredPrivileges(const std::string& dbname, + const BSONObj& cmdObj, + std::vector<Privilege>* out) { + // TODO: update this with the new rules around user creation in 2.6. + ActionSet actions; + actions.addAction(ActionType::userAdmin); + out->push_back(Privilege(ResourcePattern::forDatabaseName(dbname), actions)); + } + + bool run(const string& dbname, + BSONObj& cmdObj, + int options, + string& errmsg, + BSONObjBuilder& result, + bool fromRepl) { + AuthorizationManager* authzManager = getGlobalAuthorizationManager(); + AuthzDocumentsUpdateGuard updateGuard(authzManager); + if (!updateGuard.tryLock("Revoke roles from role")) { + addStatus(Status(ErrorCodes::LockBusy, "Could not lock auth data update lock."), + result); + return false; + } + + std::string roleNameString; + std::vector<RoleName> rolesToRemove; + BSONObj writeConcern; + Status status = auth::parseRolePossessionManipulationCommands(cmdObj, + "revokeRolesFromRole", + "revokedRoles", + dbname, + &roleNameString, + &rolesToRemove, + &writeConcern); + if (!status.isOK()) { + addStatus(status, result); + return false; + } + + RoleName roleName(roleNameString, dbname); + if (!authzManager->roleExists(roleName)) { + addStatus(Status(ErrorCodes::RoleNotFound, + str::stream() << roleName.getFullName() << + " does not name an existing role"), + result); + return false; + } + + if (authzManager->isBuiltinRole(roleName)) { + addStatus(Status(ErrorCodes::InvalidRoleModification, + str::stream() << roleName.getFullName() << + " is a built-in role and cannot be modified."), + result); + return false; + } + + std::vector<RoleName> roles = authzManager->getSubordinateRolesForRole(roleName); + for (vector<RoleName>::iterator it = rolesToRemove.begin(); + it != rolesToRemove.end(); ++it) { + vector<RoleName>::iterator itToRm = std::find(roles.begin(), roles.end(), *it); + if (itToRm != roles.end()) { + roles.erase(itToRm); + } + } + + BSONArray newRolesBSONArray; + status = rolesVectorToBSONArrayIfRolesExist(roles, + authzManager, + &newRolesBSONArray); + if (!status.isOK()) { + addStatus(status, result); + return false; + } + status = authzManager->updateRoleDocument( + roleName, BSON("$set" << BSON("roles" << newRolesBSONArray)), writeConcern); + if (!status.isOK()) { + addStatus(status, result); + return false; + } + return true; + } + + } cmdRevokeRolesFromRole; + + class CmdRemoveRole: public Command { + public: + + CmdRemoveRole() : Command("removeRole") {} + + virtual bool logTheOp() { + return false; + } + + virtual bool slaveOk() const { + return false; + } + + virtual LockType locktype() const { + return NONE; + } + + virtual void help(stringstream& ss) const { + ss << "Removes a single role. Before deleting the role completely it must remove it " + "from any users or roles that reference it. If any errors occur in the middle " + "of that process it's possible to be left in a state where the role has been " + "removed from some user/roles but otherwise still exists."<< endl; + } + + virtual void addRequiredPrivileges(const std::string& dbname, + const BSONObj& cmdObj, + std::vector<Privilege>* out) { + // TODO: update this with the new rules around user creation in 2.6. + ActionSet actions; + actions.addAction(ActionType::userAdmin); + out->push_back(Privilege(ResourcePattern::forDatabaseName(dbname), actions)); + } + + bool run(const string& dbname, + BSONObj& cmdObj, + int options, + string& errmsg, + BSONObjBuilder& result, + bool fromRepl) { + AuthorizationManager* authzManager = getGlobalAuthorizationManager(); + AuthzDocumentsUpdateGuard updateGuard(authzManager); + if (!updateGuard.tryLock("Remove role")) { + addStatus(Status(ErrorCodes::LockBusy, "Could not lock auth data update lock."), + result); + return false; + } + + RoleName roleName; + BSONObj writeConcern; + + Status status = auth::parseRemoveRoleCommand(cmdObj, + dbname, + &roleName, + &writeConcern); + if (!status.isOK()) { + addStatus(status, result); + return false; + } + + if (!authzManager->roleExists(roleName)) { + addStatus(Status(ErrorCodes::RoleNotFound, + str::stream() << roleName.getFullName() << + " does not name an existing role"), + result); + return false; + } + + if (authzManager->isBuiltinRole(roleName)) { + addStatus(Status(ErrorCodes::InvalidRoleModification, + str::stream() << roleName.getFullName() << + " is a built-in role and cannot be modified."), + result); + return false; + } + + // Remove this role from all users + int numUpdated; + status = authzManager->updateAuthzDocuments( + NamespaceString("admin.system.users"), + BSON("roles" << BSON("$elemMatch" << + BSON(AuthorizationManager::ROLE_NAME_FIELD_NAME << + roleName.getRole() << + AuthorizationManager::ROLE_SOURCE_FIELD_NAME << + roleName.getDB()))), + BSON("$pull" << BSON("roles" << + BSON(AuthorizationManager::ROLE_NAME_FIELD_NAME << + roleName.getRole() << + AuthorizationManager::ROLE_SOURCE_FIELD_NAME << + roleName.getDB()))), + false, + true, + writeConcern, + &numUpdated); + if (!status.isOK()) { + ErrorCodes::Error code = status.code() == ErrorCodes::UnknownError ? + ErrorCodes::UserModificationFailed : status.code(); + addStatus(Status(code, + str::stream() << "Failed to remove role " << roleName.getFullName() + << " from all users: " << status.reason()), + result); + return false; + } + + // Remove this role from all other roles + status = authzManager->updateAuthzDocuments( + NamespaceString("admin.system.roles"), + BSON("roles" << BSON("$elemMatch" << + BSON(AuthorizationManager::ROLE_NAME_FIELD_NAME << + roleName.getRole() << + AuthorizationManager::ROLE_SOURCE_FIELD_NAME << + roleName.getDB()))), + BSON("$pull" << BSON("roles" << + BSON(AuthorizationManager::ROLE_NAME_FIELD_NAME << + roleName.getRole() << + AuthorizationManager::ROLE_SOURCE_FIELD_NAME << + roleName.getDB()))), + false, + true, + writeConcern, + &numUpdated); + if (!status.isOK()) { + ErrorCodes::Error code = status.code() == ErrorCodes::UnknownError ? + ErrorCodes::RoleModificationFailed : status.code(); + addStatus(Status(code, + str::stream() << "Removed role " << roleName.getFullName() << + " from all users but failed to remove from all roles: " << + status.reason()), + result); + return false; + } + + // Finally, remove the actual role document + status = authzManager->removeRoleDocuments( + BSON(AuthorizationManager::ROLE_NAME_FIELD_NAME << roleName.getRole() << + AuthorizationManager::ROLE_SOURCE_FIELD_NAME << roleName.getDB()), + writeConcern, + &numUpdated); + if (!status.isOK()) { + addStatus(Status(status.code(), + str::stream() << "Removed role " << roleName.getFullName() << + " from all users and roles but failed to actually delete" + " the role itself: " << status.reason()), + result); + return false; + } + + dassert(numUpdated == 0 || numUpdated == 1); + if (numUpdated == 0) { + addStatus(Status(ErrorCodes::RoleNotFound, + str::stream() << "Role '" << roleName.getFullName() << + "' not found"), + result); + return false; + } + + return true; + } + + } cmdRemoveRole; + class CmdRolesInfo: public Command { public: |