/**
* Copyright (C) 2013 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.
*/
#include "mongo/platform/basic.h"
#include "mongo/db/auth/user_management_commands_parser.h"
#include
#include
#include
#include "mongo/base/status.h"
#include "mongo/bson/util/bson_extract.h"
#include "mongo/db/auth/action_type.h"
#include "mongo/db/auth/address_restriction.h"
#include "mongo/db/auth/authorization_manager.h"
#include "mongo/db/auth/privilege.h"
#include "mongo/db/auth/privilege_parser.h"
#include "mongo/db/auth/user_document_parser.h"
#include "mongo/db/auth/user_name.h"
#include "mongo/db/commands.h"
#include "mongo/db/jsobj.h"
#include "mongo/stdx/unordered_set.h"
#include "mongo/util/mongoutils/str.h"
#include "mongo/util/stringutils.h"
namespace mongo {
namespace auth {
using std::vector;
Status _checkNoExtraFields(const BSONObj& cmdObj,
StringData cmdName,
const stdx::unordered_set& validFieldNames) {
// Iterate through all fields in command object and make sure there are no unexpected
// ones.
for (BSONObjIterator iter(cmdObj); iter.more(); iter.next()) {
StringData fieldName = (*iter).fieldNameStringData();
if (!CommandHelpers::isGenericArgument(fieldName) &&
!validFieldNames.count(fieldName.toString())) {
return Status(ErrorCodes::BadValue,
mongoutils::str::stream() << "\"" << fieldName << "\" is not "
"a valid argument to "
<< cmdName);
}
}
return Status::OK();
}
// Extracts a UserName or RoleName object from a BSONElement.
template
Status _parseNameFromBSONElement(const BSONElement& element,
StringData dbname,
StringData nameFieldName,
StringData sourceFieldName,
Name* parsedName) {
if (element.type() == String) {
*parsedName = Name(element.String(), dbname);
} else if (element.type() == Object) {
BSONObj obj = element.Obj();
std::string name;
std::string source;
Status status = bsonExtractStringField(obj, nameFieldName, &name);
if (!status.isOK()) {
return status;
}
status = bsonExtractStringField(obj, sourceFieldName, &source);
if (!status.isOK()) {
return status;
}
*parsedName = Name(name, source);
} else {
return Status(ErrorCodes::BadValue,
"User and role names must be either strings or objects");
}
return Status::OK();
}
// Extracts UserName or RoleName objects from a BSONArray of role/user names.
template
Status _parseNamesFromBSONArray(const BSONArray& array,
StringData dbname,
StringData nameFieldName,
StringData sourceFieldName,
std::vector* parsedNames) {
for (BSONObjIterator it(array); it.more(); it.next()) {
BSONElement element = *it;
Name name;
Status status =
_parseNameFromBSONElement(element, dbname, nameFieldName, sourceFieldName, &name);
if (!status.isOK()) {
return status;
}
parsedNames->push_back(name);
}
return Status::OK();
}
Status parseUserNamesFromBSONArray(const BSONArray& usersArray,
StringData dbname,
std::vector* parsedUserNames) {
return _parseNamesFromBSONArray(usersArray,
dbname,
AuthorizationManager::USER_NAME_FIELD_NAME,
AuthorizationManager::USER_DB_FIELD_NAME,
parsedUserNames);
}
Status parseRoleNamesFromBSONArray(const BSONArray& rolesArray,
StringData dbname,
std::vector* parsedRoleNames) {
return _parseNamesFromBSONArray(rolesArray,
dbname,
AuthorizationManager::ROLE_NAME_FIELD_NAME,
AuthorizationManager::ROLE_DB_FIELD_NAME,
parsedRoleNames);
}
Status parseRolePossessionManipulationCommands(const BSONObj& cmdObj,
StringData cmdName,
const std::string& dbname,
std::string* parsedName,
vector* parsedRoleNames) {
stdx::unordered_set validFieldNames;
validFieldNames.insert(cmdName.toString());
validFieldNames.insert("roles");
Status status = _checkNoExtraFields(cmdObj, cmdName, validFieldNames);
if (!status.isOK()) {
return status;
}
status = bsonExtractStringField(cmdObj, cmdName, parsedName);
if (!status.isOK()) {
return status;
}
BSONElement rolesElement;
status = bsonExtractTypedField(cmdObj, "roles", Array, &rolesElement);
if (!status.isOK()) {
return status;
}
status = parseRoleNamesFromBSONArray(BSONArray(rolesElement.Obj()), dbname, parsedRoleNames);
if (!status.isOK()) {
return status;
}
if (!parsedRoleNames->size()) {
return Status(ErrorCodes::BadValue,
mongoutils::str::stream() << cmdName << " command requires a non-empty "
"\"roles\" array");
}
return Status::OK();
}
Status parseCreateOrUpdateUserCommands(const BSONObj& cmdObj,
StringData cmdName,
const std::string& dbname,
CreateOrUpdateUserArgs* parsedArgs) {
stdx::unordered_set validFieldNames;
validFieldNames.insert(cmdName.toString());
validFieldNames.insert("customData");
validFieldNames.insert("digestPassword");
validFieldNames.insert("pwd");
validFieldNames.insert("roles");
validFieldNames.insert("authenticationRestrictions");
validFieldNames.insert("mechanisms");
Status status = _checkNoExtraFields(cmdObj, cmdName, validFieldNames);
if (!status.isOK()) {
return status;
}
BSONObjBuilder userObjBuilder;
// Parse user name
std::string userName;
status = bsonExtractStringField(cmdObj, cmdName, &userName);
if (!status.isOK()) {
return status;
}
if (userName.find('\0') != std::string::npos) {
return Status(ErrorCodes::BadValue, "Username cannot contain NULL characters");
}
parsedArgs->userName = UserName(userName, dbname);
// Parse mechanisms
if (cmdObj.hasField("mechanisms")) {
const auto mechsElem = cmdObj["mechanisms"];
if (mechsElem.type() != Array) {
return {ErrorCodes::UnsupportedFormat, "mechanisms field must be an array"};
}
const auto mechs = mechsElem.Obj();
if (mechs.nFields() == 0) {
return {ErrorCodes::UnsupportedFormat, "mechanisms field must not be empty"};
}
for (auto elem : mechs) {
if (elem.type() != String) {
return {ErrorCodes::BadValue, "mechanisms field must be an array of strings"};
}
parsedArgs->mechanisms.push_back(elem.String());
}
}
// Parse password
if (cmdObj.hasField("pwd")) {
status = bsonExtractStringField(cmdObj, "pwd", &parsedArgs->password);
if (!status.isOK()) {
return status;
}
if (parsedArgs->password.empty()) {
return Status(ErrorCodes::BadValue, "User passwords must not be empty");
}
parsedArgs->hasPassword = true;
// True if the server should digest the password
status = bsonExtractBooleanFieldWithDefault(
cmdObj, "digestPassword", true, &parsedArgs->digestPassword);
if (!status.isOK()) {
return status;
}
}
// Parse custom data
if (cmdObj.hasField("customData")) {
BSONElement element;
status = bsonExtractTypedField(cmdObj, "customData", Object, &element);
if (!status.isOK()) {
return status;
}
parsedArgs->customData = element.Obj();
parsedArgs->hasCustomData = true;
}
// Parse authentication restrictions
if (cmdObj.hasField("authenticationRestrictions")) {
BSONElement element = cmdObj["authenticationRestrictions"];
if (element.type() != Array) {
return Status(ErrorCodes::BadValue, "authenticationRestrictions must be an array");
}
parsedArgs->authenticationRestrictions =
BSONArray(cmdObj["authenticationRestrictions"].Obj());
}
// Parse roles
if (cmdObj.hasField("roles")) {
BSONElement rolesElement;
status = bsonExtractTypedField(cmdObj, "roles", Array, &rolesElement);
if (!status.isOK()) {
return status;
}
status =
parseRoleNamesFromBSONArray(BSONArray(rolesElement.Obj()), dbname, &parsedArgs->roles);
if (!status.isOK()) {
return status;
}
parsedArgs->hasRoles = true;
}
return Status::OK();
}
Status parseAndValidateDropUserCommand(const BSONObj& cmdObj,
const std::string& dbname,
UserName* parsedUserName) {
stdx::unordered_set validFieldNames;
validFieldNames.insert("dropUser");
Status status = _checkNoExtraFields(cmdObj, "dropUser", validFieldNames);
if (!status.isOK()) {
return status;
}
std::string user;
status = bsonExtractStringField(cmdObj, "dropUser", &user);
if (!status.isOK()) {
return status;
}
*parsedUserName = UserName(user, dbname);
return Status::OK();
}
Status parseFromDatabaseCommand(const BSONObj& cmdObj,
const std::string& dbname,
std::string command) {
stdx::unordered_set validFieldNames;
validFieldNames.insert(command);
Status status = _checkNoExtraFields(cmdObj, command, validFieldNames);
if (!status.isOK()) {
return status;
}
return Status::OK();
}
Status parseAndValidateDropAllUsersFromDatabaseCommand(const BSONObj& cmdObj,
const std::string& dbname) {
return parseFromDatabaseCommand(cmdObj, dbname, "dropAllUsersFromDatabase");
}
Status parseUsersInfoCommand(const BSONObj& cmdObj, StringData dbname, UsersInfoArgs* parsedArgs) {
stdx::unordered_set validFieldNames;
validFieldNames.insert("usersInfo");
validFieldNames.insert("showAuthenticationRestrictions");
validFieldNames.insert("showPrivileges");
validFieldNames.insert("showCredentials");
validFieldNames.insert("filter");
Status status = _checkNoExtraFields(cmdObj, "usersInfo", validFieldNames);
if (!status.isOK()) {
return status;
}
if (cmdObj["usersInfo"].numberInt() == 1) {
parsedArgs->allForDB = true;
} else if (cmdObj["usersInfo"].type() == Array) {
status = parseUserNamesFromBSONArray(
BSONArray(cmdObj["usersInfo"].Obj()), dbname, &parsedArgs->userNames);
if (!status.isOK()) {
return status;
}
std::sort(parsedArgs->userNames.begin(), parsedArgs->userNames.end());
} else {
UserName name;
status = _parseNameFromBSONElement(cmdObj["usersInfo"],
dbname,
AuthorizationManager::USER_NAME_FIELD_NAME,
AuthorizationManager::USER_DB_FIELD_NAME,
&name);
if (!status.isOK()) {
return status;
}
parsedArgs->userNames.push_back(name);
}
status = bsonExtractBooleanFieldWithDefault(
cmdObj, "showPrivileges", false, &parsedArgs->showPrivileges);
if (!status.isOK()) {
return status;
}
status = bsonExtractBooleanFieldWithDefault(
cmdObj, "showCredentials", false, &parsedArgs->showCredentials);
if (!status.isOK()) {
return status;
}
const auto showAuthenticationRestrictions = cmdObj["showAuthenticationRestrictions"];
if (showAuthenticationRestrictions.eoo()) {
parsedArgs->authenticationRestrictionsFormat = AuthenticationRestrictionsFormat::kOmit;
} else {
bool show;
status = bsonExtractBooleanField(cmdObj, "showAuthenticationRestrictions", &show);
if (!status.isOK()) {
return status;
}
parsedArgs->authenticationRestrictionsFormat = show
? AuthenticationRestrictionsFormat::kShow
: AuthenticationRestrictionsFormat::kOmit;
}
const auto filterObj = cmdObj["filter"];
if (!filterObj.eoo()) {
if (filterObj.type() != Object) {
return Status(ErrorCodes::TypeMismatch, "filter must be an Object");
}
parsedArgs->filter = filterObj.Obj();
}
return Status::OK();
}
Status parseRolesInfoCommand(const BSONObj& cmdObj, StringData dbname, RolesInfoArgs* parsedArgs) {
stdx::unordered_set validFieldNames;
validFieldNames.insert("rolesInfo");
validFieldNames.insert("showPrivileges");
validFieldNames.insert("showAuthenticationRestrictions");
validFieldNames.insert("showBuiltinRoles");
Status status = _checkNoExtraFields(cmdObj, "rolesInfo", validFieldNames);
if (!status.isOK()) {
return status;
}
if (cmdObj["rolesInfo"].numberInt() == 1) {
parsedArgs->allForDB = true;
} else if (cmdObj["rolesInfo"].type() == Array) {
status = parseRoleNamesFromBSONArray(
BSONArray(cmdObj["rolesInfo"].Obj()), dbname, &parsedArgs->roleNames);
if (!status.isOK()) {
return status;
}
} else {
RoleName name;
status = _parseNameFromBSONElement(cmdObj["rolesInfo"],
dbname,
AuthorizationManager::ROLE_NAME_FIELD_NAME,
AuthorizationManager::ROLE_DB_FIELD_NAME,
&name);
if (!status.isOK()) {
return status;
}
parsedArgs->roleNames.push_back(name);
}
BSONElement showPrivileges = cmdObj["showPrivileges"];
if (showPrivileges.eoo()) {
parsedArgs->privilegeFormat = PrivilegeFormat::kOmit;
} else if (showPrivileges.isNumber() || showPrivileges.isBoolean()) {
parsedArgs->privilegeFormat =
showPrivileges.trueValue() ? PrivilegeFormat::kShowSeparate : PrivilegeFormat::kOmit;
} else if (showPrivileges.type() == BSONType::String &&
showPrivileges.String() == "asUserFragment") {
parsedArgs->privilegeFormat = PrivilegeFormat::kShowAsUserFragment;
} else {
return Status(ErrorCodes::FailedToParse,
str::stream() << "Failed to parse 'showPrivileges'. 'showPrivileges' should "
"either be a boolean or the string 'asUserFragment', given: "
<< showPrivileges.toString());
}
const auto showAuthenticationRestrictions = cmdObj["showAuthenticationRestrictions"];
if (showAuthenticationRestrictions.eoo()) {
parsedArgs->authenticationRestrictionsFormat = AuthenticationRestrictionsFormat::kOmit;
} else if (parsedArgs->privilegeFormat == PrivilegeFormat::kShowAsUserFragment) {
return Status(
ErrorCodes::UnsupportedFormat,
"showAuthenticationRestrictions may not be used with showPrivileges='asUserFragment'");
} else {
bool show;
status = bsonExtractBooleanField(cmdObj, "showAuthenticationRestrictions", &show);
if (!status.isOK()) {
return status;
}
parsedArgs->authenticationRestrictionsFormat = show
? AuthenticationRestrictionsFormat::kShow
: AuthenticationRestrictionsFormat::kOmit;
}
status = bsonExtractBooleanFieldWithDefault(
cmdObj, "showBuiltinRoles", false, &parsedArgs->showBuiltinRoles);
if (!status.isOK()) {
return status;
}
return Status::OK();
}
/*
* Validates that the given privilege BSONArray is valid.
* If parsedPrivileges is not NULL, adds to it the privileges parsed out of the input BSONArray.
*/
Status parseAndValidatePrivilegeArray(const BSONArray& privileges,
PrivilegeVector* parsedPrivileges) {
for (BSONObjIterator it(privileges); it.more(); it.next()) {
BSONElement element = *it;
if (element.type() != Object) {
return Status(ErrorCodes::FailedToParse,
"Elements in privilege arrays must be objects");
}
ParsedPrivilege parsedPrivilege;
std::string errmsg;
if (!parsedPrivilege.parseBSON(element.Obj(), &errmsg)) {
return Status(ErrorCodes::FailedToParse, errmsg);
}
if (!parsedPrivilege.isValid(&errmsg)) {
return Status(ErrorCodes::FailedToParse, errmsg);
}
Privilege privilege;
std::vector unrecognizedActions;
Status status = ParsedPrivilege::parsedPrivilegeToPrivilege(
parsedPrivilege, &privilege, &unrecognizedActions);
if (!status.isOK()) {
return status;
}
if (unrecognizedActions.size()) {
std::string unrecognizedActionsString;
joinStringDelim(unrecognizedActions, &unrecognizedActionsString, ',');
return Status(ErrorCodes::FailedToParse,
str::stream() << "Unrecognized action privilege strings: "
<< unrecognizedActionsString);
}
parsedPrivileges->push_back(privilege);
}
return Status::OK();
}
Status parseCreateOrUpdateRoleCommands(const BSONObj& cmdObj,
StringData cmdName,
const std::string& dbname,
CreateOrUpdateRoleArgs* parsedArgs) {
stdx::unordered_set validFieldNames;
validFieldNames.insert(cmdName.toString());
validFieldNames.insert("privileges");
validFieldNames.insert("roles");
validFieldNames.insert("authenticationRestrictions");
Status status = _checkNoExtraFields(cmdObj, cmdName, validFieldNames);
if (!status.isOK()) {
return status;
}
// Parse role name
std::string roleName;
status = bsonExtractStringField(cmdObj, cmdName, &roleName);
if (!status.isOK()) {
return status;
}
if (roleName.find('\0') != std::string::npos) {
return Status(ErrorCodes::BadValue, "Role name cannot contain NULL characters");
}
parsedArgs->roleName = RoleName(roleName, dbname);
// Parse privileges
if (cmdObj.hasField("privileges")) {
BSONElement privilegesElement;
status = bsonExtractTypedField(cmdObj, "privileges", Array, &privilegesElement);
if (!status.isOK()) {
return status;
}
status = parseAndValidatePrivilegeArray(BSONArray(privilegesElement.Obj()),
&parsedArgs->privileges);
if (!status.isOK()) {
return status;
}
parsedArgs->hasPrivileges = true;
}
// Parse roles
if (cmdObj.hasField("roles")) {
BSONElement rolesElement;
status = bsonExtractTypedField(cmdObj, "roles", Array, &rolesElement);
if (!status.isOK()) {
return status;
}
status =
parseRoleNamesFromBSONArray(BSONArray(rolesElement.Obj()), dbname, &parsedArgs->roles);
if (!status.isOK()) {
return status;
}
parsedArgs->hasRoles = true;
}
// Parse restrictions
if (cmdObj.hasField("authenticationRestrictions")) {
BSONElement restrictionsElement;
status = bsonExtractTypedField(
cmdObj, "authenticationRestrictions", Array, &restrictionsElement);
if (!status.isOK()) {
return status;
}
auto restrictions = getRawAuthenticationRestrictions(BSONArray(restrictionsElement.Obj()));
if (!restrictions.isOK()) {
return restrictions.getStatus();
}
parsedArgs->authenticationRestrictions = restrictions.getValue();
}
return Status::OK();
}
Status parseAndValidateRolePrivilegeManipulationCommands(const BSONObj& cmdObj,
StringData cmdName,
const std::string& dbname,
RoleName* parsedRoleName,
PrivilegeVector* parsedPrivileges) {
stdx::unordered_set validFieldNames;
validFieldNames.insert(cmdName.toString());
validFieldNames.insert("privileges");
Status status = _checkNoExtraFields(cmdObj, cmdName, validFieldNames);
if (!status.isOK()) {
return status;
}
BSONObjBuilder roleObjBuilder;
// Parse role name
std::string roleName;
status = bsonExtractStringField(cmdObj, cmdName, &roleName);
if (!status.isOK()) {
return status;
}
*parsedRoleName = RoleName(roleName, dbname);
// Parse privileges
BSONElement privilegesElement;
status = bsonExtractTypedField(cmdObj, "privileges", Array, &privilegesElement);
if (!status.isOK()) {
return status;
}
status = parseAndValidatePrivilegeArray(BSONArray(privilegesElement.Obj()), parsedPrivileges);
if (!status.isOK()) {
return status;
}
if (!parsedPrivileges->size()) {
return Status(ErrorCodes::BadValue,
mongoutils::str::stream() << cmdName << " command requires a non-empty "
"\"privileges\" array");
}
return Status::OK();
}
Status parseDropRoleCommand(const BSONObj& cmdObj,
const std::string& dbname,
RoleName* parsedRoleName) {
stdx::unordered_set validFieldNames;
validFieldNames.insert("dropRole");
Status status = _checkNoExtraFields(cmdObj, "dropRole", validFieldNames);
if (!status.isOK()) {
return status;
}
std::string user;
status = bsonExtractStringField(cmdObj, "dropRole", &user);
if (!status.isOK()) {
return status;
}
*parsedRoleName = RoleName(user, dbname);
return Status::OK();
}
Status parseDropAllRolesFromDatabaseCommand(const BSONObj& cmdObj, const std::string& dbname) {
return parseFromDatabaseCommand(cmdObj, dbname, "dropAllRolesFromDatabase");
}
Status parseMergeAuthzCollectionsCommand(const BSONObj& cmdObj,
MergeAuthzCollectionsArgs* parsedArgs) {
stdx::unordered_set validFieldNames;
validFieldNames.insert("_mergeAuthzCollections");
validFieldNames.insert("tempUsersCollection");
validFieldNames.insert("tempRolesCollection");
validFieldNames.insert("db");
validFieldNames.insert("drop");
Status status = _checkNoExtraFields(cmdObj, "_mergeAuthzCollections", validFieldNames);
if (!status.isOK()) {
return status;
}
status = bsonExtractStringFieldWithDefault(
cmdObj, "tempUsersCollection", "", &parsedArgs->usersCollName);
if (!status.isOK()) {
return status;
}
status = bsonExtractStringFieldWithDefault(
cmdObj, "tempRolesCollection", "", &parsedArgs->rolesCollName);
if (!status.isOK()) {
return status;
}
status = bsonExtractStringField(cmdObj, "db", &parsedArgs->db);
if (!status.isOK()) {
if (status == ErrorCodes::NoSuchKey) {
return Status(ErrorCodes::OutdatedClient,
"Missing \"db\" field for _mergeAuthzCollections command. This is "
"most likely due to running an outdated (pre-2.6.4) version of "
"mongorestore.");
}
return status;
}
status = bsonExtractBooleanFieldWithDefault(cmdObj, "drop", false, &parsedArgs->drop);
if (!status.isOK()) {
return status;
}
return Status::OK();
}
} // namespace auth
} // namespace mongo