Class: ActiveRecordSaferQuery::Cli

Inherits:
Object
  • Object
show all
Defined in:
lib/active_record_safer_query/checker.rb

Constant Summary collapse

DEFAULT_FORMAT =
'text'
DEFAULT_FAIL_LEVEL =
'LOW'

Class Method Summary collapse

Class Method Details

.emit(findings, options, out) ⇒ Object



474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
# File 'lib/active_record_safer_query/checker.rb', line 474

def self.emit(findings, options, out)
  if options[:format] == 'json'
    out.puts JSON.pretty_generate(findings.map(&:to_h))
    return
  end

  if findings.empty?
    out.puts '[activerecord-safer-query] no findings'
    return
  end

  out.puts "[activerecord-safer-query] #{findings.size} findings"
  findings.each do |finding|
    out.puts "#{finding.path}:#{finding.line}: #{finding.severity} #{finding.rule}: #{finding.message}"
    out.puts "  #{finding.source}" unless finding.source.empty?
  end
end

.run(argv = ARGV, out: $stdout, err: $stderr) ⇒ Object



428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
# File 'lib/active_record_safer_query/checker.rb', line 428

def self.run(argv = ARGV, out: $stdout, err: $stderr)
  options = {
    format: DEFAULT_FORMAT,
    fail_level: DEFAULT_FAIL_LEVEL,
    root: Dir.pwd,
    whitelist_paths: []
  }

  parser = OptionParser.new do |opts|
    opts.banner = 'Usage: activerecord-safer-query [options] [paths...]'
    opts.separator ''
    opts.separator 'Detect class-level ActiveRecord lookups that may bypass tenant/user scopes.'
    opts.separator ''
    opts.on('--root PATH', 'Target repository root. Default: current directory') { |value| options[:root] = value }
    opts.on('--format FORMAT', 'text or json') { |value| options[:format] = value }
    opts.on('--fail-level LEVEL', 'LOW, MEDIUM, or HIGH. Default: LOW') { |value| options[:fail_level] = value.upcase }
    opts.on('--whitelist PATH', 'YAML whitelist file. Can be used multiple times') { |value| options[:whitelist_paths] << value }
    opts.on('-h', '--help', 'Show this help') do
      out.puts opts
      return 0
    end
  end

  paths = parser.parse(argv)
  validate_options!(options)

  findings = Checker.new(paths: paths, root: options[:root], whitelist_paths: options[:whitelist_paths]).findings
  emit(findings, options, out)

  findings.any? { |finding| finding.fail_at?(options[:fail_level]) } ? 1 : 0
rescue OptionParser::ParseError, ArgumentError => e
  err.puts "[activerecord-safer-query] #{e.message}"
  err.puts parser
  2
end

.validate_options!(options) ⇒ Object



464
465
466
467
468
469
470
471
472
# File 'lib/active_record_safer_query/checker.rb', line 464

def self.validate_options!(options)
  unless %w[text json].include?(options[:format])
    raise ArgumentError, "--format must be text or json: #{options[:format]}"
  end

  unless Checker::SEVERITY_RANK.key?(options[:fail_level])
    raise ArgumentError, "--fail-level must be LOW, MEDIUM, or HIGH: #{options[:fail_level]}"
  end
end