brakefast 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/brakefast.gemspec ADDED
@@ -0,0 +1,30 @@
1
+ lib = File.expand_path('../lib/', __FILE__)
2
+ $:.unshift lib unless $:.include?(lib)
3
+
4
+ require 'brakefast/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "brakefast"
8
+ spec.version = Brakefast::VERSION
9
+ spec.platform = Gem::Platform::RUBY
10
+ spec.authors = ["Sho Hashimoto"]
11
+ spec.email = ["sho.hsmt@gmail.com"]
12
+ spec.homepage = "https://github.com/sho-h/brakefast"
13
+
14
+ spec.summary = "runtime brakeman notifier like bullet"
15
+ spec.description = "runtime brakeman notifier like bullet"
16
+ spec.license = 'MIT'
17
+
18
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
19
+ spec.bindir = "exe"
20
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
21
+ spec.require_paths = ["lib"]
22
+
23
+ spec.add_runtime_dependency "uniform_notifier", "~> 1.9.0"
24
+ spec.add_runtime_dependency "brakeman"
25
+
26
+ spec.add_development_dependency "bundler", "~> 1.9"
27
+ spec.add_development_dependency "rake", "~> 10.0"
28
+ spec.add_development_dependency "rspec"
29
+ end
30
+
@@ -0,0 +1,18 @@
1
+ require 'brakeman'
2
+ require 'brakefast/detector'
3
+
4
+ module Brakefast
5
+ class Brakeman
6
+ def self.run(path)
7
+ tracker = ::Brakeman.run(Rails.root.to_s)
8
+ report = tracker.report.format(:to_hash)
9
+ Brakefast::Detector::Base.types.each do |type|
10
+ next if !Brakefast.public_send("#{type}_enable?")
11
+ report[type].each do |vulnerability|
12
+ detector = Brakefast::Detector::Base.create_instance(type, vulnerability)
13
+ detector.set_detector_module
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,7 @@
1
+ module Brakefast
2
+ module Dependency
3
+ def rails?
4
+ @rails ||= defined? ::Rails
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,58 @@
1
+ require 'forwardable'
2
+
3
+ module Brakefast
4
+ module Detector
5
+ class Base
6
+ extend Forwardable
7
+
8
+ attr_reader :vulnerability
9
+ def_delegator :@vulnerability, :method, :target_method_name
10
+ def_delegator :@vulnerability, :message, :message
11
+ def_delegator :@vulnerability, :file, :file
12
+ def_delegator :@vulnerability, :line, :line
13
+
14
+ @@type2klass = {
15
+ }
16
+
17
+ class << self
18
+ def create_instance(type, *args)
19
+ @@type2klass[type].new(*args)
20
+ end
21
+
22
+ def register_detector(type)
23
+ @@type2klass[type] = self
24
+ end
25
+
26
+ def types
27
+ @@type2klass.keys
28
+ end
29
+ end
30
+
31
+ def initialize(vulnerability)
32
+ @vulnerability = vulnerability
33
+ end
34
+
35
+ def set_detector_module
36
+ raise "override me"
37
+ end
38
+
39
+ def target_module_name
40
+ raise "override me"
41
+ end
42
+
43
+ private
44
+
45
+ def create_module_name(s)
46
+ s.to_s.gsub("::", "__") + "BrakefastHook"
47
+ end
48
+
49
+ def create_hook(module_name, hookee_mod)
50
+ name = create_module_name(module_name)
51
+ Brakefast.const_set(name, hookee_mod)
52
+ ::Object.const_get(module_name).class_eval %Q{
53
+ prepend Brakefast::#{name}
54
+ }
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,32 @@
1
+ module Brakefast
2
+ module Detector
3
+ class ControllerWarnings < Base
4
+ register_detector :controller_warnings
5
+
6
+ def set_detector_module
7
+ if target_module_name && target_method_name
8
+ mod = Module.new
9
+ mod.module_eval %Q{
10
+ def #{target_method_name}
11
+ s = "vulnerability found in #{file}:#{line} - #{message}"
12
+ Thread.current[:brakefast_notifications] << s
13
+ =begin
14
+ # for debug
15
+ File.open("/tmp/1.log", "a") do |f|
16
+ f.write("vulnerability found in #{e.file}:#{e.line} - #{msg}")
17
+ end
18
+ =end
19
+ super
20
+ end
21
+ }
22
+
23
+ create_hook(target_module_name, mod)
24
+ end
25
+ end
26
+
27
+ def target_module_name
28
+ vulnerability.controller
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,27 @@
1
+ module Brakefast
2
+ module Detector
3
+ class GenericWarnings < Base
4
+ register_detector :generic_warnings
5
+
6
+ def set_detector_module
7
+ if target_module_name && target_method_name
8
+ mod = Module.new
9
+ mod.module_eval %Q{
10
+ def #{target_method_name}
11
+ n = Brakefast::Notification::GenericWarnings.new(self, '#{message}',
12
+ '#{file}','#{line}')
13
+ Brakefast.notification_collector.add(n)
14
+ super
15
+ end
16
+ }
17
+
18
+ create_hook(target_module_name, mod)
19
+ end
20
+ end
21
+
22
+ def target_module_name
23
+ vulnerability.class
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,26 @@
1
+ module Brakefast
2
+ module Detector
3
+ class ModelWarnings < Base
4
+ register_detector :model_warnings
5
+
6
+ def set_detector_module
7
+ if target_module_name && target_method_name
8
+ mod = Module.new
9
+ mod.module_eval %Q{
10
+ def #{target_method_name}
11
+ s = "vulnerability found in #{file}:#{line} - #{message}"
12
+ Thread.current[:brakefast_notifications] << s
13
+ super
14
+ end
15
+ }
16
+
17
+ create_hook(target_module_name, mod)
18
+ end
19
+ end
20
+
21
+ def target_module_name
22
+ vulnerability.model
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,8 @@
1
+ module Brakefast
2
+ module Detector
3
+ autoload :Base, 'brakefast/detector/base'
4
+ autoload :ControllerWarnings, 'brakefast/detector/controller_warnings'
5
+ autoload :ModelWarnings, 'brakefast/detector/model_warnings'
6
+ autoload :GenericWarnings, 'brakefast/detector/generic_warnings'
7
+ end
8
+ end
@@ -0,0 +1,61 @@
1
+ module Brakefast
2
+ module Notification
3
+ class Base
4
+ attr_accessor :notifier, :url
5
+ attr_reader :klass, :message, :path, :line
6
+
7
+ def initialize(klass, message, path = nil, line = nil)
8
+ @klass = klass
9
+ @message = message
10
+ @path = path
11
+ @line = line
12
+ end
13
+
14
+ def title
15
+ raise NoMethodError.new("no method title defined")
16
+ end
17
+
18
+ def body
19
+ raise NoMethodError.new("no method body defined")
20
+ end
21
+
22
+ def call_stack_messages
23
+ ""
24
+ end
25
+
26
+ def whoami
27
+ @user ||= ENV['USER'].presence || (`whoami`.chomp rescue "")
28
+ if @user.present?
29
+ "user: #{@user}"
30
+ else
31
+ ""
32
+ end
33
+ end
34
+
35
+ def body_with_caller
36
+ "#{body}\n#{call_stack_messages}\n"
37
+ end
38
+
39
+ def notify_inline
40
+ self.notifier.inline_notify(notification_data)
41
+ end
42
+
43
+ def notify_out_of_channel
44
+ self.notifier.out_of_channel_notify(notification_data)
45
+ end
46
+
47
+ def short_notice
48
+ [whoami.presence, url, title, body].compact.join(" ")
49
+ end
50
+
51
+ def notification_data
52
+ {
53
+ :user => whoami,
54
+ :url => url,
55
+ :title => title,
56
+ :body => body_with_caller,
57
+ }
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,14 @@
1
+ module Brakefast
2
+ module Notification
3
+ class ControllerWarnings < Base
4
+ # TODO: move to base?
5
+ def title
6
+ "vulnerability found: ControllerWarnings"
7
+ end
8
+
9
+ def body
10
+ "#{message} - #{path}:#{line}"
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,14 @@
1
+ module Brakefast
2
+ module Notification
3
+ class GenericWarnings < Base
4
+ # TODO: move to base?
5
+ def title
6
+ "vulnerability found: GenericWarnings"
7
+ end
8
+
9
+ def body
10
+ "#{message} - #{path}:#{line}"
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,14 @@
1
+ module Brakefast
2
+ module Notification
3
+ class ModelWarnings < Base
4
+ # TODO: move to base?
5
+ def title
6
+ "vulnerability found: ModelWarnings"
7
+ end
8
+
9
+ def body
10
+ "#{message} - #{path}:#{line}"
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,10 @@
1
+ module Brakefast
2
+ module Notification
3
+ autoload :Base, 'brakefast/notification/base'
4
+ autoload :GenericWarnings, 'brakefast/notification/generic_warnings'
5
+ autoload :ControllerWarnings, 'brakefast/notification/controller_warnings'
6
+ autoload :ModelWarnings, 'brakefast/notification/model_warnings'
7
+
8
+ class UnoptimizedQueryError < StandardError; end
9
+ end
10
+ end
@@ -0,0 +1,24 @@
1
+ require 'set'
2
+
3
+ module Brakefast
4
+ class NotificationCollector
5
+ attr_reader :collection
6
+
7
+ def initialize
8
+ reset
9
+ end
10
+
11
+ def reset
12
+ @collection = Set.new
13
+ end
14
+
15
+ def add(value)
16
+ @collection << value
17
+ end
18
+
19
+ def notifications_present?
20
+ !@collection.empty?
21
+ end
22
+ end
23
+ end
24
+
@@ -0,0 +1,95 @@
1
+ require 'brakefast/brakeman'
2
+
3
+ module Brakefast
4
+ class Rack
5
+ include Dependency
6
+
7
+ def initialize(app)
8
+ @app = app
9
+ @brakeman_run = false
10
+ end
11
+
12
+ def call(env)
13
+ return @app.call(env) unless Brakefast.enable?
14
+ if !@brakeman_run
15
+ Brakefast::Brakeman.run(Rails.root.to_s)
16
+ @brakeman_run = true
17
+ end
18
+ Brakefast.start_request
19
+ status, headers, response = @app.call(env)
20
+
21
+ response_body = nil
22
+ if Brakefast.notification?
23
+ if !file?(headers) && !sse?(headers) && !empty?(response) &&
24
+ status == 200 && !response_body(response).frozen? && html_request?(headers, response)
25
+ response_body = response_body(response)
26
+ append_to_html_body(response_body, footer_note) if Brakefast.add_footer
27
+ append_to_html_body(response_body, Brakefast.gather_inline_notifications)
28
+ headers['Content-Length'] = response_body.bytesize.to_s
29
+ end
30
+ Brakefast.perform_out_of_channel_notifications(env)
31
+ end
32
+ [status, headers, response_body ? [response_body] : response]
33
+ ensure
34
+ Brakefast.end_request
35
+ end
36
+
37
+ # fix issue if response's body is a Proc
38
+ def empty?(response)
39
+ # response may be ["Not Found"], ["Move Permanently"], etc.
40
+ if rails?
41
+ (response.is_a?(Array) && response.size <= 1) ||
42
+ !response.respond_to?(:body) ||
43
+ !response_body(response).respond_to?(:empty?) ||
44
+ response_body(response).empty?
45
+ else
46
+ body = response_body(response)
47
+ body.nil? || body.empty?
48
+ end
49
+ end
50
+
51
+ def append_to_html_body(response_body, content)
52
+ if response_body.include?('</body>')
53
+ position = response_body.rindex('</body>')
54
+ response_body.insert(position, content)
55
+ else
56
+ response_body << content
57
+ end
58
+ end
59
+
60
+ def footer_note
61
+ "<div #{footer_div_attributes}>" + Brakefast.footer_info.uniq.join("<br>") + "</div>"
62
+ end
63
+
64
+ def file?(headers)
65
+ headers["Content-Transfer-Encoding"] == "binary"
66
+ end
67
+
68
+ def sse?(headers)
69
+ headers["Content-Type"] == "text/event-stream"
70
+ end
71
+
72
+ def html_request?(headers, response)
73
+ headers['Content-Type'] && headers['Content-Type'].include?('text/html') && response_body(response).include?("<html")
74
+ end
75
+
76
+ def response_body(response)
77
+ if rails?
78
+ Array === response.body ? response.body.first : response.body
79
+ else
80
+ response.first
81
+ end
82
+ end
83
+
84
+ private
85
+ def footer_div_attributes
86
+ <<EOF
87
+ data-is-brakefast-footer ondblclick="this.parentNode.removeChild(this);" style="position: fixed; bottom: 0pt; left: 0pt; cursor: pointer; border-style: solid; border-color: rgb(153, 153, 153);
88
+ -moz-border-top-colors: none; -moz-border-right-colors: none; -moz-border-bottom-colors: none;
89
+ -moz-border-left-colors: none; -moz-border-image: none; border-width: 2pt 2pt 0px 0px;
90
+ padding: 5px; border-radius: 0pt 10pt 0pt 0px; background: none repeat scroll 0% 0% rgba(200, 200, 200, 0.8);
91
+ color: rgb(119, 119, 119); font-size: 18px; font-family: 'Arial', sans-serif; z-index:9999;"
92
+ EOF
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,4 @@
1
+ # encoding: utf-8
2
+ module Brakefast
3
+ VERSION = "0.0.1"
4
+ end
data/lib/brakefast.rb ADDED
@@ -0,0 +1,178 @@
1
+ require "active_support/core_ext/module/delegation"
2
+ require 'set'
3
+ require 'uniform_notifier'
4
+ require 'brakefast/dependency'
5
+
6
+ module Brakefast
7
+ extend Dependency
8
+
9
+ autoload :Rack, 'brakefast/rack'
10
+ autoload :Notification, 'brakefast/notification'
11
+ autoload :Detector, 'brakefast/detector'
12
+ autoload :NotificationCollector, 'brakefast/notification_collector'
13
+
14
+ BRAKEFAST_DEBUG = 'BRAKEFAST_DEBUG'.freeze
15
+ TRUE = 'true'.freeze
16
+
17
+ if defined? Rails::Railtie
18
+ class BrakefastRailtie < Rails::Railtie
19
+ initializer "brakefast.configure_rails_initialization" do |app|
20
+ app.middleware.use Brakefast::Rack
21
+ end
22
+ end
23
+ end
24
+
25
+ class << self
26
+ attr_writer :enable, :errors_enable, :generic_warnings_enable, :controller_warnings_enable, :model_warnings_enable, :stacktrace_includes
27
+ attr_reader :notification_collector, :whitelist
28
+ attr_accessor :add_footer
29
+
30
+ available_notifiers = UniformNotifier::AVAILABLE_NOTIFIERS.map { |notifier| "#{notifier}=" }
31
+ available_notifiers << { :to => UniformNotifier }
32
+ delegate *available_notifiers
33
+
34
+ def raise=(should_raise)
35
+ UniformNotifier.raise=(should_raise ? Notification::UnoptimizedQueryError : false)
36
+ end
37
+
38
+ DETECTORS = [ Brakefast::Detector::ControllerWarnings,
39
+ Brakefast::Detector::ModelWarnings,
40
+ Brakefast::Detector::GenericWarnings ]
41
+
42
+ def enable=(enable)
43
+ @enable = @errors_enable = @generic_warnings_enable = @controller_warnings_enable = @model_warnings_enable = enable
44
+ reset_whitelist if enable?
45
+ end
46
+
47
+ def enable?
48
+ !!@enable
49
+ end
50
+
51
+ def errors_enable?
52
+ self.enable? && !!@errors_enable
53
+ end
54
+
55
+ def generic_warnings_enable?
56
+ self.enable? && !!@generic_warnings_enable
57
+ end
58
+
59
+ def controller_warnings_enable?
60
+ self.enable? && !!@controller_warnings_enable
61
+ end
62
+
63
+ def model_warnings_enable?
64
+ self.enable? && !!@model_warnings_enable
65
+ end
66
+
67
+ def stacktrace_includes
68
+ @stacktrace_includes || []
69
+ end
70
+
71
+ def add_whitelist(options)
72
+ @whitelist[options[:type]][options[:class_name].classify] ||= []
73
+ @whitelist[options[:type]][options[:class_name].classify] << options[:association].to_sym
74
+ end
75
+
76
+ def get_whitelist_associations(type, class_name)
77
+ Array(@whitelist[type][class_name])
78
+ end
79
+
80
+ def reset_whitelist
81
+ @whitelist = {:n_plus_one_query => {}, :unused_eager_loading => {}, :counter_cache => {}}
82
+ end
83
+
84
+ def brakefast_logger=(active)
85
+ if active
86
+ require 'fileutils'
87
+ root_path = "#{rails? ? Rails.root.to_s : Dir.pwd}"
88
+ FileUtils.mkdir_p(root_path + '/log')
89
+ brakefast_log_file = File.open("#{root_path}/log/brakefast.log", 'a+')
90
+ brakefast_log_file.sync = true
91
+ UniformNotifier.customized_logger = brakefast_log_file
92
+ end
93
+ end
94
+
95
+ def debug(title, message)
96
+ puts "[Brakefast][#{title}] #{message}" if ENV[BRAKEFAST_DEBUG] == TRUE
97
+ end
98
+
99
+ def start_request
100
+ Thread.current[:brakefast_start] = true
101
+ Thread.current[:brakefast_notification_collector] = Brakefast::NotificationCollector.new
102
+ end
103
+
104
+ def end_request
105
+ Thread.current[:brakefast_start] = nil
106
+ Thread.current[:brakefast_notification_collector] = nil
107
+ end
108
+
109
+ def start?
110
+ enable? && Thread.current[:brakefast_start]
111
+ end
112
+
113
+ def notification_collector
114
+ Thread.current[:brakefast_notification_collector]
115
+ end
116
+
117
+ def notification?
118
+ return unless start?
119
+ notification_collector.notifications_present?
120
+ end
121
+
122
+ def gather_inline_notifications
123
+ responses = []
124
+ for_each_active_notifier_with_notification do |notification|
125
+ responses << notification.notify_inline
126
+ end
127
+ responses.join( "\n" )
128
+ end
129
+
130
+ def perform_out_of_channel_notifications(env = {})
131
+ for_each_active_notifier_with_notification do |notification|
132
+ notification.url = env['REQUEST_URI']
133
+ notification.notify_out_of_channel
134
+ end
135
+ end
136
+
137
+ def footer_info
138
+ info = []
139
+ notification_collector.collection.each do |notification|
140
+ info << notification.short_notice
141
+ end
142
+ info
143
+ end
144
+
145
+ def warnings
146
+ notification_collector.collection.inject({}) do |warnings, notification|
147
+ warning_type = notification.class.to_s.split(':').last.tableize
148
+ warnings[warning_type] ||= []
149
+ warnings[warning_type] << notification
150
+ warnings
151
+ end
152
+ end
153
+
154
+ def profile
155
+ if Brakefast.enable?
156
+ begin
157
+ Brakefast.start_request
158
+
159
+ yield
160
+
161
+ Brakefast.perform_out_of_channel_notifications if Brakefast.notification?
162
+ ensure
163
+ Brakefast.end_request
164
+ end
165
+ end
166
+ end
167
+
168
+ private
169
+ def for_each_active_notifier_with_notification
170
+ UniformNotifier.active_notifiers.each do |notifier|
171
+ notification_collector.collection.each do |notification|
172
+ notification.notifier = notifier
173
+ yield notification
174
+ end
175
+ end
176
+ end
177
+ end
178
+ end