brakefast 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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