/** * 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 . * * 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. */ #define MONGO_LOG_DEFAULT_COMPONENT ::mongo::logger::LogComponent::kAccessControl #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/db/operation_context.h" #include "mongo/util/log.h" #include "mongo/util/mongoutils/str.h" namespace mongo { using std::vector; Status AuthzManagerExternalStateLocal::initialize(OperationContext* txn) { Status status = _initializeRoleGraph(txn); 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: " << status; } } return Status::OK(); } Status AuthzManagerExternalStateLocal::getStoredAuthorizationVersion(OperationContext* txn, int* outVersion) { BSONObj versionDoc; Status status = findOne(txn, 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() << "Could not determine schema version of authorization data. " "Bad (non-numeric) type " << typeName(versionElement.type()) << " (" << versionElement.type() << ") for " << AuthorizationManager::schemaVersionFieldName << " field in version document"); } } else if (status == ErrorCodes::NoMatchingDocument) { *outVersion = AuthorizationManager::schemaVersion28SCRAM; 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_DB_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 bool AuthzManagerExternalStateLocal::hasAnyPrivilegeDocuments(OperationContext* txn) { BSONObj userBSONObj; Status status = findOne(txn, AuthorizationManager::usersCollectionNamespace, BSONObj(), &userBSONObj); // If we were unable to complete the query, // it's best to assume that there _are_ privilege documents. return status != ErrorCodes::NoMatchingDocument; } Status AuthzManagerExternalStateLocal::getUserDescription(OperationContext* txn, const UserName& userName, BSONObj* result) { Status status = _getUserDocument(txn, userName, result); if (!status.isOK()) return status; BSONElement directRolesElement; status = bsonExtractTypedField(*result, "roles", Array, &directRolesElement); if (!status.isOK()) return status; std::vector directRoles; status = V2UserDocumentParser::parseRoleVector(BSONArray(directRolesElement.Obj()), &directRoles); if (!status.isOK()) return status; mutablebson::Document resultDoc(*result, mutablebson::Document::kInPlaceDisabled); resolveUserRoles(&resultDoc, directRoles); *result = resultDoc.getObject(); return Status::OK(); } void AuthzManagerExternalStateLocal::resolveUserRoles(mutablebson::Document* userDoc, const std::vector& directRoles) { unordered_set indirectRoles; PrivilegeVector allPrivileges; bool isRoleGraphInconsistent; { stdx::lock_guard 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::Element inheritedRolesElement = userDoc->makeElementArray("inheritedRoles"); mutablebson::Element privilegesElement = userDoc->makeElementArray("inheritedPrivileges"); mutablebson::Element warningsElement = userDoc->makeElementArray("warnings"); fassert(17159, userDoc->root().pushBack(inheritedRolesElement)); fassert(17158, userDoc->root().pushBack(privilegesElement)); if (!isRoleGraphInconsistent) { fassert(17160, warningsElement.appendString( "", "Role graph inconsistent, only direct privileges available.")); } addRoleNameObjectsToArrayElement(inheritedRolesElement, makeRoleNameIteratorForContainer(indirectRoles)); addPrivilegeObjectsOrWarningsToArrayElement(privilegesElement, warningsElement, allPrivileges); if (warningsElement.hasChildren()) { fassert(17161, userDoc->root().pushBack(warningsElement)); } } Status AuthzManagerExternalStateLocal::_getUserDocument(OperationContext* txn, const UserName& userName, BSONObj* userDoc) { Status status = findOne(txn, AuthorizationManager::usersCollectionNamespace, 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()); } return status; } Status AuthzManagerExternalStateLocal::getRoleDescription(const RoleName& roleName, bool showPrivileges, BSONObj* result) { stdx::lock_guard 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()); mutablebson::Document resultDoc; fassert(17162, resultDoc.root().appendString(AuthorizationManager::ROLE_NAME_FIELD_NAME, roleName.getRole())); fassert( 17163, resultDoc.root().appendString(AuthorizationManager::ROLE_DB_FIELD_NAME, roleName.getDB())); fassert(17267, resultDoc.root().appendBool("isBuiltin", _roleGraph.isBuiltinRole(roleName))); mutablebson::Element rolesElement = resultDoc.makeElementArray("roles"); fassert(17164, resultDoc.root().pushBack(rolesElement)); mutablebson::Element inheritedRolesElement = resultDoc.makeElementArray("inheritedRoles"); fassert(17165, resultDoc.root().pushBack(inheritedRolesElement)); mutablebson::Element privilegesElement = resultDoc.makeElementArray("privileges"); mutablebson::Element inheritedPrivilegesElement = resultDoc.makeElementArray("inheritedPrivileges"); if (showPrivileges) { fassert(17166, resultDoc.root().pushBack(privilegesElement)); } mutablebson::Element warningsElement = resultDoc.makeElementArray("warnings"); addRoleNameObjectsToArrayElement(rolesElement, _roleGraph.getDirectSubordinates(roleName)); if (_roleGraphState == roleGraphStateConsistent) { addRoleNameObjectsToArrayElement(inheritedRolesElement, _roleGraph.getIndirectSubordinates(roleName)); if (showPrivileges) { addPrivilegeObjectsOrWarningsToArrayElement( privilegesElement, warningsElement, _roleGraph.getDirectPrivileges(roleName)); addPrivilegeObjectsOrWarningsToArrayElement( inheritedPrivilegesElement, warningsElement, _roleGraph.getAllPrivileges(roleName)); fassert(17323, resultDoc.root().pushBack(inheritedPrivilegesElement)); } } else if (showPrivileges) { 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(); } Status AuthzManagerExternalStateLocal::getRoleDescriptionsForDB(const std::string dbname, bool showPrivileges, bool showBuiltinRoles, vector* result) { stdx::lock_guard 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 { /** * 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 admin.system.roles document while calculating privileges" " for user-defined roles: " << status << "; document " << doc; } } } // namespace Status AuthzManagerExternalStateLocal::_initializeRoleGraph(OperationContext* txn) { stdx::lock_guard lkInitialzeRoleGraph(_roleGraphMutex); _roleGraphState = roleGraphStateInitial; _roleGraph = RoleGraph(); RoleGraph newRoleGraph; Status status = query(txn, AuthorizationManager::rolesCollectionNamespace, BSONObj(), BSONObj(), stdx::bind(addRoleFromDocumentOrWarn, &newRoleGraph, stdx::placeholders::_1)); if (!status.isOK()) return status; status = newRoleGraph.recomputePrivilegeData(); RoleGraphState newState; if (status == ErrorCodes::GraphContainsCycle) { error() << "Inconsistent role graph during authorization manager initialization. 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; } class AuthzManagerExternalStateLocal::AuthzManagerLogOpHandler : public RecoveryUnit::Change { public: // None of the parameters below (except externalState) need to live longer than // the instantiations of this class AuthzManagerLogOpHandler(AuthzManagerExternalStateLocal* externalState, const char* op, const char* ns, const BSONObj& o, const BSONObj* o2) : _externalState(externalState), _op(op), _ns(ns), _o(o.getOwned()), _isO2Set(o2 ? true : false), _o2(_isO2Set ? o2->getOwned() : BSONObj()) {} virtual void commit() { stdx::lock_guard lk(_externalState->_roleGraphMutex); Status status = _externalState->_roleGraph.handleLogOp( _op.c_str(), NamespaceString(_ns.c_str()), _o, _isO2Set ? &_o2 : NULL); if (status == ErrorCodes::OplogOperationUnsupported) { _externalState->_roleGraph = RoleGraph(); _externalState->_roleGraphState = _externalState->roleGraphStateInitial; BSONObjBuilder oplogEntryBuilder; oplogEntryBuilder << "op" << _op << "ns" << _ns << "o" << _o; if (_isO2Set) oplogEntryBuilder << "o2" << _o2; error() << "Unsupported modification to roles collection in oplog; " "restart this process to reenable user-defined roles; " << status.reason() << "; Oplog entry: " << oplogEntryBuilder.done(); } else if (!status.isOK()) { warning() << "Skipping bad update to roles collection in oplog. " << status << " Oplog entry: " << _op; } status = _externalState->_roleGraph.recomputePrivilegeData(); if (status == ErrorCodes::GraphContainsCycle) { _externalState->_roleGraphState = _externalState->roleGraphStateHasCycle; error() << "Inconsistent role graph during authorization manager initialization. " "Only direct privileges available. " << status.reason() << " after applying oplog entry " << _op; } else { fassert(17183, status); _externalState->_roleGraphState = _externalState->roleGraphStateConsistent; } } virtual void rollback() {} private: AuthzManagerExternalStateLocal* _externalState; const std::string _op; const std::string _ns; const BSONObj _o; const bool _isO2Set; const BSONObj _o2; }; void AuthzManagerExternalStateLocal::logOp( OperationContext* txn, const char* op, const char* ns, const BSONObj& o, BSONObj* o2) { if (ns == AuthorizationManager::rolesCollectionNamespace.ns() || ns == AuthorizationManager::adminCommandNamespace.ns()) { txn->recoveryUnit()->registerChange(new AuthzManagerLogOpHandler(this, op, ns, o, o2)); } } } // namespace mongo