require 'cgi'
require 'set'
require 'brakeman/processors/output_processor'
require 'brakeman/util'
require 'terminal-table'
require 'highline'
require "csv"
require 'multi_json'
require 'brakeman/version'
require 'brakeman/report/renderer'
Dir.glob(File.dirname(__FILE__) + '/report/initializers/*.rb').each {|file| require file }
#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(app_tree, tracker)
@app_tree = app_tree
@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
locals = {
:tracker => tracker,
:warnings => warnings,
:warnings_summary => warnings_summary,
:number_of_templates => number_of_templates(@tracker),
}
Brakeman::Report::Renderer.new('overview', :locals => locals).render
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
values = types.sort.collect{|warning_type| [warning_type, warnings_summary[warning_type]] }
locals = {:types => types, :warnings_summary => warnings_summary}
render_array('warning_overview', ['Warning Type', 'Total'], values, locals, html)
end
#Generate table of errors or return nil if no errors
def generate_errors html = false
values = tracker.errors.collect{|error| [error[:error], error[:backtrace][0]]}
render_array('error_overview', ['Error', 'Location'], values, {:tracker => tracker}, html)
end
def render_array(template, headings, value_array, locals, html = false)
return if value_array.empty?
if html
Brakeman::Report::Renderer.new(template, :locals => locals).render
else
Terminal::Table.new(:headings => headings) do |t|
value_array.each { |value_row| t.add_row value_row }
end
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"]
w["Warning Type"] = with_link warning, w["Warning Type"]
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]}
locals = {:warning_messages => warning_messages}
values = warning_messages.collect{|row| [row["Confidence"], row["Class"], row["Method"], row["Warning Type"], row["Message"]] }
render_array('security_warnings', ["Confidence", "Class", "Method", "Warning Type", "Message"], values, locals, html)
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"]
w["Warning Type"] = with_link warning, w["Warning Type"]
w["Called From"] = warning.called_from
w["Template Name"] = warning.template[:name]
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]}
locals = {:warnings => warnings}
values = warnings.collect{|warning| [warning["Confidence"], warning["Template"], warning["Warning Type"], warning["Message"]] }
render_array('view_warnings', ["Confidence", "Template", "Warning Type", "Message"], values, locals, html)
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"]
w["Warning Type"] = with_link warning, w["Warning Type"]
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]}
locals = {:warnings => warnings}
values = warnings.collect{|warning| [warning["Confidence"], warning["Model"], warning["Warning Type"], warning["Message"]] }
render_array('model_warnings', ["Confidence", "Model", "Warning Type", "Message"], values, locals, html)
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"]
w["Warning Type"] = with_link warning, w["Warning Type"]
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]}
locals = {:warnings => warnings}
values = warnings.collect{|warning| [warning["Confidence"], warning["Controller"], warning["Warning Type"], warning["Message"]] }
render_array('controller_warnings', ["Confidence", "Controller", "Warning Type", "Message"], values, locals, html)
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']}
locals = {:controller_rows => controller_rows}
values = controller_rows.collect{|row| [row['Name'], row['Parent'], row['Includes'], row['Routes']] }
render_array('controller_overview', ['Name', 'Parent', 'Includes', 'Routes'], values, locals, html)
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
Brakeman::Report::Renderer.new('template_overview', :locals => {:template_rows => template_rows}).render
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
# format output from filename or format
def format(filename_or_format)
case filename_or_format
when /\.html/, :to_html
to_html
when /\.pdf/, :to_pdf
to_pdf
when /\.csv/, :to_csv
to_csv
when /\.json/, :to_json
to_json
when /\.tabs/, :to_tabs
to_tabs
when :to_test
to_test
else
to_s
end
end
#Generate HTML output
def to_html
out = html_header <<
generate_overview(true) <<
generate_warning_overview(true).to_s
# Return early if only summarizing
return out if tracker.options[:summary_only]
out << generate_controllers(true).to_s if tracker.options[:report_routes] or tracker.options[:debug]
out << generate_templates(true).to_s if tracker.options[:debug]
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 << "