/**
* 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