text: /**
  *    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.
  */
  
  #define MONGO_LOG_DEFAULT_COMPONENT ::mongo::logger::LogComponent::kAccessControl
  
  #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/log.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_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) && 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<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
  