/** * 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/user_document_parser.h" #include #include "mongo/base/init.h" #include "mongo/base/status.h" #include "mongo/db/auth/authorization_manager.h" #include "mongo/db/auth/user.h" #include "mongo/db/jsobj.h" #include "mongo/db/namespace_string.h" #include "mongo/util/log.h" #include "mongo/util/mongoutils/str.h" #include "mongo/util/stringutils.h" namespace mongo { namespace { const std::string ADMIN_DBNAME = "admin"; const std::string ROLES_FIELD_NAME = "roles"; const std::string PRIVILEGES_FIELD_NAME = "inheritedPrivileges"; const std::string INHERITED_ROLES_FIELD_NAME = "inheritedRoles"; const std::string OTHER_DB_ROLES_FIELD_NAME = "otherDBRoles"; const std::string READONLY_FIELD_NAME = "readOnly"; const std::string CREDENTIALS_FIELD_NAME = "credentials"; const std::string ROLE_NAME_FIELD_NAME = "role"; const std::string ROLE_DB_FIELD_NAME = "db"; const std::string MONGODB_CR_CREDENTIAL_FIELD_NAME = "MONGODB-CR"; const std::string SCRAM_CREDENTIAL_FIELD_NAME = "SCRAM-SHA-1"; const std::string MONGODB_EXTERNAL_CREDENTIAL_FIELD_NAME = "external"; inline Status _badValue(const char* reason, int location) { return Status(ErrorCodes::BadValue, reason, location); } inline Status _badValue(const std::string& reason, int location) { return Status(ErrorCodes::BadValue, reason, location); } Status _checkV1RolesArray(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 || element.valueStringData().empty()) { return _badValue("Roles must be non-empty strings.", 0); } } return Status::OK(); } } // namespace std::string V1UserDocumentParser::extractUserNameFromUserDocument(const BSONObj& doc) const { return doc[AuthorizationManager::V1_USER_NAME_FIELD_NAME].str(); } Status V1UserDocumentParser::initializeUserCredentialsFromUserDocument( User* user, const BSONObj& privDoc) const { User::CredentialData credentials; if (privDoc.hasField(AuthorizationManager::PASSWORD_FIELD_NAME)) { credentials.password = privDoc[AuthorizationManager::PASSWORD_FIELD_NAME].String(); credentials.isExternal = false; } else if (privDoc.hasField(AuthorizationManager::V1_USER_SOURCE_FIELD_NAME)) { std::string userSource = privDoc[AuthorizationManager::V1_USER_SOURCE_FIELD_NAME].String(); if (userSource != "$external") { return Status(ErrorCodes::UnsupportedFormat, "Cannot extract credentials from user documents without a password " "and with userSource != \"$external\""); } else { credentials.isExternal = true; } } else { return Status(ErrorCodes::UnsupportedFormat, "Invalid user document: must have one of \"pwd\" and \"userSource\""); } user->setCredentials(credentials); return Status::OK(); } static void _initializeUserRolesFromV0UserDocument(User* user, const BSONObj& privDoc, StringData dbname) { bool readOnly = privDoc["readOnly"].trueValue(); if (dbname == "admin") { if (readOnly) { user->addRole(RoleName(RoleGraph::BUILTIN_ROLE_V0_ADMIN_READ, "admin")); } else { user->addRole(RoleName(RoleGraph::BUILTIN_ROLE_V0_ADMIN_READ_WRITE, "admin")); } } else { if (readOnly) { user->addRole(RoleName(RoleGraph::BUILTIN_ROLE_V0_READ, dbname)); } else { user->addRole(RoleName(RoleGraph::BUILTIN_ROLE_V0_READ_WRITE, dbname)); } } } Status _initializeUserRolesFromV1RolesArray(User* user, const BSONElement& rolesElement, StringData dbname) { static const char privilegesTypeMismatchMessage[] = "Roles in V1 user documents must be enumerated in an array of strings."; 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); user->addRole(RoleName(roleElement.String(), dbname)); } return Status::OK(); } static Status _initializeUserRolesFromV1UserDocument(User* user, const BSONObj& privDoc, StringData dbname) { if (!privDoc[READONLY_FIELD_NAME].eoo()) { return Status(ErrorCodes::UnsupportedFormat, "User documents may not contain both \"readonly\" and " "\"roles\" fields"); } Status status = _initializeUserRolesFromV1RolesArray(user, privDoc[ROLES_FIELD_NAME], dbname); 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 = privDoc[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 = _initializeUserRolesFromV1RolesArray( user, rolesElement, rolesElement.fieldName()); 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::UnsupportedFormat, "Only the admin database may contain a field called \"otherDBRoles\""); } return Status::OK(); } Status V1UserDocumentParser::initializeUserRolesFromUserDocument(User* user, const BSONObj& privDoc, StringData dbname) const { if (!privDoc.hasField("roles")) { _initializeUserRolesFromV0UserDocument(user, privDoc, dbname); } else { return _initializeUserRolesFromV1UserDocument(user, privDoc, dbname); } return Status::OK(); } Status _checkV2RolesArray(const BSONElement& rolesElement) { if (rolesElement.eoo()) { return _badValue("User document needs 'roles' field to be provided", 0); } if (rolesElement.type() != Array) { return _badValue("'roles' field must be an array", 0); } for (BSONObjIterator iter(rolesElement.embeddedObject()); iter.more(); iter.next()) { if ((*iter).type() != Object) { return _badValue("Elements in 'roles' array must objects", 0); } Status status = V2UserDocumentParser::checkValidRoleObject((*iter).Obj()); if (!status.isOK()) return status; } return Status::OK(); } Status V2UserDocumentParser::checkValidUserDocument(const BSONObj& doc) const { BSONElement userElement = doc[AuthorizationManager::USER_NAME_FIELD_NAME]; BSONElement userDBElement = doc[AuthorizationManager::USER_DB_FIELD_NAME]; BSONElement credentialsElement = doc[CREDENTIALS_FIELD_NAME]; BSONElement rolesElement = doc[ROLES_FIELD_NAME]; // Validate the "user" element. if (userElement.type() != String) return _badValue("User document needs 'user' field to be a string", 0); if (userElement.valueStringData().empty()) return _badValue("User document needs 'user' field to be non-empty", 0); // Validate the "db" element if (userDBElement.type() != String || userDBElement.valueStringData().empty()) { return _badValue("User document needs 'db' field to be a non-empty string", 0); } StringData userDBStr = userDBElement.valueStringData(); if (!NamespaceString::validDBName(userDBStr, NamespaceString::DollarInDbNameBehavior::Allow) && userDBStr != "$external") { return _badValue(mongoutils::str::stream() << "'" << userDBStr << "' is not a valid value for the db field.", 0); } // Validate the "credentials" element if (credentialsElement.eoo()) { return _badValue("User document needs 'credentials' object", 0); } if (credentialsElement.type() != Object) { return _badValue("User document needs 'credentials' field to be an object", 0); } BSONObj credentialsObj = credentialsElement.Obj(); if (credentialsObj.isEmpty()) { return _badValue("User document needs 'credentials' field to be a non-empty object", 0); } if (userDBStr == "$external") { BSONElement externalElement = credentialsObj[MONGODB_EXTERNAL_CREDENTIAL_FIELD_NAME]; if (externalElement.eoo() || externalElement.type() != Bool || !externalElement.Bool()) { return _badValue( "User documents for users defined on '$external' must have " "'credentials' field set to {external: true}", 0); } } else { BSONElement scramElement = credentialsObj[SCRAM_CREDENTIAL_FIELD_NAME]; BSONElement mongoCRElement = credentialsObj[MONGODB_CR_CREDENTIAL_FIELD_NAME]; if (!mongoCRElement.eoo()) { if (mongoCRElement.type() != String || mongoCRElement.valueStringData().empty()) { return _badValue( "MONGODB-CR credential must to be a non-empty string" ", if present", 0); } } else if (!scramElement.eoo()) { if (scramElement.type() != Object) { return _badValue("SCRAM credential must be an object, if present", 0); } } else { return _badValue( "User document must provide credentials for all " "non-external users", 0); } } // Validate the "roles" element. Status status = _checkV2RolesArray(rolesElement); if (!status.isOK()) return status; return Status::OK(); } std::string V2UserDocumentParser::extractUserNameFromUserDocument(const BSONObj& doc) const { return doc[AuthorizationManager::USER_NAME_FIELD_NAME].str(); } Status V2UserDocumentParser::initializeUserCredentialsFromUserDocument( User* user, const BSONObj& privDoc) const { User::CredentialData credentials; std::string userDB = privDoc[AuthorizationManager::USER_DB_FIELD_NAME].String(); BSONElement credentialsElement = privDoc[CREDENTIALS_FIELD_NAME]; if (!credentialsElement.eoo()) { if (credentialsElement.type() != Object) { return Status(ErrorCodes::UnsupportedFormat, "'credentials' field in user documents must be an object"); } if (userDB == "$external") { BSONElement externalCredentialElement = credentialsElement.Obj()[MONGODB_EXTERNAL_CREDENTIAL_FIELD_NAME]; if (!externalCredentialElement.eoo()) { if (externalCredentialElement.type() != Bool || !externalCredentialElement.Bool()) { return Status(ErrorCodes::UnsupportedFormat, "'external' field in credentials object must be set to true"); } else { credentials.isExternal = true; } } else { return Status(ErrorCodes::UnsupportedFormat, "User documents defined on '$external' must provide set " "credentials to {external:true}"); } } else { BSONElement scramElement = credentialsElement.Obj()[SCRAM_CREDENTIAL_FIELD_NAME]; BSONElement mongoCRCredentialElement = credentialsElement.Obj()[MONGODB_CR_CREDENTIAL_FIELD_NAME]; if (scramElement.eoo() && mongoCRCredentialElement.eoo()) { return Status(ErrorCodes::UnsupportedFormat, "User documents must provide credentials for SCRAM-SHA-1 " "or MONGODB-CR authentication"); } if (!scramElement.eoo()) { // We are asserting rather then returning errors since these // fields should have been prepopulated by the calling code. credentials.scram.iterationCount = scramElement.Obj()["iterationCount"].numberInt(); uassert(17501, "Invalid or missing SCRAM iteration count", credentials.scram.iterationCount > 0); credentials.scram.salt = scramElement.Obj()["salt"].str(); uassert(17502, "Missing SCRAM salt", !credentials.scram.salt.empty()); credentials.scram.serverKey = scramElement["serverKey"].str(); uassert(17503, "Missing SCRAM serverKey", !credentials.scram.serverKey.empty()); credentials.scram.storedKey = scramElement["storedKey"].str(); uassert(17504, "Missing SCRAM storedKey", !credentials.scram.storedKey.empty()); } if (!mongoCRCredentialElement.eoo()) { if (mongoCRCredentialElement.type() != String || mongoCRCredentialElement.valueStringData().empty()) { return Status(ErrorCodes::UnsupportedFormat, "MONGODB-CR credentials must be non-empty strings"); } else { credentials.password = mongoCRCredentialElement.String(); if (credentials.password.empty()) { return Status(ErrorCodes::UnsupportedFormat, "User documents must provide authentication credentials"); } } } credentials.isExternal = false; } } else { return Status(ErrorCodes::UnsupportedFormat, "Cannot extract credentials from user documents without a " "'credentials' field"); } user->setCredentials(credentials); return Status::OK(); } static Status _extractRoleDocumentElements(const BSONObj& roleObject, BSONElement* roleNameElement, BSONElement* roleSourceElement) { *roleNameElement = roleObject[ROLE_NAME_FIELD_NAME]; *roleSourceElement = roleObject[ROLE_DB_FIELD_NAME]; if (roleNameElement->type() != String || roleNameElement->valueStringData().empty()) { return Status(ErrorCodes::UnsupportedFormat, "Role names must be non-empty strings"); } if (roleSourceElement->type() != String || roleSourceElement->valueStringData().empty()) { return Status(ErrorCodes::UnsupportedFormat, "Role db must be non-empty strings"); } return Status::OK(); } Status V2UserDocumentParser::checkValidRoleObject(const BSONObj& roleObject) { BSONElement roleNameElement; BSONElement roleSourceElement; return _extractRoleDocumentElements(roleObject, &roleNameElement, &roleSourceElement); } Status V2UserDocumentParser::parseRoleName(const BSONObj& roleObject, RoleName* result) { BSONElement roleNameElement; BSONElement roleSourceElement; Status status = _extractRoleDocumentElements(roleObject, &roleNameElement, &roleSourceElement); if (!status.isOK()) return status; *result = RoleName(roleNameElement.str(), roleSourceElement.str()); return status; } Status V2UserDocumentParser::parseRoleVector(const BSONArray& rolesArray, std::vector* result) { std::vector roles; for (BSONObjIterator it(rolesArray); it.more(); it.next()) { if ((*it).type() != Object) { return Status(ErrorCodes::TypeMismatch, "Roles must be objects."); } RoleName role; Status status = parseRoleName((*it).Obj(), &role); if (!status.isOK()) return status; roles.push_back(role); } std::swap(*result, roles); return Status::OK(); } Status V2UserDocumentParser::initializeUserRolesFromUserDocument(const BSONObj& privDoc, User* user) const { BSONElement rolesElement = privDoc[ROLES_FIELD_NAME]; if (rolesElement.type() != Array) { return Status(ErrorCodes::UnsupportedFormat, "User document needs 'roles' field to be an array"); } std::vector roles; for (BSONObjIterator it(rolesElement.Obj()); it.more(); it.next()) { if ((*it).type() != Object) { return Status(ErrorCodes::UnsupportedFormat, "User document needs values in 'roles' array to be a sub-documents"); } BSONObj roleObject = (*it).Obj(); RoleName role; Status status = parseRoleName(roleObject, &role); if (!status.isOK()) { return status; } roles.push_back(role); } user->setRoles(makeRoleNameIteratorForContainer(roles)); return Status::OK(); } Status V2UserDocumentParser::initializeUserIndirectRolesFromUserDocument(const BSONObj& privDoc, User* user) const { BSONElement indirectRolesElement = privDoc[INHERITED_ROLES_FIELD_NAME]; if (indirectRolesElement.type() != Array) { return Status(ErrorCodes::UnsupportedFormat, "User document needs 'inheritedRoles' field to be an array"); } std::vector indirectRoles; for (BSONObjIterator it(indirectRolesElement.Obj()); it.more(); it.next()) { if ((*it).type() != Object) { return Status(ErrorCodes::UnsupportedFormat, "User document needs values in 'inheritedRoles'" " array to be a sub-documents"); } BSONObj indirectRoleObject = (*it).Obj(); RoleName indirectRole; Status status = parseRoleName(indirectRoleObject, &indirectRole); if (!status.isOK()) { return status; } indirectRoles.push_back(indirectRole); } user->setIndirectRoles(makeRoleNameIteratorForContainer(indirectRoles)); return Status::OK(); } Status V2UserDocumentParser::initializeUserPrivilegesFromUserDocument(const BSONObj& doc, User* user) const { BSONElement privilegesElement = doc[PRIVILEGES_FIELD_NAME]; if (privilegesElement.eoo()) return Status::OK(); if (privilegesElement.type() != Array) { return Status(ErrorCodes::UnsupportedFormat, "User document 'inheritedPrivileges' element must be Array if present."); } PrivilegeVector privileges; std::string errmsg; for (BSONObjIterator it(privilegesElement.Obj()); it.more(); it.next()) { if ((*it).type() != Object) { warning() << "Wrong type of element in inheritedPrivileges array for " << user->getName() << ": " << *it; continue; } Privilege privilege; ParsedPrivilege pp; if (!pp.parseBSON((*it).Obj(), &errmsg)) { warning() << "Could not parse privilege element in user document for " << user->getName() << ": " << errmsg; continue; } std::vector unrecognizedActions; Status status = ParsedPrivilege::parsedPrivilegeToPrivilege(pp, &privilege, &unrecognizedActions); if (!status.isOK()) { warning() << "Could not parse privilege element in user document for " << user->getName() << causedBy(status); continue; } if (unrecognizedActions.size()) { std::string unrecognizedActionsString; joinStringDelim(unrecognizedActions, &unrecognizedActionsString, ','); warning() << "Encountered unrecognized actions \" " << unrecognizedActionsString << "\" while parsing user document for " << user->getName(); } privileges.push_back(privilege); } user->setPrivileges(privileges); return Status::OK(); } } // namespace mongo