diff options
author | Andy Schwerin <schwerin@10gen.com> | 2013-11-07 11:56:08 -0500 |
---|---|---|
committer | Andy Schwerin <schwerin@10gen.com> | 2013-11-08 14:22:14 -0500 |
commit | 33f16ec7ed5faf0f5bcf8e6677447a8024f0e7f7 (patch) | |
tree | dc8c1636bfa0650a2f0a1f6cb2a333cd858cad90 /src/mongo/db | |
parent | d0fa8b74df7c4a5d1ac897110610d6582f17556b (diff) | |
download | mongo-33f16ec7ed5faf0f5bcf8e6677447a8024f0e7f7.tar.gz |
SERVER-9516 Factor out common code from mock & mongod implementations of AuthzManagerExternalState.
Diffstat (limited to 'src/mongo/db')
-rw-r--r-- | src/mongo/db/auth/SConscript | 1 | ||||
-rw-r--r-- | src/mongo/db/auth/authorization_session_test.cpp | 20 | ||||
-rw-r--r-- | src/mongo/db/auth/authz_manager_external_state_d.cpp | 407 | ||||
-rw-r--r-- | src/mongo/db/auth/authz_manager_external_state_d.h | 45 | ||||
-rw-r--r-- | src/mongo/db/auth/authz_manager_external_state_local.cpp | 338 | ||||
-rw-r--r-- | src/mongo/db/auth/authz_manager_external_state_local.h | 107 | ||||
-rw-r--r-- | src/mongo/db/auth/authz_manager_external_state_mock.cpp | 108 | ||||
-rw-r--r-- | src/mongo/db/auth/authz_manager_external_state_mock.h | 16 |
8 files changed, 590 insertions, 452 deletions
diff --git a/src/mongo/db/auth/SConscript b/src/mongo/db/auth/SConscript index a86d079b26a..46055ad149f 100644 --- a/src/mongo/db/auth/SConscript +++ b/src/mongo/db/auth/SConscript @@ -14,6 +14,7 @@ env.StaticLibrary('authcore', ['action_set.cpp', 'authorization_session.cpp', 'authz_documents_update_guard.cpp', 'authz_manager_external_state.cpp', + 'authz_manager_external_state_local.cpp', 'authz_session_external_state.cpp', 'privilege.cpp', 'privilege_parser.cpp', diff --git a/src/mongo/db/auth/authorization_session_test.cpp b/src/mongo/db/auth/authorization_session_test.cpp index 5a38ce053a5..3489196da6a 100644 --- a/src/mongo/db/auth/authorization_session_test.cpp +++ b/src/mongo/db/auth/authorization_session_test.cpp @@ -334,7 +334,12 @@ namespace { ASSERT(user->isValid()); // Change the user to be read-only - managerState->clearPrivilegeDocuments(); + int ignored; + managerState->remove( + AuthorizationManager::usersCollectionNamespace, + BSONObj(), + BSONObj(), + &ignored); ASSERT_OK(managerState->insertPrivilegeDocument("admin", BSON("user" << "spencer" << "db" << "test" << @@ -357,7 +362,11 @@ namespace { ASSERT(user->isValid()); // Delete the user. - managerState->clearPrivilegeDocuments(); + managerState->remove( + AuthorizationManager::usersCollectionNamespace, + BSONObj(), + BSONObj(), + &ignored); // Make sure that invalidating the user causes the session to reload its privileges. authzManager->invalidateUserByName(user->getName()); authzSession->startRequest(); // Refreshes cached data for invalid users @@ -390,8 +399,13 @@ namespace { ASSERT(user->isValid()); // Change the user to be read-only + int ignored; managerState->setFindsShouldFail(true); - managerState->clearPrivilegeDocuments(); + managerState->remove( + AuthorizationManager::usersCollectionNamespace, + BSONObj(), + BSONObj(), + &ignored); ASSERT_OK(managerState->insertPrivilegeDocument("admin", BSON("user" << "spencer" << "db" << "test" << diff --git a/src/mongo/db/auth/authz_manager_external_state_d.cpp b/src/mongo/db/auth/authz_manager_external_state_d.cpp index 92f92f46e24..60958cdeff1 100644 --- a/src/mongo/db/auth/authz_manager_external_state_d.cpp +++ b/src/mongo/db/auth/authz_manager_external_state_d.cpp @@ -33,14 +33,9 @@ #include <string> #include "mongo/base/status.h" -#include "mongo/bson/mutable/document.h" -#include "mongo/bson/mutable/element.h" -#include "mongo/bson/util/bson_extract.h" #include "mongo/db/auth/authorization_manager.h" -#include "mongo/db/auth/user_document_parser.h" #include "mongo/db/auth/user_name.h" #include "mongo/db/client.h" -#include "mongo/db/d_concurrency.h" #include "mongo/db/dbhelpers.h" #include "mongo/db/instance.h" #include "mongo/db/jsobj.h" @@ -49,245 +44,40 @@ namespace mongo { - AuthzManagerExternalStateMongod::AuthzManagerExternalStateMongod() : - _roleGraphState(roleGraphStateInitial) {} - + AuthzManagerExternalStateMongod::AuthzManagerExternalStateMongod() {} AuthzManagerExternalStateMongod::~AuthzManagerExternalStateMongod() {} - Status AuthzManagerExternalStateMongod::initialize() { - Status status = _initializeRoleGraph(); - if (!status.isOK()) { - if (status == ErrorCodes::GraphContainsCycle) { - error() << "Cycle detected in admin.system.roles; role inheritance disabled. " - "Remove the listed cycle and any others to re-enable role inheritance. " << - status.reason(); - } - else { - error() << "Could not generate role graph from admin.system.roles; " - "only system roles available. TODO EXPLAIN REMEDY. " << status; - } - } - - return Status::OK(); - } - -namespace { - const Status userNotFoundStatus(ErrorCodes::UserNotFound, "User not found"); - - void addRoleNameToObjectElement(mutablebson::Element object, const RoleName& role) { - fassert(17153, object.appendString(AuthorizationManager::ROLE_NAME_FIELD_NAME, role.getRole())); - fassert(17154, object.appendString(AuthorizationManager::ROLE_SOURCE_FIELD_NAME, role.getDB())); - } - - void addRoleNameObjectsToArrayElement(mutablebson::Element array, RoleNameIterator roles) { - for (; roles.more(); roles.next()) { - mutablebson::Element roleElement = array.getDocument().makeElementObject(""); - addRoleNameToObjectElement(roleElement, roles.get()); - fassert(17155, array.pushBack(roleElement)); - } - } - - void addPrivilegeObjectsOrWarningsToArrayElement(mutablebson::Element privilegesElement, - mutablebson::Element warningsElement, - const PrivilegeVector& privileges) { - std::string errmsg; - for (size_t i = 0; i < privileges.size(); ++i) { - ParsedPrivilege pp; - if (ParsedPrivilege::privilegeToParsedPrivilege(privileges[i], &pp, &errmsg)) { - fassert(17156, privilegesElement.appendObject("", pp.toBSON())); - } else { - fassert(17157, - warningsElement.appendString( - "", - std::string(mongoutils::str::stream() << - "Skipped privileges on resource " << - privileges[i].getResourcePattern().toString() << - ". Reason: " << errmsg))); - } - } - } -} // namespace - - Status AuthzManagerExternalStateMongod::getStoredAuthorizationVersion(int* outVersion) { - { - Client::ReadContext ctx(AuthorizationManager::versionCollectionNamespace.ns()); - BSONObj versionDoc; - if (Helpers::findOne(AuthorizationManager::versionCollectionNamespace.ns(), - AuthorizationManager::versionDocumentQuery, - versionDoc)) { - BSONElement versionElement = versionDoc[ - AuthorizationManager::schemaVersionFieldName]; - if (versionElement.isNumber()) { - *outVersion = versionElement.numberInt(); - return Status::OK(); - } - else if (versionElement.eoo()) { - return Status(ErrorCodes::NoSuchKey, "No currentVersion field in version document."); - } - else { - return Status(ErrorCodes::TypeMismatch, mongoutils::str::stream() << - "Bad (non-numeric) type " << versionElement.type() << - "for currentVersion field in version document"); - } - } - } - if (hasAnyPrivilegeDocuments()) { - *outVersion = AuthorizationManager::schemaVersion24; - } - else { - *outVersion = AuthorizationManager::schemaVersion26Final; - } - return Status::OK(); - } - - Status AuthzManagerExternalStateMongod::getUserDescription(const UserName& userName, - BSONObj* result) { + Status AuthzManagerExternalStateMongod::_getUserDocument(const UserName& userName, + BSONObj* userDoc) { - BSONObj userDoc; - { - Client::ReadContext ctx("admin"); - int authzVersion; - Status status = getStoredAuthorizationVersion(&authzVersion); - if (!status.isOK()) - return status; - - switch (authzVersion) { - case AuthorizationManager::schemaVersion26Upgrade: - case AuthorizationManager::schemaVersion26Final: - break; - default: - return Status(ErrorCodes::AuthSchemaIncompatible, mongoutils::str::stream() << - "Unsupported schema version for getUserDescription(): " << - authzVersion); - } - - status = findOne( - (authzVersion == AuthorizationManager::schemaVersion26Final ? - AuthorizationManager::usersCollectionNamespace : - AuthorizationManager::usersAltCollectionNamespace), - BSON(AuthorizationManager::USER_NAME_FIELD_NAME << userName.getUser() << - AuthorizationManager::USER_DB_FIELD_NAME << userName.getDB()), - &userDoc); - if (status == ErrorCodes::NoMatchingDocument) { - status = Status(ErrorCodes::UserNotFound, mongoutils::str::stream() << - "Could not find description of user " << userName.getFullName()); - } - if (!status.isOK()) - return status; - } - - BSONElement directRolesElement; - Status status = bsonExtractTypedField(userDoc, "roles", Array, &directRolesElement); - if (!status.isOK()) - return status; - std::vector<RoleName> directRoles; - status = V2UserDocumentParser::parseRoleVector(BSONArray(directRolesElement.Obj()), - &directRoles); + Client::ReadContext ctx("admin"); + int authzVersion; + Status status = getStoredAuthorizationVersion(&authzVersion); if (!status.isOK()) return status; - unordered_set<RoleName> indirectRoles; - PrivilegeVector allPrivileges; - bool isRoleGraphInconsistent; - { - boost::lock_guard<boost::mutex> lk(_roleGraphMutex); - isRoleGraphInconsistent = _roleGraphState == roleGraphStateConsistent; - for (size_t i = 0; i < directRoles.size(); ++i) { - const RoleName& role(directRoles[i]); - indirectRoles.insert(role); - if (isRoleGraphInconsistent) { - for (RoleNameIterator subordinates = _roleGraph.getIndirectSubordinates(role); - subordinates.more(); - subordinates.next()) { - - indirectRoles.insert(subordinates.get()); - } - } - const PrivilegeVector& rolePrivileges( - isRoleGraphInconsistent ? - _roleGraph.getAllPrivileges(role) : - _roleGraph.getDirectPrivileges(role)); - for (PrivilegeVector::const_iterator priv = rolePrivileges.begin(), - end = rolePrivileges.end(); - priv != end; - ++priv) { - - Privilege::addPrivilegeToPrivilegeVector(&allPrivileges, *priv); - } - } - } - - mutablebson::Document resultDoc(userDoc, mutablebson::Document::kInPlaceDisabled); - mutablebson::Element indirectRolesElement = resultDoc.makeElementArray("indirectRoles"); - mutablebson::Element privilegesElement = resultDoc.makeElementArray("privileges"); - mutablebson::Element warningsElement = resultDoc.makeElementArray("warnings"); - fassert(17158, resultDoc.root().pushBack(privilegesElement)); - fassert(17159, resultDoc.root().pushBack(indirectRolesElement)); - if (!isRoleGraphInconsistent) { - fassert(17160, warningsElement.appendString( - "", "Role graph inconsistent, only direct privileges available.")); - } - addRoleNameObjectsToArrayElement(indirectRolesElement, - makeRoleNameIteratorForContainer(indirectRoles)); - addPrivilegeObjectsOrWarningsToArrayElement( - privilegesElement, warningsElement, allPrivileges); - if (warningsElement.hasChildren()) { - fassert(17161, resultDoc.root().pushBack(warningsElement)); - } - *result = resultDoc.getObject(); - return Status::OK(); - } - - Status AuthzManagerExternalStateMongod::getRoleDescription(const RoleName& roleName, - BSONObj* result) { - boost::lock_guard<boost::mutex> lk(_roleGraphMutex); - if (!_roleGraph.roleExists(roleName)) - return Status(ErrorCodes::RoleNotFound, "No role named " + roleName.toString()); - - mutablebson::Document resultDoc; - fassert(17162, resultDoc.root().appendString( - AuthorizationManager::ROLE_NAME_FIELD_NAME, roleName.getRole())); - fassert(17163, resultDoc.root().appendString( - AuthorizationManager::ROLE_SOURCE_FIELD_NAME, roleName.getDB())); - mutablebson::Element rolesElement = resultDoc.makeElementArray("roles"); - fassert(17164, resultDoc.root().pushBack(rolesElement)); - mutablebson::Element indirectRolesElement = resultDoc.makeElementArray("indirectRoles"); - fassert(17165, resultDoc.root().pushBack(indirectRolesElement)); - mutablebson::Element privilegesElement = resultDoc.makeElementArray("privileges"); - fassert(17166, resultDoc.root().pushBack(privilegesElement)); - 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)); - } - else { - warningsElement.appendString( - "", "Role graph state inconsistent; only direct privileges available."); - addPrivilegeObjectsOrWarningsToArrayElement( - privilegesElement, warningsElement, _roleGraph.getDirectPrivileges(roleName)); + switch (authzVersion) { + case AuthorizationManager::schemaVersion26Upgrade: + case AuthorizationManager::schemaVersion26Final: + break; + default: + return Status(ErrorCodes::AuthSchemaIncompatible, mongoutils::str::stream() << + "Unsupported schema version for getUserDescription(): " << + authzVersion); } - if (warningsElement.hasChildren()) { - fassert(17167, resultDoc.root().pushBack(warningsElement)); - } - *result = resultDoc.getObject(); - return Status::OK(); - } - - Status AuthzManagerExternalStateMongod::findOne(const NamespaceString& collectionName, - const BSONObj& query, - BSONObj* result) { - Client::ReadContext ctx(collectionName); - if (!Helpers::findOne(collectionName.ns(), query, *result)) { - return Status(ErrorCodes::NoMatchingDocument, mongoutils::str::stream() << - "No document in " << collectionName.ns() << " matches " << query); + status = findOne( + (authzVersion == AuthorizationManager::schemaVersion26Final ? + AuthorizationManager::usersCollectionNamespace : + AuthorizationManager::usersAltCollectionNamespace), + BSON(AuthorizationManager::USER_NAME_FIELD_NAME << userName.getUser() << + AuthorizationManager::USER_DB_FIELD_NAME << userName.getDB()), + userDoc); + if (status == ErrorCodes::NoMatchingDocument) { + status = Status(ErrorCodes::UserNotFound, mongoutils::str::stream() << + "Could not find user " << userName.getFullName()); } - *result = result->getOwned(); - return Status::OK(); + return status; } Status AuthzManagerExternalStateMongod::query( @@ -311,6 +101,23 @@ namespace { return Status::OK(); } + Status AuthzManagerExternalStateMongod::findOne( + const NamespaceString& collectionName, + const BSONObj& query, + BSONObj* result) { + + Client::ReadContext ctx(collectionName.ns()); + BSONObj found; + if (Helpers::findOne(collectionName.ns(), + query, + found)) { + *result = found.getOwned(); + return Status::OK(); + } + return Status(ErrorCodes::NoMatchingDocument, mongoutils::str::stream() << + "No document in " << collectionName.ns() << " matches " << query); + } + Status AuthzManagerExternalStateMongod::insert( const NamespaceString& collectionName, const BSONObj& document, @@ -399,13 +206,48 @@ namespace { const BSONObj& pattern, bool unique, const BSONObj& writeConcern) { - fassertFailed(17095); + DBDirectClient client; + try { + if (client.ensureIndex(collectionName.ns(), + pattern, + unique)) { + BSONObjBuilder gleBuilder; + gleBuilder.append("getLastError", 1); + gleBuilder.appendElements(writeConcern); + BSONObj res; + client.runCommand("admin", gleBuilder.done(), res); + string errstr = client.getLastErrorString(res); + if (!errstr.empty()) { + return Status(ErrorCodes::UnknownError, errstr); + } + } + return Status::OK(); + } + catch (const DBException& ex) { + return ex.toStatus(); + } } Status AuthzManagerExternalStateMongod::dropIndexes( const NamespaceString& collectionName, const BSONObj& writeConcern) { - fassertFailed(0); + DBDirectClient client; + try { + client.dropIndexes(collectionName.ns()); + BSONObjBuilder gleBuilder; + gleBuilder.append("getLastError", 1); + gleBuilder.appendElements(writeConcern); + BSONObj res; + client.runCommand("admin", gleBuilder.done(), res); + string errstr = client.getLastErrorString(res); + if (!errstr.empty()) { + return Status(ErrorCodes::UnknownError, errstr); + } + return Status::OK(); + } + catch (const DBException& ex) { + return ex.toStatus(); + } } bool AuthzManagerExternalStateMongod::tryAcquireAuthzUpdateLock(const StringData& why) { @@ -418,95 +260,4 @@ namespace { return _authzDataUpdateLock.unlock(); } -namespace { - - /** - * Adds the role described in "doc" to "roleGraph". If the role cannot be added, due to - * some error in "doc", logs a warning. - */ - void addRoleFromDocumentOrWarn(RoleGraph* roleGraph, const BSONObj& doc) { - Status status = roleGraph->addRoleFromDocument(doc); - if (!status.isOK()) { - warning() << "Skipping invalid role document. " << status << "; document " << doc; - } - } - - -} // namespace - - Status AuthzManagerExternalStateMongod::_initializeRoleGraph() { - boost::lock_guard<boost::mutex> lkInitialzeRoleGraph(_roleGraphMutex); - - _roleGraphState = roleGraphStateInitial; - _roleGraph = RoleGraph(); - - RoleGraph newRoleGraph; - Status status = query( - AuthorizationManager::rolesCollectionNamespace, - BSONObj(), - BSONObj(), - boost::bind(addRoleFromDocumentOrWarn, &newRoleGraph, _1)); - if (!status.isOK()) - return status; - - status = newRoleGraph.recomputePrivilegeData(); - - RoleGraphState newState; - if (status == ErrorCodes::GraphContainsCycle) { - error() << "Inconsistent role graph during authorization manager intialization. Only " - "direct privileges available. " << status.reason(); - newState = roleGraphStateHasCycle; - status = Status::OK(); - } - else if (status.isOK()) { - newState = roleGraphStateConsistent; - } - else { - newState = roleGraphStateInitial; - } - - if (status.isOK()) { - _roleGraph.swap(newRoleGraph); - _roleGraphState = newState; - } - return status; - } - - void AuthzManagerExternalStateMongod::logOp( - const char* op, - const char* ns, - const BSONObj& o, - BSONObj* o2, - bool* b) { - - if (ns == AuthorizationManager::rolesCollectionNamespace.ns() || - ns == AuthorizationManager::adminCommandNamespace.ns()) { - - boost::lock_guard<boost::mutex> lk(_roleGraphMutex); - Status status = _roleGraph.handleLogOp(op, NamespaceString(ns), o, o2); - - if (status == ErrorCodes::OplogOperationUnsupported) { - _roleGraph = RoleGraph(); - _roleGraphState = roleGraphStateInitial; - error() << "Unsupported modification to roles collection in oplog; " - "TODO how to remedy. " << status << " Oplog entry: " << op; - } - else if (!status.isOK()) { - warning() << "Skipping bad update to roles collection in oplog. " << status << - " Oplog entry: " << op; - } - status = _roleGraph.recomputePrivilegeData(); - if (status == ErrorCodes::GraphContainsCycle) { - _roleGraphState = roleGraphStateHasCycle; - error() << "Inconsistent role graph during authorization manager intialization. " - "Only direct privileges available. " << status.reason() << - " after applying oplog entry " << op; - } - else { - fassert(17183, status); - _roleGraphState = roleGraphStateConsistent; - } - } - } - } // namespace mongo diff --git a/src/mongo/db/auth/authz_manager_external_state_d.h b/src/mongo/db/auth/authz_manager_external_state_d.h index 22e99d5573a..193126d2247 100644 --- a/src/mongo/db/auth/authz_manager_external_state_d.h +++ b/src/mongo/db/auth/authz_manager_external_state_d.h @@ -34,7 +34,7 @@ #include "mongo/base/disallow_copying.h" #include "mongo/base/status.h" -#include "mongo/db/auth/authz_manager_external_state.h" +#include "mongo/db/auth/authz_manager_external_state_local.h" #include "mongo/db/auth/role_graph.h" #include "mongo/db/auth/user_name.h" @@ -43,19 +43,13 @@ namespace mongo { /** * The implementation of AuthzManagerExternalState functionality for mongod. */ - class AuthzManagerExternalStateMongod : public AuthzManagerExternalState { + class AuthzManagerExternalStateMongod : public AuthzManagerExternalStateLocal { MONGO_DISALLOW_COPYING(AuthzManagerExternalStateMongod); public: AuthzManagerExternalStateMongod(); virtual ~AuthzManagerExternalStateMongod(); - 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 getAllDatabaseNames(std::vector<std::string>* dbnames); virtual Status findOne(const NamespaceString& collectionName, @@ -88,41 +82,8 @@ namespace mongo { virtual bool tryAcquireAuthzUpdateLock(const StringData& why); virtual void releaseAuthzUpdateLock(); - virtual void logOp( - const char* op, - const char* ns, - const BSONObj& o, - BSONObj* o2, - bool* b); - private: - enum RoleGraphState { - roleGraphStateInitial = 0, - roleGraphStateConsistent, - roleGraphStateHasCycle - }; - - /** - * Initializes the role graph from the contents of the admin.system.roles collection. - */ - Status _initializeRoleGraph(); - - /** - * Eventually consistent, in-memory representation of all roles in the system (both - * user-defined and built-in). Synchronized via _roleGraphMutex. - */ - RoleGraph _roleGraph; - - /** - * State of _roleGraph, one of "initial", "consistent" and "has cycle". Synchronized via - * _roleGraphMutex. - */ - RoleGraphState _roleGraphState; - - /** - * Guards _roleGraphState and _roleGraph. - */ - boost::mutex _roleGraphMutex; + virtual Status _getUserDocument(const UserName& userName, BSONObj* userDoc); boost::timed_mutex _authzDataUpdateLock; }; diff --git a/src/mongo/db/auth/authz_manager_external_state_local.cpp b/src/mongo/db/auth/authz_manager_external_state_local.cpp new file mode 100644 index 00000000000..13cf991bda7 --- /dev/null +++ b/src/mongo/db/auth/authz_manager_external_state_local.cpp @@ -0,0 +1,338 @@ +/** +* Copyright (C) 2012 10gen Inc. +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Affero General Public License, version 3, +* as published by the Free Software Foundation. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU Affero General Public License for more details. +* +* You should have received a copy of the GNU Affero General Public License +* along with this program. If not, see <http://www.gnu.org/licenses/>. +* +* As a special exception, the copyright holders give permission to link the +* code of portions of this program with the OpenSSL library under certain +* conditions as described in each individual source file and distribute +* linked combinations including the program with the OpenSSL library. You +* must comply with the GNU Affero General Public License in all respects for +* all of the code used other than as permitted herein. If you modify file(s) +* with this exception, you may extend this exception to your version of the +* file(s), but you are not obligated to do so. If you do not wish to do so, +* delete this exception statement from your version. If you delete this +* exception statement from all source files in the program, then also delete +* it in the license file. +*/ + +#include "mongo/db/auth/authz_manager_external_state_local.h" + +#include "mongo/base/status.h" +#include "mongo/bson/mutable/document.h" +#include "mongo/bson/mutable/element.h" +#include "mongo/bson/util/bson_extract.h" +#include "mongo/db/auth/authorization_manager.h" +#include "mongo/db/auth/user_document_parser.h" +#include "mongo/util/mongoutils/str.h" + +namespace mongo { + + AuthzManagerExternalStateLocal::AuthzManagerExternalStateLocal() : + _roleGraphState(roleGraphStateInitial) {} + AuthzManagerExternalStateLocal::~AuthzManagerExternalStateLocal() {} + + Status AuthzManagerExternalStateLocal::initialize() { + Status status = _initializeRoleGraph(); + if (!status.isOK()) { + if (status == ErrorCodes::GraphContainsCycle) { + error() << "Cycle detected in admin.system.roles; role inheritance disabled. " + "Remove the listed cycle and any others to re-enable role inheritance. " << + status.reason(); + } + else { + error() << "Could not generate role graph from admin.system.roles; " + "only system roles available. TODO EXPLAIN REMEDY. " << status; + } + } + + return Status::OK(); + } + + Status AuthzManagerExternalStateLocal::getStoredAuthorizationVersion(int* outVersion) { + BSONObj versionDoc; + Status status = findOne(AuthorizationManager::versionCollectionNamespace, + AuthorizationManager::versionDocumentQuery, + &versionDoc); + if (status.isOK()) { + BSONElement versionElement = versionDoc[AuthorizationManager::schemaVersionFieldName]; + if (versionElement.isNumber()) { + *outVersion = versionElement.numberInt(); + return Status::OK(); + } + else if (versionElement.eoo()) { + return Status(ErrorCodes::NoSuchKey, mongoutils::str::stream() << + "No " << AuthorizationManager::schemaVersionFieldName << + " field in version document."); + } + else { + return Status(ErrorCodes::TypeMismatch, mongoutils::str::stream() << + "Bad (non-numeric) type " << versionElement.type() << + "for " << AuthorizationManager::schemaVersionFieldName << + " field in version document"); + } + } + else if (status == ErrorCodes::NoMatchingDocument) { + if (hasAnyPrivilegeDocuments()) { + *outVersion = AuthorizationManager::schemaVersion24; + } + else { + *outVersion = AuthorizationManager::schemaVersion26Final; + } + return Status::OK(); + } + else { + return status; + } + } + +namespace { + void addRoleNameToObjectElement(mutablebson::Element object, const RoleName& role) { + fassert(17153, object.appendString(AuthorizationManager::ROLE_NAME_FIELD_NAME, + role.getRole())); + fassert(17154, object.appendString(AuthorizationManager::ROLE_SOURCE_FIELD_NAME, + role.getDB())); + } + + void addRoleNameObjectsToArrayElement(mutablebson::Element array, RoleNameIterator roles) { + for (; roles.more(); roles.next()) { + mutablebson::Element roleElement = array.getDocument().makeElementObject(""); + addRoleNameToObjectElement(roleElement, roles.get()); + fassert(17155, array.pushBack(roleElement)); + } + } + + void addPrivilegeObjectsOrWarningsToArrayElement(mutablebson::Element privilegesElement, + mutablebson::Element warningsElement, + const PrivilegeVector& privileges) { + std::string errmsg; + for (size_t i = 0; i < privileges.size(); ++i) { + ParsedPrivilege pp; + if (ParsedPrivilege::privilegeToParsedPrivilege(privileges[i], &pp, &errmsg)) { + fassert(17156, privilegesElement.appendObject("", pp.toBSON())); + } else { + fassert(17157, + warningsElement.appendString( + "", + std::string(mongoutils::str::stream() << + "Skipped privileges on resource " << + privileges[i].getResourcePattern().toString() << + ". Reason: " << errmsg))); + } + } + } +} // namespace + + Status AuthzManagerExternalStateLocal::getUserDescription( + const UserName& userName, + BSONObj* result) { + + BSONObj userDoc; + Status status = _getUserDocument(userName, &userDoc); + if (!status.isOK()) + return status; + + BSONElement directRolesElement; + status = bsonExtractTypedField(userDoc, "roles", Array, &directRolesElement); + if (!status.isOK()) + return status; + std::vector<RoleName> directRoles; + status = V2UserDocumentParser::parseRoleVector(BSONArray(directRolesElement.Obj()), + &directRoles); + if (!status.isOK()) + return status; + + unordered_set<RoleName> indirectRoles; + PrivilegeVector allPrivileges; + bool isRoleGraphInconsistent; + { + boost::lock_guard<boost::mutex> lk(_roleGraphMutex); + isRoleGraphInconsistent = _roleGraphState == roleGraphStateConsistent; + for (size_t i = 0; i < directRoles.size(); ++i) { + const RoleName& role(directRoles[i]); + indirectRoles.insert(role); + if (isRoleGraphInconsistent) { + for (RoleNameIterator subordinates = _roleGraph.getIndirectSubordinates(role); + subordinates.more(); + subordinates.next()) { + + indirectRoles.insert(subordinates.get()); + } + } + const PrivilegeVector& rolePrivileges( + isRoleGraphInconsistent ? + _roleGraph.getAllPrivileges(role) : + _roleGraph.getDirectPrivileges(role)); + for (PrivilegeVector::const_iterator priv = rolePrivileges.begin(), + end = rolePrivileges.end(); + priv != end; + ++priv) { + + Privilege::addPrivilegeToPrivilegeVector(&allPrivileges, *priv); + } + } + } + + mutablebson::Document resultDoc(userDoc, mutablebson::Document::kInPlaceDisabled); + mutablebson::Element indirectRolesElement = resultDoc.makeElementArray("indirectRoles"); + mutablebson::Element privilegesElement = resultDoc.makeElementArray("privileges"); + mutablebson::Element warningsElement = resultDoc.makeElementArray("warnings"); + fassert(17158, resultDoc.root().pushBack(privilegesElement)); + fassert(17159, resultDoc.root().pushBack(indirectRolesElement)); + if (!isRoleGraphInconsistent) { + fassert(17160, warningsElement.appendString( + "", "Role graph inconsistent, only direct privileges available.")); + } + addRoleNameObjectsToArrayElement(indirectRolesElement, + makeRoleNameIteratorForContainer(indirectRoles)); + addPrivilegeObjectsOrWarningsToArrayElement( + privilegesElement, warningsElement, allPrivileges); + if (warningsElement.hasChildren()) { + fassert(17161, resultDoc.root().pushBack(warningsElement)); + } + *result = resultDoc.getObject(); + return Status::OK(); + } + + Status AuthzManagerExternalStateLocal::getRoleDescription( + const RoleName& roleName, + BSONObj* result) { + boost::lock_guard<boost::mutex> lk(_roleGraphMutex); + if (!_roleGraph.roleExists(roleName)) + return Status(ErrorCodes::RoleNotFound, "No role named " + roleName.toString()); + + mutablebson::Document resultDoc; + fassert(17162, resultDoc.root().appendString( + AuthorizationManager::ROLE_NAME_FIELD_NAME, roleName.getRole())); + fassert(17163, resultDoc.root().appendString( + AuthorizationManager::ROLE_SOURCE_FIELD_NAME, roleName.getDB())); + mutablebson::Element rolesElement = resultDoc.makeElementArray("roles"); + fassert(17164, resultDoc.root().pushBack(rolesElement)); + mutablebson::Element indirectRolesElement = resultDoc.makeElementArray("indirectRoles"); + fassert(17165, resultDoc.root().pushBack(indirectRolesElement)); + mutablebson::Element privilegesElement = resultDoc.makeElementArray("privileges"); + fassert(17166, resultDoc.root().pushBack(privilegesElement)); + 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)); + } + else { + warningsElement.appendString( + "", "Role graph state inconsistent; only direct privileges available."); + addPrivilegeObjectsOrWarningsToArrayElement( + privilegesElement, warningsElement, _roleGraph.getDirectPrivileges(roleName)); + } + if (warningsElement.hasChildren()) { + fassert(17167, resultDoc.root().pushBack(warningsElement)); + } + *result = resultDoc.getObject(); + return Status::OK(); + } + +namespace { + + /** + * Adds the role described in "doc" to "roleGraph". If the role cannot be added, due to + * some error in "doc", logs a warning. + */ + void addRoleFromDocumentOrWarn(RoleGraph* roleGraph, const BSONObj& doc) { + Status status = roleGraph->addRoleFromDocument(doc); + if (!status.isOK()) { + warning() << "Skipping invalid role document. " << status << "; document " << doc; + } + } + + +} // namespace + + Status AuthzManagerExternalStateLocal::_initializeRoleGraph() { + boost::lock_guard<boost::mutex> lkInitialzeRoleGraph(_roleGraphMutex); + + _roleGraphState = roleGraphStateInitial; + _roleGraph = RoleGraph(); + + RoleGraph newRoleGraph; + Status status = query( + AuthorizationManager::rolesCollectionNamespace, + BSONObj(), + BSONObj(), + boost::bind(addRoleFromDocumentOrWarn, &newRoleGraph, _1)); + if (!status.isOK()) + return status; + + status = newRoleGraph.recomputePrivilegeData(); + + RoleGraphState newState; + if (status == ErrorCodes::GraphContainsCycle) { + error() << "Inconsistent role graph during authorization manager intialization. Only " + "direct privileges available. " << status.reason(); + newState = roleGraphStateHasCycle; + status = Status::OK(); + } + else if (status.isOK()) { + newState = roleGraphStateConsistent; + } + else { + newState = roleGraphStateInitial; + } + + if (status.isOK()) { + _roleGraph.swap(newRoleGraph); + _roleGraphState = newState; + } + return status; + } + + void AuthzManagerExternalStateLocal::logOp( + const char* op, + const char* ns, + const BSONObj& o, + BSONObj* o2, + bool* b) { + + if (ns == AuthorizationManager::rolesCollectionNamespace.ns() || + ns == AuthorizationManager::adminCommandNamespace.ns()) { + + boost::lock_guard<boost::mutex> lk(_roleGraphMutex); + Status status = _roleGraph.handleLogOp(op, NamespaceString(ns), o, o2); + + if (status == ErrorCodes::OplogOperationUnsupported) { + _roleGraph = RoleGraph(); + _roleGraphState = roleGraphStateInitial; + error() << "Unsupported modification to roles collection in oplog; " + "TODO how to remedy. " << status << " Oplog entry: " << op; + } + else if (!status.isOK()) { + warning() << "Skipping bad update to roles collection in oplog. " << status << + " Oplog entry: " << op; + } + status = _roleGraph.recomputePrivilegeData(); + if (status == ErrorCodes::GraphContainsCycle) { + _roleGraphState = roleGraphStateHasCycle; + error() << "Inconsistent role graph during authorization manager intialization. " + "Only direct privileges available. " << status.reason() << + " after applying oplog entry " << op; + } + else { + fassert(17183, status); + _roleGraphState = roleGraphStateConsistent; + } + } + } + +} // namespace mongo diff --git a/src/mongo/db/auth/authz_manager_external_state_local.h b/src/mongo/db/auth/authz_manager_external_state_local.h new file mode 100644 index 00000000000..14016dacbc5 --- /dev/null +++ b/src/mongo/db/auth/authz_manager_external_state_local.h @@ -0,0 +1,107 @@ +/** +* Copyright (C) 2012 10gen Inc. +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU Affero General Public License, version 3, +* as published by the Free Software Foundation. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU Affero General Public License for more details. +* +* You should have received a copy of the GNU Affero General Public License +* along with this program. If not, see <http://www.gnu.org/licenses/>. +* +* As a special exception, the copyright holders give permission to link the +* code of portions of this program with the OpenSSL library under certain +* conditions as described in each individual source file and distribute +* linked combinations including the program with the OpenSSL library. You +* must comply with the GNU Affero General Public License in all respects for +* all of the code used other than as permitted herein. If you modify file(s) +* with this exception, you may extend this exception to your version of the +* file(s), but you are not obligated to do so. If you do not wish to do so, +* delete this exception statement from your version. If you delete this +* exception statement from all source files in the program, then also delete +* it in the license file. +*/ + +#pragma once + +#include <boost/function.hpp> +#include <boost/thread/mutex.hpp> +#include <string> + +#include "mongo/base/disallow_copying.h" +#include "mongo/base/status.h" +#include "mongo/db/auth/authz_manager_external_state.h" +#include "mongo/db/auth/role_graph.h" +#include "mongo/db/auth/role_name.h" +#include "mongo/db/auth/user_name.h" + +namespace mongo { + + /** + * Common implementation of AuthzManagerExternalState for systems where role + * and user information are stored locally. + */ + class AuthzManagerExternalStateLocal : public AuthzManagerExternalState { + MONGO_DISALLOW_COPYING(AuthzManagerExternalStateLocal); + + public: + virtual ~AuthzManagerExternalStateLocal(); + + 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 void logOp( + const char* op, + const char* ns, + const BSONObj& o, + BSONObj* o2, + bool* b); + + protected: + AuthzManagerExternalStateLocal(); + + private: + enum RoleGraphState { + roleGraphStateInitial = 0, + roleGraphStateConsistent, + roleGraphStateHasCycle + }; + + /** + * Initializes the role graph from the contents of the admin.system.roles collection. + */ + Status _initializeRoleGraph(); + + /** + * Fetches the user document for "userName" from local storage, and stores it into "result". + */ + virtual Status _getUserDocument(const UserName& userName, BSONObj* result) = 0; + + /** + * Eventually consistent, in-memory representation of all roles in the system (both + * user-defined and built-in). Synchronized via _roleGraphMutex. + */ + RoleGraph _roleGraph; + + /** + * State of _roleGraph, one of "initial", "consistent" and "has cycle". Synchronized via + * _roleGraphMutex. + */ + RoleGraphState _roleGraphState; + + /** + * Guards _roleGraphState and _roleGraph. + */ + boost::mutex _roleGraphMutex; + + boost::timed_mutex _authzDataUpdateLock; + }; + +} // namespace mongo diff --git a/src/mongo/db/auth/authz_manager_external_state_mock.cpp b/src/mongo/db/auth/authz_manager_external_state_mock.cpp index 21e2dac4662..26b3f38eca2 100644 --- a/src/mongo/db/auth/authz_manager_external_state_mock.cpp +++ b/src/mongo/db/auth/authz_manager_external_state_mock.cpp @@ -82,79 +82,56 @@ namespace { AuthzManagerExternalStateMock::AuthzManagerExternalStateMock() {} AuthzManagerExternalStateMock::~AuthzManagerExternalStateMock() {} - Status AuthzManagerExternalStateMock::initialize() { - return Status::OK(); + void AuthzManagerExternalStateMock::setAuthzVersion(int version) { + uassertStatusOK( + updateOne(AuthorizationManager::versionCollectionNamespace, + AuthorizationManager::versionDocumentQuery, + BSON("$set" << BSON(AuthorizationManager::schemaVersionFieldName << + version)), + true, + BSONObj())); } Status AuthzManagerExternalStateMock::getStoredAuthorizationVersion(int* outVersion) { - if (_authzVersion < 0) { - return Status(ErrorCodes::UnknownError, - "Mock configured to fail getStoredAuthorizationVersion()"); + Status status = AuthzManagerExternalStateLocal::getStoredAuthorizationVersion(outVersion); + if (status.isOK() && outVersion < 0) { + status = Status(ErrorCodes::UnknownError, + "Mock configured to fail getStoredAuthorizationVersion()"); } - *outVersion = _authzVersion; - return Status::OK(); + return status; } - Status AuthzManagerExternalStateMock::getUserDescription( - const UserName& userName, BSONObj* result) { - BSONObj privDoc; - Status status = _findUser( - "admin.system.users", - BSON(AuthorizationManager::USER_NAME_FIELD_NAME << userName.getUser() << - AuthorizationManager::USER_DB_FIELD_NAME << userName.getDB()), - &privDoc); + Status AuthzManagerExternalStateMock::_getUserDocument(const UserName& userName, + BSONObj* userDoc) { + int authzVersion; + Status status = getStoredAuthorizationVersion(&authzVersion); if (!status.isOK()) return status; - unordered_set<RoleName> indirectRoles; - PrivilegeVector allPrivileges; - for (BSONObjIterator iter(privDoc["roles"].Obj()); iter.more(); iter.next()) { - if (!(*iter)["hasRole"].trueValue()) - continue; - RoleName roleName((*iter)[AuthorizationManager::ROLE_NAME_FIELD_NAME].str(), - (*iter)[AuthorizationManager::ROLE_SOURCE_FIELD_NAME].str()); - indirectRoles.insert(roleName); - for (RoleNameIterator subordinates = _roleGraph.getIndirectSubordinates( - roleName); - subordinates.more(); - subordinates.next()) { - - indirectRoles.insert(subordinates.get()); - } - const PrivilegeVector& rolePrivileges(_roleGraph.getAllPrivileges(roleName)); - for (PrivilegeVector::const_iterator priv = rolePrivileges.begin(), - end = rolePrivileges.end(); - priv != end; - ++priv) { - - Privilege::addPrivilegeToPrivilegeVector(&allPrivileges, *priv); - } + switch (authzVersion) { + case AuthorizationManager::schemaVersion26Upgrade: + case AuthorizationManager::schemaVersion26Final: + break; + default: + return Status(ErrorCodes::AuthSchemaIncompatible, mongoutils::str::stream() << + "Unsupported schema version for getUserDescription(): " << + authzVersion); } - mutablebson::Document userDoc(privDoc, mutablebson::Document::kInPlaceDisabled); - mutablebson::Element indirectRolesElement = userDoc.makeElementArray("indirectRoles"); - mutablebson::Element privilegesElement = userDoc.makeElementArray("privileges"); - mutablebson::Element warningsElement = userDoc.makeElementArray("warnings"); - fassert(17180, userDoc.root().pushBack(privilegesElement)); - fassert(17181, userDoc.root().pushBack(indirectRolesElement)); - - addRoleNameObjectsToArrayElement(indirectRolesElement, - makeRoleNameIteratorForContainer(indirectRoles)); - addPrivilegeObjectsOrWarningsToArrayElement( - privilegesElement, warningsElement, allPrivileges); - if (warningsElement.hasChildren()) { - fassert(17182, userDoc.root().pushBack(warningsElement)); + status = findOne( + (authzVersion == AuthorizationManager::schemaVersion26Final ? + AuthorizationManager::usersCollectionNamespace : + AuthorizationManager::usersAltCollectionNamespace), + BSON(AuthorizationManager::USER_NAME_FIELD_NAME << userName.getUser() << + AuthorizationManager::USER_DB_FIELD_NAME << userName.getDB()), + userDoc); + if (status == ErrorCodes::NoMatchingDocument) { + status = Status(ErrorCodes::UserNotFound, mongoutils::str::stream() << + "Could not find user " << userName.getFullName()); } - *result = userDoc.getObject(); - return Status::OK(); - } - - Status AuthzManagerExternalStateMock::getRoleDescription( - const RoleName& roleName, BSONObj* result) { - return Status(ErrorCodes::RoleNotFound, "Not implemented"); + return status; } - Status AuthzManagerExternalStateMock::updatePrivilegeDocument(const UserName& user, const BSONObj& updateObj, const BSONObj&) { @@ -174,10 +151,6 @@ namespace { return insert(usersCollection, userObj, writeConcern); } - void AuthzManagerExternalStateMock::clearPrivilegeDocuments() { - _documents.clear(); - } - Status AuthzManagerExternalStateMock::getAllDatabaseNames( std::vector<std::string>* dbnames) { unordered_set<std::string> dbnameSet; @@ -193,12 +166,11 @@ namespace { const std::string& usersNamespace, const BSONObj& query, BSONObj* result) { - Status status = findOne(NamespaceString(usersNamespace), query, result); - if (status == ErrorCodes::NoMatchingDocument) { - status = Status(ErrorCodes::UserNotFound, - "No matching user for query " + query.toString()); + if (!findOne(NamespaceString(usersNamespace), query, result).isOK()) { + return Status(ErrorCodes::UserNotFound, + "No matching user for query " + query.toString()); } - return status; + return Status::OK(); } Status AuthzManagerExternalStateMock::findOne( diff --git a/src/mongo/db/auth/authz_manager_external_state_mock.h b/src/mongo/db/auth/authz_manager_external_state_mock.h index 8ca0dfc7f28..770841546bf 100644 --- a/src/mongo/db/auth/authz_manager_external_state_mock.h +++ b/src/mongo/db/auth/authz_manager_external_state_mock.h @@ -35,7 +35,7 @@ #include "mongo/base/disallow_copying.h" #include "mongo/base/status.h" -#include "mongo/db/auth/authz_manager_external_state.h" +#include "mongo/db/auth/authz_manager_external_state_local.h" #include "mongo/db/auth/role_graph.h" #include "mongo/db/jsobj.h" #include "mongo/db/namespace_string.h" @@ -45,7 +45,7 @@ namespace mongo { /** * Mock of the AuthzManagerExternalState class used only for testing. */ - class AuthzManagerExternalStateMock : public AuthzManagerExternalState { + class AuthzManagerExternalStateMock : public AuthzManagerExternalStateLocal { MONGO_DISALLOW_COPYING(AuthzManagerExternalStateMock); public: @@ -53,13 +53,9 @@ namespace mongo { AuthzManagerExternalStateMock(); virtual ~AuthzManagerExternalStateMock(); - void setAuthzVersion(int v) { _authzVersion = v; } + void setAuthzVersion(int version); - 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 insertPrivilegeDocument(const std::string& dbname, const BSONObj& userObj, @@ -74,8 +70,6 @@ namespace mongo { const BSONObj& writeConcern, int* numRemoved); - void clearPrivilegeDocuments(); - virtual Status getAllDatabaseNames(std::vector<std::string>* dbnames); virtual Status findOne(const NamespaceString& collectionName, @@ -128,6 +122,8 @@ namespace mongo { typedef std::vector<BSONObj> BSONObjCollection; typedef std::map<NamespaceString, BSONObjCollection> NamespaceDocumentMap; + virtual Status _getUserDocument(const UserName& userName, BSONObj* userDoc); + Status _findOneIter(const NamespaceString& collectionName, const BSONObj& query, BSONObjCollection::iterator* result); @@ -138,8 +134,6 @@ namespace mongo { NamespaceDocumentMap _documents; // Mock database. - RoleGraph _roleGraph; - int _authzVersion; }; } // namespace mongo |