summaryrefslogtreecommitdiff
path: root/src/mongo/db/auth/user_document_parser.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/mongo/db/auth/user_document_parser.cpp')
-rw-r--r--src/mongo/db/auth/user_document_parser.cpp505
1 files changed, 505 insertions, 0 deletions
diff --git a/src/mongo/db/auth/user_document_parser.cpp b/src/mongo/db/auth/user_document_parser.cpp
new file mode 100644
index 00000000000..3cdaa98d087
--- /dev/null
+++ b/src/mongo/db/auth/user_document_parser.cpp
@@ -0,0 +1,505 @@
+/**
+* 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 <http://www.gnu.org/licenses/>.
+*
+* 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_document_parser.h"
+
+#include <string>
+
+#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/mongoutils/str.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_SOURCE_FIELD_NAME = "db";
+ const std::string MONGODB_CR_CREDENTIAL_FIELD_NAME = "MONGODB-CR";
+ 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);
+ }
+
+ inline StringData makeStringDataFromBSONElement(const BSONElement& element) {
+ return StringData(element.valuestr(), element.valuestrsize() - 1);
+ }
+
+ 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 || makeStringDataFromBSONElement(element).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, const 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,
+ const 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, const 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, const 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 (makeStringDataFromBSONElement(userElement).empty())
+ return _badValue("User document needs 'user' field to be non-empty", 0);
+
+ // Validate the "db" element
+ if (userDBElement.type() != String ||
+ makeStringDataFromBSONElement(userDBElement).empty()) {
+ return _badValue("User document needs 'db' field to be a non-empty string", 0);
+ }
+ StringData userDBStr = makeStringDataFromBSONElement(userDBElement);
+ if (!NamespaceString::validDBName(userDBStr) && 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 MongoCRElement = credentialsObj[MONGODB_CR_CREDENTIAL_FIELD_NAME];
+ if (MongoCRElement.eoo()) {
+ return _badValue("User document must provide MONGODB-CR credential to all "
+ "non-external users", 0);
+ }
+ if (MongoCRElement.type() != String ||
+ makeStringDataFromBSONElement(MongoCRElement).empty()) {
+ return _badValue("MONGODB-CR credential must to be a non-empty string, if present",
+ 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 mongoCRCredentialElement =
+ credentialsElement.Obj()[MONGODB_CR_CREDENTIAL_FIELD_NAME];
+ if (!mongoCRCredentialElement.eoo()) {
+ if (mongoCRCredentialElement.type() != String ||
+ makeStringDataFromBSONElement(mongoCRCredentialElement).empty()) {
+ return Status(ErrorCodes::UnsupportedFormat,
+ "MONGODB-CR credentials must be non-empty strings");
+ } else {
+ credentials.isExternal = false;
+ credentials.password = mongoCRCredentialElement.String();
+ }
+ } else {
+ return Status(ErrorCodes::UnsupportedFormat,
+ "User documents must provide credentials for MONGODB-CR"
+ " authentication");
+ }
+ }
+ } 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_SOURCE_FIELD_NAME];
+
+ if (roleNameElement->type() != String ||
+ makeStringDataFromBSONElement(*roleNameElement).empty()) {
+ return Status(ErrorCodes::UnsupportedFormat,
+ "Role names must be non-empty strings");
+ }
+ if (roleSourceElement->type() != String ||
+ makeStringDataFromBSONElement(*roleSourceElement).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<RoleName>* result) {
+ std::vector<RoleName> 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<RoleName> 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<RoleName> 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) ||
+ !ParsedPrivilege::parsedPrivilegeToPrivilege(pp, &privilege, &errmsg)) {
+
+ warning() << "Could not parse privilege element in user document for " <<
+ user->getName() << ": " << errmsg;
+ continue;
+ }
+ privileges.push_back(privilege);
+ }
+ user->setPrivileges(privileges);
+ return Status::OK();
+ }
+
+} // namespace mongo