/**
* 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 .
*/
#include "mongo/platform/basic.h"
#include "mongo/db/auth/authorization_manager.h"
#include
#include
#include
#include
#include "mongo/base/init.h"
#include "mongo/base/status.h"
#include "mongo/db/auth/action_set.h"
#include "mongo/db/auth/privilege.h"
#include "mongo/db/auth/privilege_set.h"
#include "mongo/db/auth/user.h"
#include "mongo/db/auth/user_name.h"
#include "mongo/db/auth/user_name_hash.h"
#include "mongo/db/jsobj.h"
#include "mongo/platform/unordered_map.h"
#include "mongo/util/mongoutils/str.h"
namespace mongo {
const std::string AuthorizationManager::SERVER_RESOURCE_NAME = "$SERVER";
const std::string AuthorizationManager::CLUSTER_RESOURCE_NAME = "$CLUSTER";
const std::string AuthorizationManager::USER_NAME_FIELD_NAME = "user";
const std::string AuthorizationManager::USER_SOURCE_FIELD_NAME = "userSource";
const std::string AuthorizationManager::PASSWORD_FIELD_NAME = "pwd";
bool AuthorizationManager::_doesSupportOldStylePrivileges = true;
bool AuthorizationManager::_authEnabled = false;
namespace {
const std::string ADMIN_DBNAME = "admin";
const std::string LOCAL_DBNAME = "local";
const std::string ROLES_FIELD_NAME = "roles";
const std::string OTHER_DB_ROLES_FIELD_NAME = "otherDBRoles";
const std::string READONLY_FIELD_NAME = "readOnly";
const std::string SYSTEM_ROLE_READ = "read";
const std::string SYSTEM_ROLE_READ_WRITE = "readWrite";
const std::string SYSTEM_ROLE_USER_ADMIN = "userAdmin";
const std::string SYSTEM_ROLE_DB_ADMIN = "dbAdmin";
const std::string SYSTEM_ROLE_CLUSTER_ADMIN = "clusterAdmin";
const std::string SYSTEM_ROLE_READ_ANY_DB = "readAnyDatabase";
const std::string SYSTEM_ROLE_READ_WRITE_ANY_DB = "readWriteAnyDatabase";
const std::string SYSTEM_ROLE_USER_ADMIN_ANY_DB = "userAdminAnyDatabase";
const std::string SYSTEM_ROLE_DB_ADMIN_ANY_DB = "dbAdminAnyDatabase";
// System roles for backwards compatibility with 2.2 and prior
const std::string SYSTEM_ROLE_V0_READ = "oldRead";
const std::string SYSTEM_ROLE_V0_READ_WRITE= "oldReadWrite";
const std::string SYSTEM_ROLE_V0_ADMIN_READ = "oldAdminRead";
const std::string SYSTEM_ROLE_V0_ADMIN_READ_WRITE= "oldAdminReadWrite";
// ActionSets for the various system roles. These ActionSets contain all the actions that
// a user of each system role is granted.
ActionSet readRoleActions;
ActionSet readWriteRoleActions;
ActionSet userAdminRoleActions;
ActionSet dbAdminRoleActions;
ActionSet clusterAdminRoleActions;
// Can only be performed by internal connections. Nothing ever explicitly grants these actions,
// but they're included when calling addAllActions on an ActionSet, which is how internal
// connections are granted their privileges.
ActionSet internalActions;
// Old-style user roles
ActionSet compatibilityReadOnlyActions;
ActionSet compatibilityReadWriteActions;
ActionSet compatibilityReadOnlyAdminActions;
ActionSet compatibilityReadWriteAdminActions;
} // namespace
// This sets up the system role ActionSets. This is what determines what actions each role
// is authorized to perform
MONGO_INITIALIZER(AuthorizationSystemRoles)(InitializerContext* context) {
// Read role
readRoleActions.addAction(ActionType::cloneCollectionLocalSource);
readRoleActions.addAction(ActionType::collStats);
readRoleActions.addAction(ActionType::dbHash);
readRoleActions.addAction(ActionType::dbStats);
readRoleActions.addAction(ActionType::find);
readRoleActions.addAction(ActionType::indexRead);
readRoleActions.addAction(ActionType::killCursors);
// Read-write role
readWriteRoleActions.addAllActionsFromSet(readRoleActions);
readWriteRoleActions.addAction(ActionType::cloneCollectionTarget);
readWriteRoleActions.addAction(ActionType::convertToCapped);
readWriteRoleActions.addAction(ActionType::createCollection); // db admin gets this also
readWriteRoleActions.addAction(ActionType::dropCollection);
readWriteRoleActions.addAction(ActionType::dropIndexes);
readWriteRoleActions.addAction(ActionType::emptycapped);
readWriteRoleActions.addAction(ActionType::ensureIndex);
readWriteRoleActions.addAction(ActionType::insert);
readWriteRoleActions.addAction(ActionType::remove);
readWriteRoleActions.addAction(ActionType::renameCollectionSameDB); // db admin gets this also
readWriteRoleActions.addAction(ActionType::update);
// User admin role
userAdminRoleActions.addAction(ActionType::userAdmin);
// DB admin role
dbAdminRoleActions.addAction(ActionType::clean);
dbAdminRoleActions.addAction(ActionType::cloneCollectionLocalSource);
dbAdminRoleActions.addAction(ActionType::collMod);
dbAdminRoleActions.addAction(ActionType::collStats);
dbAdminRoleActions.addAction(ActionType::compact);
dbAdminRoleActions.addAction(ActionType::convertToCapped);
dbAdminRoleActions.addAction(ActionType::createCollection); // read_write gets this also
dbAdminRoleActions.addAction(ActionType::dbStats);
dbAdminRoleActions.addAction(ActionType::dropCollection);
dbAdminRoleActions.addAction(ActionType::dropIndexes);
dbAdminRoleActions.addAction(ActionType::ensureIndex);
dbAdminRoleActions.addAction(ActionType::indexRead);
dbAdminRoleActions.addAction(ActionType::indexStats);
dbAdminRoleActions.addAction(ActionType::profileEnable);
dbAdminRoleActions.addAction(ActionType::profileRead);
dbAdminRoleActions.addAction(ActionType::reIndex);
dbAdminRoleActions.addAction(ActionType::renameCollectionSameDB); // read_write gets this also
dbAdminRoleActions.addAction(ActionType::storageDetails);
dbAdminRoleActions.addAction(ActionType::validate);
// We separate clusterAdmin read-only and read-write actions for backwards
// compatibility with old-style read-only admin users. This separation is not exposed to
// the user, and could go away once we stop supporting old-style privilege documents.
ActionSet clusterAdminRoleReadActions;
ActionSet clusterAdminRoleWriteActions;
// Cluster admin role
clusterAdminRoleReadActions.addAction(ActionType::connPoolStats);
clusterAdminRoleReadActions.addAction(ActionType::connPoolSync);
clusterAdminRoleReadActions.addAction(ActionType::getCmdLineOpts);
clusterAdminRoleReadActions.addAction(ActionType::getLog);
clusterAdminRoleReadActions.addAction(ActionType::getParameter);
clusterAdminRoleReadActions.addAction(ActionType::getShardMap);
clusterAdminRoleReadActions.addAction(ActionType::getShardVersion);
clusterAdminRoleReadActions.addAction(ActionType::hostInfo);
clusterAdminRoleReadActions.addAction(ActionType::listDatabases);
clusterAdminRoleReadActions.addAction(ActionType::listShards);
clusterAdminRoleReadActions.addAction(ActionType::logRotate);
clusterAdminRoleReadActions.addAction(ActionType::netstat);
clusterAdminRoleReadActions.addAction(ActionType::replSetFreeze);
clusterAdminRoleReadActions.addAction(ActionType::replSetGetStatus);
clusterAdminRoleReadActions.addAction(ActionType::replSetMaintenance);
clusterAdminRoleReadActions.addAction(ActionType::replSetStepDown);
clusterAdminRoleReadActions.addAction(ActionType::replSetSyncFrom);
clusterAdminRoleReadActions.addAction(ActionType::setParameter);
clusterAdminRoleReadActions.addAction(ActionType::setShardVersion); // TODO: should this be internal?
clusterAdminRoleReadActions.addAction(ActionType::serverStatus);
clusterAdminRoleReadActions.addAction(ActionType::splitVector);
// Shutdown is in read actions b/c that's how it was in 2.2
clusterAdminRoleReadActions.addAction(ActionType::shutdown);
clusterAdminRoleReadActions.addAction(ActionType::top);
clusterAdminRoleReadActions.addAction(ActionType::touch);
clusterAdminRoleReadActions.addAction(ActionType::unlock);
clusterAdminRoleReadActions.addAction(ActionType::unsetSharding);
clusterAdminRoleReadActions.addAction(ActionType::writeBacksQueued);
clusterAdminRoleWriteActions.addAction(ActionType::addShard);
clusterAdminRoleWriteActions.addAction(ActionType::closeAllDatabases);
clusterAdminRoleWriteActions.addAction(ActionType::cpuProfiler);
clusterAdminRoleWriteActions.addAction(ActionType::cursorInfo);
clusterAdminRoleWriteActions.addAction(ActionType::diagLogging);
clusterAdminRoleWriteActions.addAction(ActionType::dropDatabase); // TODO: Should there be a CREATE_DATABASE also?
clusterAdminRoleWriteActions.addAction(ActionType::enableSharding);
clusterAdminRoleWriteActions.addAction(ActionType::flushRouterConfig);
clusterAdminRoleWriteActions.addAction(ActionType::fsync);
clusterAdminRoleWriteActions.addAction(ActionType::inprog);
clusterAdminRoleWriteActions.addAction(ActionType::killop);
clusterAdminRoleWriteActions.addAction(ActionType::moveChunk);
clusterAdminRoleWriteActions.addAction(ActionType::movePrimary);
clusterAdminRoleWriteActions.addAction(ActionType::removeShard);
clusterAdminRoleWriteActions.addAction(ActionType::repairDatabase);
clusterAdminRoleWriteActions.addAction(ActionType::replSetInitiate);
clusterAdminRoleWriteActions.addAction(ActionType::replSetReconfig);
clusterAdminRoleWriteActions.addAction(ActionType::resync);
clusterAdminRoleWriteActions.addAction(ActionType::shardCollection);
clusterAdminRoleWriteActions.addAction(ActionType::shardingState);
clusterAdminRoleWriteActions.addAction(ActionType::split);
clusterAdminRoleWriteActions.addAction(ActionType::splitChunk);
clusterAdminRoleActions.addAllActionsFromSet(clusterAdminRoleReadActions);
clusterAdminRoleActions.addAllActionsFromSet(clusterAdminRoleWriteActions);
clusterAdminRoleActions.addAction(ActionType::killCursors);
// Old-style user actions, for backwards compatibility
compatibilityReadOnlyActions.addAllActionsFromSet(readRoleActions);
compatibilityReadWriteActions.addAllActionsFromSet(readWriteRoleActions);
compatibilityReadWriteActions.addAllActionsFromSet(dbAdminRoleActions);
compatibilityReadWriteActions.addAllActionsFromSet(userAdminRoleActions);
compatibilityReadWriteActions.addAction(ActionType::clone);
compatibilityReadWriteActions.addAction(ActionType::copyDBTarget);
compatibilityReadWriteActions.addAction(ActionType::dropDatabase);
compatibilityReadWriteActions.addAction(ActionType::repairDatabase);
compatibilityReadOnlyAdminActions.addAllActionsFromSet(compatibilityReadOnlyActions);
compatibilityReadOnlyAdminActions.addAllActionsFromSet(clusterAdminRoleReadActions);
compatibilityReadWriteAdminActions.addAllActionsFromSet(compatibilityReadWriteActions);
compatibilityReadWriteAdminActions.addAllActionsFromSet(compatibilityReadOnlyAdminActions);
compatibilityReadWriteAdminActions.addAllActionsFromSet(clusterAdminRoleWriteActions);
// Internal commands
internalActions.addAction(ActionType::clone);
internalActions.addAction(ActionType::handshake);
internalActions.addAction(ActionType::mapReduceShardedFinish);
internalActions.addAction(ActionType::replSetElect);
internalActions.addAction(ActionType::replSetFresh);
internalActions.addAction(ActionType::replSetGetRBID);
internalActions.addAction(ActionType::replSetHeartbeat);
internalActions.addAction(ActionType::writebacklisten);
internalActions.addAction(ActionType::_migrateClone);
internalActions.addAction(ActionType::_recvChunkAbort);
internalActions.addAction(ActionType::_recvChunkCommit);
internalActions.addAction(ActionType::_recvChunkStart);
internalActions.addAction(ActionType::_recvChunkStatus);
internalActions.addAction(ActionType::_transferMods);
return Status::OK();
}
static inline Status _oldPrivilegeFormatNotSupported() {
return Status(ErrorCodes::UnsupportedFormat,
"Support for compatibility-form privilege documents disabled; "
"All system.users entries must contain a 'roles' field");
}
static inline Status _badValue(const char* reason, int location) {
return Status(ErrorCodes::BadValue, reason, location);
}
static inline Status _badValue(const std::string& reason, int location) {
return Status(ErrorCodes::BadValue, reason, location);
}
static inline StringData makeStringDataFromBSONElement(const BSONElement& element) {
return StringData(element.valuestr(), element.valuestrsize() - 1);
}
AuthorizationManager::AuthorizationManager(AuthzManagerExternalState* externalState) :
_version(1), _externalState(externalState) {}
AuthorizationManager::~AuthorizationManager() {
for (unordered_map::iterator it = _userCache.begin();
it != _userCache.end(); ++it) {
delete it->second ;
}
}
AuthzManagerExternalState* AuthorizationManager::getExternalState() const {
return _externalState.get();
}
void AuthorizationManager::setSupportOldStylePrivilegeDocuments(bool enabled) {
_doesSupportOldStylePrivileges = enabled;
}
bool AuthorizationManager::getSupportOldStylePrivilegeDocuments() {
return _doesSupportOldStylePrivileges;
}
void AuthorizationManager::setAuthEnabled(bool enabled) {
_authEnabled = enabled;
}
bool AuthorizationManager::isAuthEnabled() {
return _authEnabled;
}
Status AuthorizationManager::getPrivilegeDocument(const std::string& dbname,
const UserName& userName,
BSONObj* result) const {
return _externalState->getPrivilegeDocument(dbname, userName, result);
}
bool AuthorizationManager::hasPrivilegeDocument(const std::string& dbname) const {
return _externalState->hasPrivilegeDocument(dbname);
}
Status AuthorizationManager::insertPrivilegeDocument(const std::string& dbname,
const BSONObj& userObj) const {
return _externalState->insertPrivilegeDocument(dbname, userObj);
}
Status AuthorizationManager::updatePrivilegeDocument(const UserName& user,
const BSONObj& updateObj) const {
return _externalState->updatePrivilegeDocument(user, updateObj);
}
ActionSet AuthorizationManager::getAllUserActions() const {
ActionSet allActions;
allActions.addAllActionsFromSet(readRoleActions);
allActions.addAllActionsFromSet(readWriteRoleActions);
allActions.addAllActionsFromSet(userAdminRoleActions);
allActions.addAllActionsFromSet(dbAdminRoleActions);
allActions.addAllActionsFromSet(clusterAdminRoleActions);
return allActions;
}
ActionSet AuthorizationManager::getActionsForOldStyleUser(const std::string& dbname,
bool readOnly) const {
if (dbname == ADMIN_DBNAME || dbname == LOCAL_DBNAME) {
if (readOnly) {
return compatibilityReadOnlyAdminActions;
} else {
return compatibilityReadWriteAdminActions;
}
} else {
if (readOnly) {
return compatibilityReadOnlyActions;
} else {
return compatibilityReadWriteActions;
}
}
}
Status _checkRolesArray(const BSONElement& rolesElement) {
if (rolesElement.type() != Array) {
return _badValue("Role fields must be an array when present in system.users entries",
0);
}
for (BSONObjIterator iter(rolesElement.embeddedObject()); iter.more(); iter.next()) {
BSONElement element = *iter;
if (element.type() != String || makeStringDataFromBSONElement(element).empty()) {
return _badValue("Roles must be non-empty strings.", 0);
}
}
return Status::OK();
}
Status AuthorizationManager::checkValidPrivilegeDocument(const StringData& dbname,
const BSONObj& doc) {
BSONElement userElement = doc[AuthorizationManager::USER_NAME_FIELD_NAME];
BSONElement userSourceElement = doc[AuthorizationManager::USER_SOURCE_FIELD_NAME];
BSONElement passwordElement = doc[AuthorizationManager::PASSWORD_FIELD_NAME];
BSONElement rolesElement = doc[ROLES_FIELD_NAME];
BSONElement otherDBRolesElement = doc[OTHER_DB_ROLES_FIELD_NAME];
BSONElement readOnlyElement = doc[READONLY_FIELD_NAME];
// Validate the "user" element.
if (userElement.type() != String)
return _badValue("system.users entry needs 'user' field to be a string", 14051);
if (makeStringDataFromBSONElement(userElement).empty())
return _badValue("system.users entry needs 'user' field to be non-empty", 14053);
// Must set exactly one of "userSource" and "pwd" fields.
if (userSourceElement.eoo() == passwordElement.eoo()) {
return _badValue("system.users entry must have either a 'pwd' field or a 'userSource' "
"field, but not both", 0);
}
if (!AuthorizationManager::getSupportOldStylePrivilegeDocuments() && rolesElement.eoo()) {
return _oldPrivilegeFormatNotSupported();
}
// Cannot have both "roles" and "readOnly" elements.
if (!rolesElement.eoo() && !readOnlyElement.eoo()) {
return _badValue("system.users entry must not have both 'roles' and 'readOnly' fields",
0);
}
// Validate the "pwd" element, if present.
if (!passwordElement.eoo()) {
if (passwordElement.type() != String)
return _badValue("system.users entry needs 'pwd' field to be a string", 14052);
if (makeStringDataFromBSONElement(passwordElement).empty())
return _badValue("system.users entry needs 'pwd' field to be non-empty", 14054);
}
// Validate the "userSource" element, if present.
if (!userSourceElement.eoo()) {
if (userSourceElement.type() != String ||
makeStringDataFromBSONElement(userSourceElement).empty()) {
return _badValue("system.users entry needs 'userSource' field to be a non-empty "
"string, if present", 0);
}
if (userSourceElement.str() == dbname) {
return _badValue(mongoutils::str::stream() << "'" << dbname <<
"' is not a valid value for the userSource field in " <<
dbname << ".system.users entries",
0);
}
if (rolesElement.eoo()) {
return _badValue("system.users entry needs 'roles' field if 'userSource' field "
"is present.", 0);
}
}
// Validate the "roles" element.
if (!rolesElement.eoo()) {
Status status = _checkRolesArray(rolesElement);
if (!status.isOK())
return status;
}
if (!otherDBRolesElement.eoo()) {
if (dbname != ADMIN_DBNAME) {
return _badValue("Only admin.system.users entries may contain 'otherDBRoles' "
"fields", 0);
}
if (rolesElement.eoo()) {
return _badValue("system.users entries with 'otherDBRoles' fields must contain "
"'roles' fields", 0);
}
if (otherDBRolesElement.type() != Object) {
return _badValue("'otherDBRoles' field must be an object when present in "
"system.users entries", 0);
}
for (BSONObjIterator iter(otherDBRolesElement.embeddedObject());
iter.more(); iter.next()) {
Status status = _checkRolesArray(*iter);
if (!status.isOK())
return status;
}
}
return Status::OK();
}
Status AuthorizationManager::buildPrivilegeSet(const std::string& dbname,
const UserName& user,
const BSONObj& privilegeDocument,
PrivilegeSet* result) const {
if (!privilegeDocument.hasField(ROLES_FIELD_NAME)) {
// Old-style (v2.2 and prior) privilege document
if (AuthorizationManager::getSupportOldStylePrivilegeDocuments()) {
return _buildPrivilegeSetFromOldStylePrivilegeDocument(dbname,
user,
privilegeDocument,
result);
}
else {
return _oldPrivilegeFormatNotSupported();
}
}
else {
return _buildPrivilegeSetFromExtendedPrivilegeDocument(
dbname, user, privilegeDocument, result);
}
}
Status AuthorizationManager::_buildPrivilegeSetFromOldStylePrivilegeDocument(
const std::string& dbname,
const UserName& user,
const BSONObj& privilegeDocument,
PrivilegeSet* result) const {
if (!(privilegeDocument.hasField(AuthorizationManager::USER_NAME_FIELD_NAME) &&
privilegeDocument.hasField(AuthorizationManager::PASSWORD_FIELD_NAME))) {
return Status(ErrorCodes::UnsupportedFormat,
mongoutils::str::stream() << "Invalid old-style privilege document "
"received when trying to extract privileges: "
<< privilegeDocument,
0);
}
std::string userName = privilegeDocument[AuthorizationManager::USER_NAME_FIELD_NAME].str();
if (userName != user.getUser()) {
return Status(ErrorCodes::BadValue,
mongoutils::str::stream() << "Principal name from privilege document \""
<< userName
<< "\" doesn't match name of provided Principal \""
<< user.getUser()
<< "\"",
0);
}
bool readOnly = privilegeDocument[READONLY_FIELD_NAME].trueValue();
ActionSet actions = getActionsForOldStyleUser(dbname, readOnly);
std::string resourceName = (dbname == ADMIN_DBNAME || dbname == LOCAL_DBNAME) ?
PrivilegeSet::WILDCARD_RESOURCE : dbname;
result->grantPrivilege(Privilege(resourceName, actions), user);
return Status::OK();
}
/**
* Adds to "outPrivileges" the privileges associated with having the named "role" on "dbname".
*
* Returns non-OK status if "role" is not a defined role in "dbname".
*/
void _addPrivilegesForSystemRole(const std::string& dbname,
const std::string& role,
std::vector* outPrivileges) {
const bool isAdminDB = (dbname == ADMIN_DBNAME);
if (role == SYSTEM_ROLE_READ) {
outPrivileges->push_back(Privilege(dbname, readRoleActions));
}
else if (role == SYSTEM_ROLE_READ_WRITE) {
outPrivileges->push_back(Privilege(dbname, readWriteRoleActions));
}
else if (role == SYSTEM_ROLE_USER_ADMIN) {
outPrivileges->push_back(Privilege(dbname, userAdminRoleActions));
}
else if (role == SYSTEM_ROLE_DB_ADMIN) {
outPrivileges->push_back(Privilege(dbname, dbAdminRoleActions));
}
else if (role == SYSTEM_ROLE_V0_READ) {
outPrivileges->push_back(Privilege(dbname, compatibilityReadOnlyActions));
}
else if (role == SYSTEM_ROLE_V0_READ_WRITE) {
outPrivileges->push_back(Privilege(dbname, compatibilityReadWriteActions));
}
else if (isAdminDB && role == SYSTEM_ROLE_READ_ANY_DB) {
outPrivileges->push_back(Privilege(PrivilegeSet::WILDCARD_RESOURCE, readRoleActions));
}
else if (isAdminDB && role == SYSTEM_ROLE_READ_WRITE_ANY_DB) {
outPrivileges->push_back(
Privilege(PrivilegeSet::WILDCARD_RESOURCE, readWriteRoleActions));
}
else if (isAdminDB && role == SYSTEM_ROLE_USER_ADMIN_ANY_DB) {
outPrivileges->push_back(
Privilege(PrivilegeSet::WILDCARD_RESOURCE, userAdminRoleActions));
}
else if (isAdminDB && role == SYSTEM_ROLE_DB_ADMIN_ANY_DB) {
outPrivileges->push_back(
Privilege(PrivilegeSet::WILDCARD_RESOURCE, dbAdminRoleActions));
}
else if (isAdminDB && role == SYSTEM_ROLE_CLUSTER_ADMIN) {
outPrivileges->push_back(
Privilege(PrivilegeSet::WILDCARD_RESOURCE, clusterAdminRoleActions));
}
else if (isAdminDB && role == SYSTEM_ROLE_V0_ADMIN_READ) {
outPrivileges->push_back(
Privilege(PrivilegeSet::WILDCARD_RESOURCE, compatibilityReadOnlyAdminActions));
}
else if (isAdminDB && role == SYSTEM_ROLE_V0_ADMIN_READ_WRITE) {
outPrivileges->push_back(
Privilege(PrivilegeSet::WILDCARD_RESOURCE, compatibilityReadWriteAdminActions));
}
else {
warning() << "No such role, \"" << role << "\", in database " << dbname <<
". No privileges will be acquired from this role" << endl;
}
}
/**
* Given a database name and a BSONElement representing an array of roles, populates
* "outPrivileges" with the privileges associated with the given roles on the named database.
*
* Returns Status::OK() on success.
*/
Status _getPrivilegesFromRoles(const std::string& dbname,
const BSONElement& rolesElement,
std::vector* outPrivileges) {
static const char privilegesTypeMismatchMessage[] =
"Roles must be enumerated in an array of strings.";
if (dbname == PrivilegeSet::WILDCARD_RESOURCE) {
return Status(ErrorCodes::BadValue,
PrivilegeSet::WILDCARD_RESOURCE + " is an invalid database name.");
}
if (rolesElement.type() != Array)
return Status(ErrorCodes::TypeMismatch, privilegesTypeMismatchMessage);
for (BSONObjIterator iter(rolesElement.embeddedObject()); iter.more(); iter.next()) {
BSONElement roleElement = *iter;
if (roleElement.type() != String)
return Status(ErrorCodes::TypeMismatch, privilegesTypeMismatchMessage);
_addPrivilegesForSystemRole(dbname, roleElement.str(), outPrivileges);
}
return Status::OK();
}
Status AuthorizationManager::_buildPrivilegeSetFromExtendedPrivilegeDocument(
const std::string& dbname,
const UserName& user,
const BSONObj& privilegeDocument,
PrivilegeSet* result) const {
if (!privilegeDocument[READONLY_FIELD_NAME].eoo()) {
return Status(ErrorCodes::UnsupportedFormat,
"Privilege documents may not contain both \"readonly\" and "
"\"roles\" fields");
}
std::vector acquiredPrivileges;
// Acquire privileges on "dbname".
Status status = _getPrivilegesFromRoles(
dbname, privilegeDocument[ROLES_FIELD_NAME], &acquiredPrivileges);
if (!status.isOK())
return status;
// If "dbname" is the admin database, handle the otherDBPrivileges field, which
// grants privileges on databases other than "dbname".
BSONElement otherDbPrivileges = privilegeDocument[OTHER_DB_ROLES_FIELD_NAME];
if (dbname == ADMIN_DBNAME) {
switch (otherDbPrivileges.type()) {
case EOO:
break;
case Object: {
for (BSONObjIterator iter(otherDbPrivileges.embeddedObject());
iter.more(); iter.next()) {
BSONElement rolesElement = *iter;
status = _getPrivilegesFromRoles(
rolesElement.fieldName(), rolesElement, &acquiredPrivileges);
if (!status.isOK())
return status;
}
break;
}
default:
return Status(ErrorCodes::TypeMismatch,
"Field \"otherDBRoles\" must be an object, if present.");
}
}
else if (!otherDbPrivileges.eoo()) {
return Status(ErrorCodes::BadValue, "Only the admin database may contain a field "
"called \"otherDBRoles\"");
}
result->grantPrivileges(acquiredPrivileges, user);
return Status::OK();
}
Status AuthorizationManager::acquireUser(const UserName& userName, User** acquiredUser) {
boost::lock_guard lk(_lock);
unordered_map::iterator it = _userCache.find(userName);
if (it != _userCache.end()) {
fassert(16914, it->second);
it->second->incrementRefCount();
*acquiredUser = it->second;
return Status::OK();
}
// Put the new user into an auto_ptr temporarily in case there's an error while
// initializing the user.
auto_ptr userHolder(new User(userName));
User* user = userHolder.get();
BSONObj userObj;
if (_version == 1) {
Status status = _externalState->getPrivilegeDocument(userName.getDB().toString(),
userName,
&userObj);
if (!status.isOK()) {
return status;
}
} else {
return Status(ErrorCodes::UnsupportedFormat,
mongoutils::str::stream() <<
"Unrecognized authorization format version: " << _version);
}
Status status = _initializeUserFromPrivilegeDocument(user, userObj);
if (!status.isOK()) {
return status;
}
user->incrementRefCount();
_userCache.insert(make_pair(userName, userHolder.release()));
*acquiredUser = user;
return Status::OK();
}
void AuthorizationManager::releaseUser(User* user) {
boost::lock_guard lk(_lock);
user->decrementRefCount();
if (user->getRefCount() == 0) {
_userCache.erase(user->getName());
delete user;
}
}
/**
* Parses privDoc and initializes the user's "credentials" field with the credential
* information extracted from the privilege document.
*/
Status _initializeUserCredentialsFromPrivilegeDocument(User* user, const BSONObj& privDoc) {
User::CredentialData credentials;
if (privDoc.hasField("pwd")) {
credentials.password = privDoc["pwd"].String();
credentials.isExternal = false;
}
else if (privDoc.hasField("userSource")) {
std::string userSource = privDoc["userSource"].String();
if (userSource != "$external") {
return Status(ErrorCodes::FailedToParse,
"Cannot extract credentials from user documents without a password "
"and with userSource != \"$external\"");
} else {
credentials.isExternal = true;
}
} else {
return Status(ErrorCodes::FailedToParse,
"Invalid user document: must have one of \"pwd\" and \"userSource\"");
}
user->setCredentials(credentials);
return Status::OK();
}
void _initializeUserRolesFromV0PrivilegeDocument(
User* user, const BSONObj& privDoc, const StringData& dbname) {
bool readOnly = privDoc["readOnly"].trueValue();
if (dbname == "admin") {
if (readOnly) {
user->addRole(RoleName(SYSTEM_ROLE_V0_ADMIN_READ, "admin"));
} else {
user->addRole(RoleName(SYSTEM_ROLE_V0_ADMIN_READ_WRITE, "admin"));
}
} else {
if (readOnly) {
user->addRole(RoleName(SYSTEM_ROLE_V0_READ, dbname));
} else {
user->addRole(RoleName(SYSTEM_ROLE_V0_READ_WRITE, dbname));
}
}
}
Status _initializeUserRolesFromV1PrivilegeDocument(
User* user, const BSONObj& privDoc, const StringData& dbname) {
static const char privilegesTypeMismatchMessage[] =
"Roles in V1 user documents must be enumerated in an array of strings.";
for (BSONObjIterator iter(privDoc["roles"].embeddedObject()); iter.more(); iter.next()) {
BSONElement roleElement = *iter;
if (roleElement.type() != String)
return Status(ErrorCodes::TypeMismatch, privilegesTypeMismatchMessage);
user->addRole(RoleName(roleElement.String(), dbname));
}
return Status::OK();
}
/**
* Parses privDoc and initializes the user's "roles" field with the role list extracted
* from the privilege document.
*/
Status _initializeUserRolesFromPrivilegeDocument(
User* user, const BSONObj& privDoc, const StringData& dbname) {
if (!privDoc.hasField("roles")) {
_initializeUserRolesFromV0PrivilegeDocument(user, privDoc, dbname);
} else {
return _initializeUserRolesFromV1PrivilegeDocument(user, privDoc, dbname);
}
// TODO(spencer): dassert that if you have a V0 or V1 privilege document that the _version
// of the system is 1.
return Status::OK();
}
/**
* Modifies the given User object by inspecting its roles and giving it the relevant
* privileges from those roles.
*/
void _initializeUserPrivilegesFromRoles(User* user) {
std::vector privileges;
RoleNameIterator it = user->getRoles();
while (it.more()) {
const RoleName& roleName = it.next();
_addPrivilegesForSystemRole(roleName.getDB().toString(),
roleName.getRole().toString(),
&privileges);
}
user->addPrivileges(privileges);
}
Status AuthorizationManager::_initializeUserFromPrivilegeDocument(
User* user, const BSONObj& privDoc) const {
Status status = _initializeUserCredentialsFromPrivilegeDocument(user, privDoc);
if (!status.isOK()) {
return status;
}
status = _initializeUserRolesFromPrivilegeDocument(user, privDoc, user->getName().getDB());
if (!status.isOK()) {
return status;
}
_initializeUserPrivilegesFromRoles(user);
return Status::OK();
}
} // namespace mongo