Status V2PrivilegeDocumentParser::checkValidRoleObject(
            const BSONObj& roleObject) const {
        BSONElement roleNameElement = roleObject[ROLE_NAME_FIELD_NAME];
        BSONElement roleSourceElement = roleObject[ROLE_SOURCE_FIELD_NAME];
        BSONElement canDelegateElement = roleObject[ROLE_CAN_DELEGATE_FIELD_NAME];
        BSONElement hasRoleElement = roleObject[ROLE_HAS_ROLE_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 source must be non-empty strings");
        }
        if (canDelegateElement.type() != Bool) {
            return Status(ErrorCodes::UnsupportedFormat,
                          "Entries in 'roles' array need a 'canDelegate' boolean field");
        }
        if (hasRoleElement.type() != Bool) {
            return Status(ErrorCodes::UnsupportedFormat,
                          "Entries in 'roles' array need a 'hasRole' boolean field");
        }

        if (!hasRoleElement.Bool() && !canDelegateElement.Bool()) {
            return Status(ErrorCodes::UnsupportedFormat,
                          "At least one of 'canDelegate' and 'hasRole' must be true for "
                          "every role in the 'roles' array");
        }

        return Status::OK();
    }
Exemple #2
0
 bool BalancerPolicy::hasOpsQueued( BSONObj limits ){
     BSONElement opsQueued = limits[ LimitsFields::hasOpsQueued.name() ];
     if ( opsQueued.eoo() || ! opsQueued.Bool() ){
         return false;
     }
     return true;
 }
Exemple #3
0
        void reload() {

            list<BSONObj> all;
            {
                ScopedDbConnection conn( configServer.getPrimary() );
                auto_ptr<DBClientCursor> c = conn->query( ShardNS::shard , Query() );
                massert( 13632 , "couldn't get updated shard list from config server" , c.get() );
                while ( c->more() ) {
                    all.push_back( c->next().getOwned() );
                }
                conn.done();
            }

            scoped_lock lk( _mutex );

            // We use the _lookup table for all shards and for the primary config DB. The config DB info,
            // however, does not come from the ShardNS::shard. So when cleaning the _lookup table we leave
            // the config state intact. The rationale is that this way we could drop shards that
            // were removed without reinitializing the config DB information.

            map<string,Shard>::iterator i = _lookup.find( "config" );
            if ( i != _lookup.end() ) {
                Shard config = i->second;
                _lookup.clear();
                _lookup[ "config" ] = config;
            }
            else {
                _lookup.clear();
            }

            for ( list<BSONObj>::iterator i=all.begin(); i!=all.end(); ++i ) {
                BSONObj o = *i;
                string name = o["_id"].String();
                string host = o["host"].String();

                long long maxSize = 0;
                BSONElement maxSizeElem = o[ ShardFields::maxSize.name() ];
                if ( ! maxSizeElem.eoo() ) {
                    maxSize = maxSizeElem.numberLong();
                }

                bool isDraining = false;
                BSONElement isDrainingElem = o[ ShardFields::draining.name() ];
                if ( ! isDrainingElem.eoo() ) {
                    isDraining = isDrainingElem.Bool();
                }

                Shard s( name , host , maxSize , isDraining );
                _lookup[name] = s;
                _lookup[host] = s;

                // add rs name to lookup (if it exists)
                size_t pos;
                if ((pos = host.find('/', 0)) != string::npos) {
                    _lookup[host.substr(0, pos)] = s;
                }
            }

        }
Exemple #4
0
   // PD_TRACE_DECLARE_FUNCTION ( SDB__RTNALTERCL_DOIT, "_rtnAlterCollection::doit" )
   INT32 _rtnAlterCollection::doit( _pmdEDUCB *cb, _SDB_DMSCB *dmsCB,
                                    _SDB_RTNCB *rtnCB, _dpsLogWrapper *dpsCB,
                                    INT16 w, INT64 *pContextID )
   {
      INT32 rc = SDB_OK ;
      PD_TRACE_ENTRY( SDB__RTNALTERCL_DOIT ) ;
      BSONObj idxDef ;
      BSONObj options ;
      BSONElement ensureIndex  ;
      BSONElement shardingKey ;
      options = _alterObj.getField( FIELD_NAME_OPTIONS ).embeddedObject() ;
      shardingKey = options.getField( FIELD_NAME_SHARDINGKEY ) ;

      if ( Object != shardingKey.type() )
      {
         PD_LOG( PDDEBUG, "no sharding key in the alter object, do noting." ) ;
         goto done ;
      }

      ensureIndex = options.getField( FIELD_NAME_ENSURE_SHDINDEX ) ;
      if ( Bool == ensureIndex.type() &&
           !ensureIndex.Bool() )
      {
         PD_LOG( PDDEBUG, "ensureShardingIndex is false, do nothing." ) ;
         goto done ;
      }

      idxDef = BSON( IXM_FIELD_NAME_KEY << shardingKey.embeddedObject()
                     << IXM_FIELD_NAME_NAME << IXM_SHARD_KEY_NAME
                     << "v"<<0 ) ;

      rc = rtnCreateIndexCommand( collectionFullName(),
                                  idxDef,
                                  cb, dmsCB, dpsCB, TRUE ) ;
      if ( SDB_IXM_REDEF == rc )
      {
         rc = SDB_OK ;
         goto done ;
      }
      else if ( SDB_OK != rc )
      {
         PD_LOG( PDERROR, "failed to create sharding key index:%d", rc ) ;
         goto error ;
      }
      else
      {
        /*
         catAgent *catAgent = sdbGetShardCB()->getCataAgent() ;
         catAgent->lock_w() ;
         catAgent->clear( collectionFullName() ) ;
         catAgent->release_w() ;
         sdbGetClsCB()->invalidateCata( collectionFullName() ) ;*/
      }
   done:
      PD_TRACE_EXITRC( SDB__RTNALTERCL_DOIT, rc ) ;
      return rc ;
   error:
      goto done ;
   }
Exemple #5
0
    bool BalancerPolicy::isDraining( BSONObj limits ){
        BSONElement draining = limits[ ShardFields::draining.name() ];
        if ( draining.eoo() || ! draining.Bool() ){
            return false;
        }

        return true;
    }
    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);
            }
            BSONObj roleObj = (*iter).Obj();
            BSONElement nameElement = roleObj[ROLE_NAME_FIELD_NAME];
            BSONElement sourceElement = roleObj[ROLE_SOURCE_FIELD_NAME];
            BSONElement canDelegateElement = roleObj[ROLE_CAN_DELEGATE_FIELD_NAME];
            BSONElement hasRoleElement = roleObj[ROLE_HAS_ROLE_FIELD_NAME];

            if (nameElement.type() != String ||
                    makeStringDataFromBSONElement(nameElement).empty()) {
                return _badValue("Entries in 'roles' array need 'name' field to be a non-empty "
                                         "string",
                                 0);
            }
            if (sourceElement.type() != String ||
                    makeStringDataFromBSONElement(sourceElement).empty()) {
                return _badValue("Entries in 'roles' array need 'source' field to be a non-empty "
                                         "string",
                                 0);
            }
            if (canDelegateElement.type() != Bool) {
                return _badValue("Entries in 'roles' array need a 'canDelegate' boolean field",
                                 0);
            }
            if (hasRoleElement.type() != Bool) {
                return _badValue("Entries in 'roles' array need a 'canDelegate' boolean field",
                                 0);
            }
            if (!canDelegateElement.Bool() && !hasRoleElement.Bool()) {
                return _badValue("At least one of 'canDelegate' and 'hasRole' must be true for "
                                         "every role in the 'roles' array",
                                 0);
            }
        }
        return Status::OK();
    }
/* ****************************************************************************
*
* addCompoundNode -
*
*/
static void addCompoundNode(orion::CompoundValueNode* cvP, const BSONElement& e)
{
  if ((e.type() != String) && (e.type() != Bool) && (e.type() != NumberDouble) && (e.type() != jstNULL) && (e.type() != Object) && (e.type() != Array))
  {
    LM_E(("Runtime Error (unknown BSON type: %d)", e.type()));
    return;
  }

  orion::CompoundValueNode* child = new orion::CompoundValueNode(orion::ValueTypeObject);
  child->name = dbDotDecode(e.fieldName());

  switch (e.type())
  {
  case String:
    child->valueType  = orion::ValueTypeString;
    child->stringValue = e.String();
    break;

  case Bool:
    child->valueType  = orion::ValueTypeBoolean;
    child->boolValue = e.Bool();
    break;

  case NumberDouble:
    child->valueType  = orion::ValueTypeNumber;
    child->numberValue = e.Number();
    break;

  case jstNULL:
    child->valueType  = orion::ValueTypeNone;
    break;

  case Object:
    compoundObjectResponse(child, e);
    break;

  case Array:
    compoundVectorResponse(child, e);
    break;

  default:
    //
    // We need the default clause to avoid 'enumeration value X not handled in switch' errors
    // due to -Werror=switch at compilation time
    //
    break;
  }

  cvP->add(child);
}
    Status V2PrivilegeDocumentParser::initializeUserRolesFromPrivilegeDocument(
            User* user, const BSONObj& privDoc, const StringData&) const {

        BSONElement rolesElement = privDoc[ROLES_FIELD_NAME];

        if (rolesElement.type() != Array) {
            return Status(ErrorCodes::UnsupportedFormat,
                          "User document needs 'roles' field to be an array");
        }

        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();

            Status status = checkValidRoleObject(roleObject);
            if (!status.isOK()) {
                return status;
            }

            BSONElement roleNameElement = roleObject[ROLE_NAME_FIELD_NAME];
            BSONElement roleSourceElement = roleObject[ROLE_SOURCE_FIELD_NAME];
            BSONElement canDelegateElement = roleObject[ROLE_CAN_DELEGATE_FIELD_NAME];
            BSONElement hasRoleElement = roleObject[ROLE_HAS_ROLE_FIELD_NAME];

            if (hasRoleElement.Bool()) {
                user->addRole(RoleName(roleNameElement.String(), roleSourceElement.String()));
            }
            if (canDelegateElement.Bool()) {
                // TODO(spencer): record the fact that this user can delegate this role
            }
        }
        return Status::OK();
    }
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 {
            const bool haveSha1 = parseSCRAMCredentials(
                credentialsElement, credentials.scram_sha1, SCRAMSHA1_CREDENTIAL_FIELD_NAME);
            const bool haveSha256 = parseSCRAMCredentials(
                credentialsElement, credentials.scram_sha256, SCRAMSHA256_CREDENTIAL_FIELD_NAME);

            if (!haveSha1 && !haveSha256) {
                return Status(
                    ErrorCodes::UnsupportedFormat,
                    "User documents must provide credentials for SCRAM-SHA-1 and/or SCRAM-SHA-256");
            }

            credentials.isExternal = false;
        }
    } else {
        return Status(ErrorCodes::UnsupportedFormat,
                      "Cannot extract credentials from user documents without a "
                      "'credentials' field");
    }

    user->setCredentials(credentials);
    return Status::OK();
}
Exemple #10
0
        void reload() {

            list<BSONObj> all;
            {
                scoped_ptr<ScopedDbConnection> conn( ScopedDbConnection::getScopedDbConnection(
                        configServer.getPrimary().getConnString() ) );
                auto_ptr<DBClientCursor> c = conn->get()->query( ShardNS::shard , Query() );
                massert( 13632 , "couldn't get updated shard list from config server" , c.get() );
                while ( c->more() ) {
                    all.push_back( c->next().getOwned() );
                }
                conn->done();
            }

            scoped_lock lk( _mutex );

            // We use the _lookup table for all shards and for the primary config DB. The config DB info,
            // however, does not come from the ShardNS::shard. So when cleaning the _lookup table we leave
            // the config state intact. The rationale is that this way we could drop shards that
            // were removed without reinitializing the config DB information.

            ShardMap::iterator i = _lookup.find( "config" );
            if ( i != _lookup.end() ) {
                ShardPtr config = i->second;
                _lookup.clear();
                _lookup[ "config" ] = config;
            }
            else {
                _lookup.clear();
            }
            _rsLookup.clear();
            
            for ( list<BSONObj>::iterator i=all.begin(); i!=all.end(); ++i ) {
                BSONObj o = *i;
                string name = o["_id"].String();
                string host = o["host"].String();

                long long maxSize = 0;
                BSONElement maxSizeElem = o[ ShardFields::maxSize.name() ];
                if ( ! maxSizeElem.eoo() ) {
                    maxSize = maxSizeElem.numberLong();
                }

                bool isDraining = false;
                BSONElement isDrainingElem = o[ ShardFields::draining.name() ];
                if ( ! isDrainingElem.eoo() ) {
                    isDraining = isDrainingElem.Bool();
                }

                ShardPtr s( new Shard( name , host , maxSize , isDraining ) );

                if ( o["tags"].type() == Array ) {
                    vector<BSONElement> v = o["tags"].Array();
                    for ( unsigned j=0; j<v.size(); j++ ) {
                        s->addTag( v[j].String() );
                    }
                }

                _lookup[name] = s;
                _installHost( host , s );
            }

        }
Status V2UserDocumentParser::checkValidUserDocument(const BSONObj& doc) const {
    auto userIdElement = doc[AuthorizationManager::USERID_FIELD_NAME];
    auto userElement = doc[AuthorizationManager::USER_NAME_FIELD_NAME];
    auto userDBElement = doc[AuthorizationManager::USER_DB_FIELD_NAME];
    auto credentialsElement = doc[CREDENTIALS_FIELD_NAME];
    auto rolesElement = doc[ROLES_FIELD_NAME];

    // Validate the "userId" element.
    if (!userIdElement.eoo()) {
        if (!userIdElement.isBinData(BinDataType::newUUID)) {
            return _badValue("User document needs 'userId' field to be a UUID");
        }
    }

    // Validate the "user" element.
    if (userElement.type() != String)
        return _badValue("User document needs 'user' field to be a string");
    if (userElement.valueStringData().empty())
        return _badValue("User document needs 'user' field to be non-empty");

    // Validate the "db" element
    if (userDBElement.type() != String || userDBElement.valueStringData().empty()) {
        return _badValue("User document needs 'db' field to be a non-empty string");
    }
    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.");
    }

    // Validate the "credentials" element
    if (credentialsElement.eoo()) {
        return _badValue("User document needs 'credentials' object");
    }
    if (credentialsElement.type() != Object) {
        return _badValue("User document needs 'credentials' field to be an object");
    }

    BSONObj credentialsObj = credentialsElement.Obj();
    if (credentialsObj.isEmpty()) {
        return _badValue("User document needs 'credentials' field to be a non-empty object");
    }
    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}");
        }
    } else {
        const auto validateScram = [&credentialsObj](const auto& fieldName) {
            auto scramElement = credentialsObj[fieldName];

            if (scramElement.eoo()) {
                return Status(ErrorCodes::NoSuchKey,
                              str::stream() << fieldName << " does not exist");
            }
            if (scramElement.type() != Object) {
                return _badValue(str::stream() << fieldName
                                               << " credential must be an object, if present");
            }
            return Status::OK();
        };

        const auto sha1status = validateScram(SCRAMSHA1_CREDENTIAL_FIELD_NAME);
        if (!sha1status.isOK() && (sha1status.code() != ErrorCodes::NoSuchKey)) {
            return sha1status;
        }
        const auto sha256status = validateScram(SCRAMSHA256_CREDENTIAL_FIELD_NAME);
        if (!sha256status.isOK() && (sha256status.code() != ErrorCodes::NoSuchKey)) {
            return sha256status;
        }

        if (!sha1status.isOK() && !sha256status.isOK()) {
            return _badValue(
                "User document must provide credentials for all "
                "non-external users");
        }
    }

    // Validate the "roles" element.
    Status status = _checkV2RolesArray(rolesElement);
    if (!status.isOK())
        return status;

    // Validate the "authenticationRestrictions" element.
    status = initializeAuthenticationRestrictionsFromUserDocument(doc, nullptr);
    if (!status.isOK()) {
        return status;
    }

    return Status::OK();
}
    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();
    }
    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();
    }