require 'cgi'
require 'set'
require 'brakeman/processors/output_processor'
require 'brakeman/util'
require 'terminal-table'
require 'highline/system_extensions'
require "csv"
require 'brakeman/version'
if CSV.const_defined? :Reader
# Ruby 1.8 compatible
require 'fastercsv'
Object.send(:remove_const, :CSV)
CSV = FasterCSV
else
# CSV is now FasterCSV in ruby 1.9
end
#Generates a report based on the Tracker and the results of
#Tracker#run_checks. Be sure to +run_checks+ before generating
#a report.
class Brakeman::Report
include Brakeman::Util
attr_reader :tracker, :checks
TEXT_CONFIDENCE = [ "High", "Medium", "Weak" ]
HTML_CONFIDENCE = [ "High",
"Medium",
"Weak" ]
def initialize tracker
@tracker = tracker
@checks = tracker.checks
@element_id = 0 #Used for HTML ids
@warnings_summary = nil
@highlight_user_input = tracker.options[:highlight_user_input]
end
#Generate summary table of what was parsed
def generate_overview html = false
warnings = all_warnings.length
if html
load_and_render_erb('overview', binding)
else
Terminal::Table.new(:headings => ['Scanned/Reported', 'Total']) do |t|
t.add_row ['Controllers', tracker.controllers.length]
t.add_row ['Models', tracker.models.length - 1]
t.add_row ['Templates', number_of_templates(@tracker)]
t.add_row ['Errors', tracker.errors.length]
t.add_row ['Security Warnings', "#{warnings} (#{warnings_summary[:high_confidence]})"]
end
end
end
#Generate table of how many warnings of each warning type were reported
def generate_warning_overview html = false
types = warnings_summary.keys
types.delete :high_confidence
if html
load_and_render_erb('warning_overview', binding)
else
Terminal::Table.new(:headings => ['Warning Type', 'Total']) do |t|
types.sort.each do |warning_type|
t.add_row [warning_type, warnings_summary[warning_type]]
end
end
end
end
#Generate table of errors or return nil if no errors
def generate_errors html = false
if tracker.errors.any?
if html
load_and_render_erb('error_overview', binding)
else
Terminal::Table.new(:headings => ['Error', 'Location']) do |t|
tracker.errors.each do |error|
t.add_row [error[:error], error[:backtrace][0]]
end
end
end
else
nil
end
end
#Generate table of general security warnings
def generate_warnings html = false
warning_messages = []
checks.warnings.each do |warning|
w = warning.to_row
if html
w["Confidence"] = HTML_CONFIDENCE[w["Confidence"]]
w["Message"] = with_context warning, w["Message"]
else
w["Confidence"] = TEXT_CONFIDENCE[w["Confidence"]]
w["Message"] = text_message warning, w["Message"]
end
warning_messages << w
end
stabilizer = 0
warning_messages = warning_messages.sort_by{|row| stabilizer += 1; [row['Confidence'], row['Warning Type'], row['Class'], stabilizer]}
if html
load_and_render_erb('security_warnings', binding)
else
if warning_messages.empty?
Terminal::Table.new(:headings => ['General Warnings']) do |t|
t.add_row ['[NONE]']
end
else
Terminal::Table.new(:headings => ["Confidence", "Class", "Method", "Warning Type", "Message"]) do |t|
warning_messages.each do |row|
t.add_row [row["Confidence"], row["Class"], row["Method"], row["Warning Type"], row["Message"]]
end
end
end
end
end
#Generate table of template warnings or return nil if no warnings
def generate_template_warnings html = false
if checks.template_warnings.any?
warnings = []
checks.template_warnings.each do |warning|
w = warning.to_row :template
if html
w["Confidence"] = HTML_CONFIDENCE[w["Confidence"]]
w["Message"] = with_context warning, w["Message"]
else
w["Confidence"] = TEXT_CONFIDENCE[w["Confidence"]]
w["Message"] = text_message warning, w["Message"]
end
warnings << w
end
return nil if warnings.empty?
stabilizer = 0
warnings = warnings.sort_by{|row| stabilizer += 1; [row["Confidence"], row["Warning Type"], row["Template"], stabilizer]}
if html
load_and_render_erb('view_warnings', binding)
else
Terminal::Table.new(:headings => ["Confidence", "Template", "Warning Type", "Message"]) do |t|
warnings.each do |warning|
t.add_row [warning["Confidence"], warning["Template"], warning["Warning Type"], warning["Message"]]
end
end
end
else
nil
end
end
#Generate table of model warnings or return nil if no warnings
def generate_model_warnings html = false
if checks.model_warnings.any?
warnings = []
checks.model_warnings.each do |warning|
w = warning.to_row :model
if html
w["Confidence"] = HTML_CONFIDENCE[w["Confidence"]]
w["Message"] = with_context warning, w["Message"]
else
w["Confidence"] = TEXT_CONFIDENCE[w["Confidence"]]
w["Message"] = text_message warning, w["Message"]
end
warnings << w
end
return nil if warnings.empty?
stabilizer = 0
warnings = warnings.sort_by{|row| stabilizer +=1; [row["Confidence"],row["Warning Type"], row["Model"], stabilizer]}
if html
load_and_render_erb('model_warnings', binding)
else
Terminal::Table.new(:headings => ["Confidence", "Model", "Warning Type", "Message"]) do |t|
warnings.each do |warning|
t.add_row [warning["Confidence"], warning["Model"], warning["Warning Type"], warning["Message"]]
end
end
end
else
nil
end
end
#Generate table of controller warnings or nil if no warnings
def generate_controller_warnings html = false
unless checks.controller_warnings.empty?
warnings = []
checks.controller_warnings.each do |warning|
w = warning.to_row :controller
if html
w["Confidence"] = HTML_CONFIDENCE[w["Confidence"]]
w["Message"] = with_context warning, w["Message"]
else
w["Confidence"] = TEXT_CONFIDENCE[w["Confidence"]]
w["Message"] = text_message warning, w["Message"]
end
warnings << w
end
return nil if warnings.empty?
stabilizer = 0
warnings = warnings.sort_by{|row| stabilizer +=1; [row["Confidence"], row["Warning Type"], row["Controller"], stabilizer]}
if html
load_and_render_erb('controller_warnings', binding)
else
Terminal::Table.new(:headings => ["Confidence", "Controller", "Warning Type", "Message"]) do |t|
warnings.each do |warning|
t.add_row [warning["Confidence"], warning["Controller"], warning["Warning Type"], warning["Message"]]
end
end
end
else
nil
end
end
#Generate table of controllers and routes found for those controllers
def generate_controllers html=false
controller_rows = []
tracker.controllers.keys.map{|k| k.to_s}.sort.each do |name|
name = name.to_sym
c = tracker.controllers[name]
if tracker.routes[:allow_all_actions] or tracker.routes[name] == :allow_all_actions
routes = c[:public].keys.map{|e| e.to_s}.sort.join(", ")
elsif tracker.routes[name].nil?
#No routes defined for this controller.
#This can happen when it is only a parent class
#for other controllers, for example.
routes = "[None]"
else
routes = (Set.new(c[:public].keys) & tracker.routes[name.to_sym]).
to_a.
map {|e| e.to_s}.
sort.
join(", ")
end
if routes == ""
routes = "[None]"
end
controller_rows << { "Name" => name.to_s,
"Parent" => c[:parent].to_s,
"Includes" => c[:includes].join(", "),
"Routes" => routes
}
end
controller_rows = controller_rows.sort_by{|row| row['Name']}
if html
load_and_render_erb('controller_overview', binding)
else
Terminal::Table.new(:headings => ['Name', 'Parent', 'Includes', 'Routes']) do |t|
controller_rows.each do |row|
t.add_row [row['Name'], row['Parent'], row['Includes'], row['Routes']]
end
end
end
end
#Generate listings of templates and their output
def generate_templates html = false
out_processor = Brakeman::OutputProcessor.new
template_rows = {}
tracker.templates.each do |name, template|
unless template[:outputs].empty?
template[:outputs].each do |out|
out = out_processor.format out
out = CGI.escapeHTML(out) if html
template_rows[name] ||= []
template_rows[name] << out.gsub("\n", ";").gsub(/\s+/, " ")
end
end
end
template_rows = template_rows.sort_by{|name, value| name.to_s}
if html
load_and_render_erb('template_overview', binding)
else
output = ''
template_rows.each do |template|
output << template.first.to_s << "\n\n"
table = Terminal::Table.new(:headings => ['Output']) do |t|
# template[1] is an array of calls
template[1].each do |v|
t.add_row [v]
end
end
output << table.to_s << "\n\n"
end
output
end
end
#Generate HTML output
def to_html
out = html_header <<
generate_overview(true) <<
generate_warning_overview(true)
# Return early if only summarizing
if tracker.options[:summary_only]
return out
end
if tracker.options[:report_routes] or tracker.options[:debug]
out << generate_controllers(true).to_s
end
if tracker.options[:debug]
out << generate_templates(true).to_s
end
out << generate_errors(true).to_s
out << generate_warnings(true).to_s
out << generate_controller_warnings(true).to_s
out << generate_model_warnings(true).to_s
out << generate_template_warnings(true).to_s
out << "