/** * 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/db/auth/user_management_commands_parser.h" #include #include #include "mongo/base/status.h" #include "mongo/bson/util/bson_extract.h" #include "mongo/client/auth_helpers.h" #include "mongo/db/auth/action_type.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/jsobj.h" #include "mongo/platform/unordered_set.h" #include "mongo/util/mongoutils/str.h" #include "mongo/util/password_digest.h" namespace mongo { namespace auth { using std::vector; /** * Writes into *writeConcern a BSONObj describing the parameters to getLastError to use for * the write confirmation. */ Status _extractWriteConcern(const BSONObj& cmdObj, BSONObj* writeConcern) { BSONElement writeConcernElement; Status status = bsonExtractTypedField(cmdObj, "writeConcern", Object, &writeConcernElement); if (!status.isOK()) { if (status.code() == ErrorCodes::NoSuchKey) { *writeConcern = BSONObj(); return Status::OK(); } return status; } *writeConcern = writeConcernElement.Obj().getOwned();; return Status::OK(); } Status _checkNoExtraFields(const BSONObj& cmdObj, const StringData& cmdName, const 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 (!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, const StringData& dbname, const StringData& nameFieldName, const 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, const StringData& dbname, const StringData& nameFieldName, const 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, const 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, const 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, const StringData& cmdName, const std::string& dbname, std::string* parsedName, vector* parsedRoleNames, BSONObj* parsedWriteConcern) { unordered_set validFieldNames; validFieldNames.insert(cmdName.toString()); validFieldNames.insert("roles"); validFieldNames.insert("writeConcern"); Status status = _checkNoExtraFields(cmdObj, cmdName, validFieldNames); if (!status.isOK()) { return status; } status = _extractWriteConcern(cmdObj, parsedWriteConcern); 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, const StringData& cmdName, const std::string& dbname, CreateOrUpdateUserArgs* parsedArgs) { unordered_set validFieldNames; validFieldNames.insert(cmdName.toString()); validFieldNames.insert("customData"); validFieldNames.insert("digestPassword"); validFieldNames.insert("pwd"); validFieldNames.insert("roles"); validFieldNames.insert("writeConcern"); Status status = _checkNoExtraFields(cmdObj, cmdName, validFieldNames); if (!status.isOK()) { return status; } status = _extractWriteConcern(cmdObj, &parsedArgs->writeConcern); if (!status.isOK()) { return status; } BSONObjBuilder userObjBuilder; // Parse user name std::string userName; status = bsonExtractStringField(cmdObj, cmdName, &userName); if (!status.isOK()) { return status; } parsedArgs->userName = UserName(userName, dbname); // Parse password if (cmdObj.hasField("pwd")) { std::string password; status = bsonExtractStringField(cmdObj, "pwd", &password); if (!status.isOK()) { return status; } if (password.empty()) { return Status(ErrorCodes::BadValue, "User passwords must not be empty"); } bool digestPassword; // True if the server should digest the password status = bsonExtractBooleanFieldWithDefault(cmdObj, "digestPassword", true, &digestPassword); if (!status.isOK()) { return status; } if (digestPassword) { parsedArgs->hashedPassword = mongo::createPasswordDigest( userName, password); } else { parsedArgs->hashedPassword = password; } parsedArgs->hasHashedPassword = true; } // 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 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, BSONObj* parsedWriteConcern) { unordered_set validFieldNames; validFieldNames.insert("dropUser"); validFieldNames.insert("writeConcern"); Status status = _checkNoExtraFields(cmdObj, "dropUser", validFieldNames); if (!status.isOK()) { return status; } std::string user; status = bsonExtractStringField(cmdObj, "dropUser", &user); if (!status.isOK()) { return status; } status = _extractWriteConcern(cmdObj, parsedWriteConcern); if (!status.isOK()) { return status; } *parsedUserName = UserName(user, dbname); return Status::OK(); } Status parseAndValidateDropAllUsersFromDatabaseCommand(const BSONObj& cmdObj, const std::string& dbname, BSONObj* parsedWriteConcern) { unordered_set validFieldNames; validFieldNames.insert("dropAllUsersFromDatabase"); validFieldNames.insert("writeConcern"); Status status = _checkNoExtraFields(cmdObj, "dropAllUsersFromDatabase", validFieldNames); if (!status.isOK()) { return status; } status = _extractWriteConcern(cmdObj, parsedWriteConcern); if (!status.isOK()) { return status; } return Status::OK(); } Status parseUsersInfoCommand(const BSONObj& cmdObj, const StringData& dbname, UsersInfoArgs* parsedArgs) { unordered_set validFieldNames; validFieldNames.insert("usersInfo"); validFieldNames.insert("showPrivileges"); validFieldNames.insert("showCredentials"); 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; } } 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; } return Status::OK(); } Status parseRolesInfoCommand(const BSONObj& cmdObj, const StringData& dbname, RolesInfoArgs* parsedArgs) { unordered_set validFieldNames; validFieldNames.insert("rolesInfo"); validFieldNames.insert("showPrivileges"); 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); } status = bsonExtractBooleanFieldWithDefault(cmdObj, "showPrivileges", false, &parsedArgs->showPrivileges); if (!status.isOK()) { return status; } 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; if (!ParsedPrivilege::parsedPrivilegeToPrivilege(parsedPrivilege, &privilege, &errmsg)) { return Status(ErrorCodes::FailedToParse, errmsg); } parsedPrivileges->push_back(privilege); } return Status::OK(); } Status parseCreateOrUpdateRoleCommands(const BSONObj& cmdObj, const StringData& cmdName, const std::string& dbname, CreateOrUpdateRoleArgs* parsedArgs) { unordered_set validFieldNames; validFieldNames.insert(cmdName.toString()); validFieldNames.insert("privileges"); validFieldNames.insert("roles"); validFieldNames.insert("writeConcern"); Status status = _checkNoExtraFields(cmdObj, cmdName, validFieldNames); if (!status.isOK()) { return status; } status = _extractWriteConcern(cmdObj, &parsedArgs->writeConcern); if (!status.isOK()) { return status; } std::string roleName; status = bsonExtractStringField(cmdObj, cmdName, &roleName); if (!status.isOK()) { return status; } 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; } return Status::OK(); } Status parseAndValidateRolePrivilegeManipulationCommands(const BSONObj& cmdObj, const StringData& cmdName, const std::string& dbname, RoleName* parsedRoleName, PrivilegeVector* parsedPrivileges, BSONObj* parsedWriteConcern) { unordered_set validFieldNames; validFieldNames.insert(cmdName.toString()); validFieldNames.insert("privileges"); validFieldNames.insert("writeConcern"); Status status = _checkNoExtraFields(cmdObj, cmdName, validFieldNames); if (!status.isOK()) { return status; } status = _extractWriteConcern(cmdObj, parsedWriteConcern); 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, BSONObj* parsedWriteConcern) { unordered_set validFieldNames; validFieldNames.insert("dropRole"); validFieldNames.insert("writeConcern"); Status status = _checkNoExtraFields(cmdObj, "dropRole", validFieldNames); if (!status.isOK()) { return status; } std::string user; status = bsonExtractStringField(cmdObj, "dropRole", &user); if (!status.isOK()) { return status; } status = _extractWriteConcern(cmdObj, parsedWriteConcern); if (!status.isOK()) { return status; } *parsedRoleName = RoleName(user, dbname); return Status::OK(); } Status parseDropAllRolesFromDatabaseCommand(const BSONObj& cmdObj, const std::string& dbname, BSONObj* parsedWriteConcern) { unordered_set validFieldNames; validFieldNames.insert("dropAllRolesFromDatabase"); validFieldNames.insert("writeConcern"); Status status = _checkNoExtraFields(cmdObj, "dropAllRolesFromDatabase", validFieldNames); if (!status.isOK()) { return status; } status = _extractWriteConcern(cmdObj, parsedWriteConcern); if (!status.isOK()) { return status; } return Status::OK(); } Status parseMergeAuthzCollectionsCommand(const BSONObj& cmdObj, MergeAuthzCollectionsArgs* parsedArgs) { unordered_set validFieldNames; validFieldNames.insert("_mergeAuthzCollections"); validFieldNames.insert("tempUsersCollection"); validFieldNames.insert("tempRolesCollection"); validFieldNames.insert("db"); validFieldNames.insert("drop"); validFieldNames.insert("writeConcern"); Status status = _checkNoExtraFields(cmdObj, "_mergeAuthzCollections", validFieldNames); if (!status.isOK()) { return status; } status = _extractWriteConcern(cmdObj, &parsedArgs->writeConcern); 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(); } Status parseAuthSchemaUpgradeStepCommand(const BSONObj& cmdObj, const std::string& dbname, int* maxSteps, bool* shouldUpgradeShards, BSONObj* parsedWriteConcern) { static const int minUpgradeSteps = 1; static const int maxUpgradeSteps = 2; unordered_set validFieldNames; validFieldNames.insert("authSchemaUpgrade"); validFieldNames.insert("maxSteps"); validFieldNames.insert("upgradeShards"); validFieldNames.insert("writeConcern"); Status status = _checkNoExtraFields(cmdObj, "authSchemaUpgrade", validFieldNames); if (!status.isOK()) { return status; } status = bsonExtractBooleanFieldWithDefault( cmdObj, "upgradeShards", true, shouldUpgradeShards); if (!status.isOK()) { return status; } long long steps; status = bsonExtractIntegerFieldWithDefault(cmdObj, "maxSteps", maxUpgradeSteps, &steps); if (!status.isOK()) return status; if (steps < minUpgradeSteps || steps > maxUpgradeSteps) { return Status(ErrorCodes::BadValue, mongoutils::str::stream() << "Legal values for \"maxSteps\" are at least " << minUpgradeSteps << " and no more than " << maxUpgradeSteps << "; found " << steps); } *maxSteps = static_cast(steps); status = _extractWriteConcern(cmdObj, parsedWriteConcern); if (!status.isOK()) { return status; } return Status::OK(); } } // namespace auth } // namespace mongo