command_utils.js 3.25 KB
'use strict';
const Msg = require('./core/connection/msg').Msg;
const KillCursor = require('./core/connection/commands').KillCursor;
const GetMore = require('./core/connection/commands').GetMore;
const deepCopy = require('./utils').deepCopy;

/** Commands that we want to redact because of the sensitive nature of their contents */
const SENSITIVE_COMMANDS = new Set([
  'authenticate',
  'saslStart',
  'saslContinue',
  'getnonce',
  'createUser',
  'updateUser',
  'copydbgetnonce',
  'copydbsaslstart',
  'copydb'
]);

const HELLO_COMMANDS = new Set(['hello', 'ismaster', 'isMaster']);

const LEGACY_FIND_QUERY_MAP = {
  $query: 'filter',
  $orderby: 'sort',
  $hint: 'hint',
  $comment: 'comment',
  $maxScan: 'maxScan',
  $max: 'max',
  $min: 'min',
  $returnKey: 'returnKey',
  $showDiskLoc: 'showRecordId',
  $maxTimeMS: 'maxTimeMS',
  $snapshot: 'snapshot'
};

const LEGACY_FIND_OPTIONS_MAP = {
  numberToSkip: 'skip',
  numberToReturn: 'batchSize',
  returnFieldsSelector: 'projection'
};

const OP_QUERY_KEYS = [
  'tailable',
  'oplogReplay',
  'noCursorTimeout',
  'awaitData',
  'partial',
  'exhaust'
];

const collectionName = command => command.ns.split('.')[1];

const shouldRedactCommand = (commandName, cmd) =>
  SENSITIVE_COMMANDS.has(commandName) ||
  (HELLO_COMMANDS.has(commandName) && !!cmd.speculativeAuthenticate);

/**
 * Extract the actual command from the query, possibly upconverting if it's a legacy
 * format
 *
 * @param {Object} command the command
 */
const extractCommand = command => {
  let extractedCommand;
  if (command instanceof GetMore) {
    extractedCommand = {
      getMore: deepCopy(command.cursorId),
      collection: collectionName(command),
      batchSize: command.numberToReturn
    };
  } else if (command instanceof KillCursor) {
    extractedCommand = {
      killCursors: collectionName(command),
      cursors: deepCopy(command.cursorIds)
    };
  } else if (command instanceof Msg) {
    extractedCommand = deepCopy(command.command);
  } else if (command.query && command.query.$query) {
    let result;
    if (command.ns === 'admin.$cmd') {
      // upconvert legacy command
      result = Object.assign({}, command.query.$query);
    } else {
      // upconvert legacy find command
      result = { find: collectionName(command) };
      Object.keys(LEGACY_FIND_QUERY_MAP).forEach(key => {
        if (typeof command.query[key] !== 'undefined')
          result[LEGACY_FIND_QUERY_MAP[key]] = deepCopy(command.query[key]);
      });
    }

    Object.keys(LEGACY_FIND_OPTIONS_MAP).forEach(key => {
      if (typeof command[key] !== 'undefined')
        result[LEGACY_FIND_OPTIONS_MAP[key]] = deepCopy(command[key]);
    });

    OP_QUERY_KEYS.forEach(key => {
      if (command[key]) result[key] = command[key];
    });

    if (typeof command.pre32Limit !== 'undefined') {
      result.limit = command.pre32Limit;
    }

    if (command.query.$explain) {
      extractedCommand = { explain: result };
    } else {
      extractedCommand = result;
    }
  } else {
    extractedCommand = deepCopy(command.query || command);
  }

  const commandName = Object.keys(extractedCommand)[0];
  return {
    cmd: extractedCommand,
    name: commandName,
    shouldRedact: shouldRedactCommand(commandName, extractedCommand)
  };
};

module.exports = {
  extractCommand
};