railroader 4.3.4

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.
Files changed (165) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGES.md +1091 -0
  3. data/FEATURES +16 -0
  4. data/README.md +174 -0
  5. data/bin/railroader +8 -0
  6. data/lib/railroader/app_tree.rb +191 -0
  7. data/lib/railroader/call_index.rb +219 -0
  8. data/lib/railroader/checks/base_check.rb +505 -0
  9. data/lib/railroader/checks/check_basic_auth.rb +88 -0
  10. data/lib/railroader/checks/check_basic_auth_timing_attack.rb +33 -0
  11. data/lib/railroader/checks/check_content_tag.rb +200 -0
  12. data/lib/railroader/checks/check_create_with.rb +74 -0
  13. data/lib/railroader/checks/check_cross_site_scripting.rb +381 -0
  14. data/lib/railroader/checks/check_default_routes.rb +86 -0
  15. data/lib/railroader/checks/check_deserialize.rb +56 -0
  16. data/lib/railroader/checks/check_detailed_exceptions.rb +55 -0
  17. data/lib/railroader/checks/check_digest_dos.rb +38 -0
  18. data/lib/railroader/checks/check_divide_by_zero.rb +42 -0
  19. data/lib/railroader/checks/check_dynamic_finders.rb +48 -0
  20. data/lib/railroader/checks/check_escape_function.rb +21 -0
  21. data/lib/railroader/checks/check_evaluation.rb +35 -0
  22. data/lib/railroader/checks/check_execute.rb +189 -0
  23. data/lib/railroader/checks/check_file_access.rb +71 -0
  24. data/lib/railroader/checks/check_file_disclosure.rb +35 -0
  25. data/lib/railroader/checks/check_filter_skipping.rb +31 -0
  26. data/lib/railroader/checks/check_forgery_setting.rb +81 -0
  27. data/lib/railroader/checks/check_header_dos.rb +31 -0
  28. data/lib/railroader/checks/check_i18n_xss.rb +48 -0
  29. data/lib/railroader/checks/check_jruby_xml.rb +36 -0
  30. data/lib/railroader/checks/check_json_encoding.rb +47 -0
  31. data/lib/railroader/checks/check_json_parsing.rb +107 -0
  32. data/lib/railroader/checks/check_link_to.rb +132 -0
  33. data/lib/railroader/checks/check_link_to_href.rb +146 -0
  34. data/lib/railroader/checks/check_mail_to.rb +49 -0
  35. data/lib/railroader/checks/check_mass_assignment.rb +196 -0
  36. data/lib/railroader/checks/check_mime_type_dos.rb +39 -0
  37. data/lib/railroader/checks/check_model_attr_accessible.rb +55 -0
  38. data/lib/railroader/checks/check_model_attributes.rb +119 -0
  39. data/lib/railroader/checks/check_model_serialize.rb +67 -0
  40. data/lib/railroader/checks/check_nested_attributes.rb +38 -0
  41. data/lib/railroader/checks/check_nested_attributes_bypass.rb +58 -0
  42. data/lib/railroader/checks/check_number_to_currency.rb +74 -0
  43. data/lib/railroader/checks/check_permit_attributes.rb +43 -0
  44. data/lib/railroader/checks/check_quote_table_name.rb +40 -0
  45. data/lib/railroader/checks/check_redirect.rb +256 -0
  46. data/lib/railroader/checks/check_regex_dos.rb +68 -0
  47. data/lib/railroader/checks/check_render.rb +97 -0
  48. data/lib/railroader/checks/check_render_dos.rb +37 -0
  49. data/lib/railroader/checks/check_render_inline.rb +53 -0
  50. data/lib/railroader/checks/check_response_splitting.rb +21 -0
  51. data/lib/railroader/checks/check_route_dos.rb +42 -0
  52. data/lib/railroader/checks/check_safe_buffer_manipulation.rb +31 -0
  53. data/lib/railroader/checks/check_sanitize_methods.rb +112 -0
  54. data/lib/railroader/checks/check_secrets.rb +40 -0
  55. data/lib/railroader/checks/check_select_tag.rb +59 -0
  56. data/lib/railroader/checks/check_select_vulnerability.rb +60 -0
  57. data/lib/railroader/checks/check_send.rb +47 -0
  58. data/lib/railroader/checks/check_send_file.rb +19 -0
  59. data/lib/railroader/checks/check_session_manipulation.rb +35 -0
  60. data/lib/railroader/checks/check_session_settings.rb +176 -0
  61. data/lib/railroader/checks/check_simple_format.rb +58 -0
  62. data/lib/railroader/checks/check_single_quotes.rb +101 -0
  63. data/lib/railroader/checks/check_skip_before_filter.rb +60 -0
  64. data/lib/railroader/checks/check_sql.rb +700 -0
  65. data/lib/railroader/checks/check_sql_cves.rb +106 -0
  66. data/lib/railroader/checks/check_ssl_verify.rb +48 -0
  67. data/lib/railroader/checks/check_strip_tags.rb +89 -0
  68. data/lib/railroader/checks/check_symbol_dos.rb +71 -0
  69. data/lib/railroader/checks/check_symbol_dos_cve.rb +30 -0
  70. data/lib/railroader/checks/check_translate_bug.rb +45 -0
  71. data/lib/railroader/checks/check_unsafe_reflection.rb +50 -0
  72. data/lib/railroader/checks/check_unscoped_find.rb +57 -0
  73. data/lib/railroader/checks/check_validation_regex.rb +116 -0
  74. data/lib/railroader/checks/check_weak_hash.rb +148 -0
  75. data/lib/railroader/checks/check_without_protection.rb +80 -0
  76. data/lib/railroader/checks/check_xml_dos.rb +45 -0
  77. data/lib/railroader/checks/check_yaml_parsing.rb +121 -0
  78. data/lib/railroader/checks.rb +209 -0
  79. data/lib/railroader/codeclimate/engine_configuration.rb +97 -0
  80. data/lib/railroader/commandline.rb +179 -0
  81. data/lib/railroader/differ.rb +66 -0
  82. data/lib/railroader/file_parser.rb +54 -0
  83. data/lib/railroader/format/style.css +133 -0
  84. data/lib/railroader/options.rb +339 -0
  85. data/lib/railroader/parsers/rails2_erubis.rb +6 -0
  86. data/lib/railroader/parsers/rails2_xss_plugin_erubis.rb +48 -0
  87. data/lib/railroader/parsers/rails3_erubis.rb +81 -0
  88. data/lib/railroader/parsers/template_parser.rb +108 -0
  89. data/lib/railroader/processor.rb +102 -0
  90. data/lib/railroader/processors/alias_processor.rb +1229 -0
  91. data/lib/railroader/processors/base_processor.rb +295 -0
  92. data/lib/railroader/processors/config_processor.rb +14 -0
  93. data/lib/railroader/processors/controller_alias_processor.rb +278 -0
  94. data/lib/railroader/processors/controller_processor.rb +249 -0
  95. data/lib/railroader/processors/erb_template_processor.rb +77 -0
  96. data/lib/railroader/processors/erubis_template_processor.rb +92 -0
  97. data/lib/railroader/processors/gem_processor.rb +64 -0
  98. data/lib/railroader/processors/haml_template_processor.rb +191 -0
  99. data/lib/railroader/processors/lib/basic_processor.rb +37 -0
  100. data/lib/railroader/processors/lib/call_conversion_helper.rb +90 -0
  101. data/lib/railroader/processors/lib/find_all_calls.rb +224 -0
  102. data/lib/railroader/processors/lib/find_call.rb +183 -0
  103. data/lib/railroader/processors/lib/find_return_value.rb +166 -0
  104. data/lib/railroader/processors/lib/module_helper.rb +111 -0
  105. data/lib/railroader/processors/lib/processor_helper.rb +88 -0
  106. data/lib/railroader/processors/lib/rails2_config_processor.rb +145 -0
  107. data/lib/railroader/processors/lib/rails2_route_processor.rb +313 -0
  108. data/lib/railroader/processors/lib/rails3_config_processor.rb +132 -0
  109. data/lib/railroader/processors/lib/rails3_route_processor.rb +308 -0
  110. data/lib/railroader/processors/lib/render_helper.rb +181 -0
  111. data/lib/railroader/processors/lib/render_path.rb +107 -0
  112. data/lib/railroader/processors/lib/route_helper.rb +68 -0
  113. data/lib/railroader/processors/lib/safe_call_helper.rb +16 -0
  114. data/lib/railroader/processors/library_processor.rb +74 -0
  115. data/lib/railroader/processors/model_processor.rb +91 -0
  116. data/lib/railroader/processors/output_processor.rb +144 -0
  117. data/lib/railroader/processors/route_processor.rb +17 -0
  118. data/lib/railroader/processors/slim_template_processor.rb +111 -0
  119. data/lib/railroader/processors/template_alias_processor.rb +118 -0
  120. data/lib/railroader/processors/template_processor.rb +85 -0
  121. data/lib/railroader/report/config/remediation.yml +71 -0
  122. data/lib/railroader/report/ignore/config.rb +153 -0
  123. data/lib/railroader/report/ignore/interactive.rb +362 -0
  124. data/lib/railroader/report/pager.rb +112 -0
  125. data/lib/railroader/report/renderer.rb +24 -0
  126. data/lib/railroader/report/report_base.rb +292 -0
  127. data/lib/railroader/report/report_codeclimate.rb +79 -0
  128. data/lib/railroader/report/report_csv.rb +55 -0
  129. data/lib/railroader/report/report_hash.rb +23 -0
  130. data/lib/railroader/report/report_html.rb +216 -0
  131. data/lib/railroader/report/report_json.rb +45 -0
  132. data/lib/railroader/report/report_markdown.rb +107 -0
  133. data/lib/railroader/report/report_table.rb +117 -0
  134. data/lib/railroader/report/report_tabs.rb +17 -0
  135. data/lib/railroader/report/report_text.rb +198 -0
  136. data/lib/railroader/report/templates/controller_overview.html.erb +22 -0
  137. data/lib/railroader/report/templates/controller_warnings.html.erb +21 -0
  138. data/lib/railroader/report/templates/error_overview.html.erb +29 -0
  139. data/lib/railroader/report/templates/header.html.erb +58 -0
  140. data/lib/railroader/report/templates/ignored_warnings.html.erb +25 -0
  141. data/lib/railroader/report/templates/model_warnings.html.erb +21 -0
  142. data/lib/railroader/report/templates/overview.html.erb +38 -0
  143. data/lib/railroader/report/templates/security_warnings.html.erb +23 -0
  144. data/lib/railroader/report/templates/template_overview.html.erb +21 -0
  145. data/lib/railroader/report/templates/view_warnings.html.erb +34 -0
  146. data/lib/railroader/report/templates/warning_overview.html.erb +17 -0
  147. data/lib/railroader/report.rb +88 -0
  148. data/lib/railroader/rescanner.rb +483 -0
  149. data/lib/railroader/scanner.rb +321 -0
  150. data/lib/railroader/tracker/collection.rb +93 -0
  151. data/lib/railroader/tracker/config.rb +154 -0
  152. data/lib/railroader/tracker/constants.rb +171 -0
  153. data/lib/railroader/tracker/controller.rb +161 -0
  154. data/lib/railroader/tracker/library.rb +17 -0
  155. data/lib/railroader/tracker/model.rb +90 -0
  156. data/lib/railroader/tracker/template.rb +33 -0
  157. data/lib/railroader/tracker.rb +362 -0
  158. data/lib/railroader/util.rb +503 -0
  159. data/lib/railroader/version.rb +3 -0
  160. data/lib/railroader/warning.rb +294 -0
  161. data/lib/railroader/warning_codes.rb +117 -0
  162. data/lib/railroader.rb +544 -0
  163. data/lib/ruby_parser/bm_sexp.rb +626 -0
  164. data/lib/ruby_parser/bm_sexp_processor.rb +116 -0
  165. metadata +386 -0
@@ -0,0 +1,148 @@
1
+ require 'railroader/checks/base_check'
2
+
3
+ class Railroader::CheckWeakHash < Railroader::BaseCheck
4
+ Railroader::Checks.add_optional self
5
+
6
+ @description = "Checks for use of weak hashes like MD5"
7
+
8
+ DIGEST_CALLS = [:base64digest, :digest, :hexdigest, :new]
9
+
10
+ def run_check
11
+ tracker.find_call(:targets => [:'Digest::MD5', :'Digest::SHA1', :'OpenSSL::Digest::MD5', :'OpenSSL::Digest::SHA1'], :nested => true).each do |result|
12
+ process_hash_result result
13
+ end
14
+
15
+ tracker.find_call(:target => :'Digest::HMAC', :methods => [:new, :hexdigest], :nested => true).each do |result|
16
+ process_hmac_result result
17
+ end
18
+
19
+ tracker.find_call(:targets => [:'OpenSSL::Digest::Digest', :'OpenSSL::Digest'], :method => :new).each do |result|
20
+ process_openssl_result result
21
+ end
22
+ end
23
+
24
+ def process_hash_result result
25
+ return unless original? result
26
+
27
+ input = nil
28
+ call = result[:call]
29
+
30
+ if DIGEST_CALLS.include? call.method
31
+ if input = user_input_as_arg?(call)
32
+ confidence = :high
33
+ elsif input = hashing_password?(call)
34
+ confidence = :high
35
+ else
36
+ confidence = :medium
37
+ end
38
+ else
39
+ confidence = :medium
40
+ end
41
+
42
+
43
+ alg = case call.target.last
44
+ when :MD5
45
+ " (MD5)"
46
+ when :SHA1
47
+ " (SHA1)"
48
+ else
49
+ ""
50
+ end
51
+
52
+ warn :result => result,
53
+ :warning_type => "Weak Hash",
54
+ :warning_code => :weak_hash_digest,
55
+ :message => "Weak hashing algorithm#{alg} used",
56
+ :confidence => confidence,
57
+ :user_input => input
58
+ end
59
+
60
+ def process_hmac_result result
61
+ return unless original? result
62
+
63
+ call = result[:call]
64
+
65
+ alg = case call.third_arg.last
66
+ when :MD5
67
+ 'MD5'
68
+ when :SHA1
69
+ 'SHA1'
70
+ else
71
+ return
72
+ end
73
+
74
+ warn :result => result,
75
+ :warning_type => "Weak Hash",
76
+ :warning_code => :weak_hash_hmac,
77
+ :message => "Weak hashing algorithm (#{alg}) used in HMAC",
78
+ :confidence => :medium
79
+ end
80
+
81
+ def process_openssl_result result
82
+ return unless original? result
83
+
84
+ arg = result[:call].first_arg
85
+
86
+ if string? arg
87
+ alg = arg.value.upcase
88
+
89
+ if alg == 'MD5' or alg == 'SHA1'
90
+ warn :result => result,
91
+ :warning_type => "Weak Hash",
92
+ :warning_code => :weak_hash_digest,
93
+ :message => "Weak hashing algorithm (#{alg}) used",
94
+ :confidence => :medium
95
+ end
96
+ end
97
+ end
98
+
99
+ def user_input_as_arg? call
100
+ call.each_arg do |arg|
101
+ if input = include_user_input?(arg)
102
+ return input
103
+ end
104
+ end
105
+
106
+ nil
107
+ end
108
+
109
+ def hashing_password? call
110
+ call.each_arg do |arg|
111
+ @has_password = false
112
+
113
+ process arg
114
+
115
+ if @has_password
116
+ return @has_password
117
+ end
118
+ end
119
+
120
+ nil
121
+ end
122
+
123
+ def process_call exp
124
+ if exp.method == :password
125
+ @has_password = exp
126
+ else
127
+ process_default exp
128
+ end
129
+
130
+ exp
131
+ end
132
+
133
+ def process_ivar exp
134
+ if exp.value == :@password
135
+ @has_password = exp
136
+ end
137
+
138
+ exp
139
+ end
140
+
141
+ def process_lvar exp
142
+ if exp.value == :password
143
+ @has_password = exp
144
+ end
145
+
146
+ exp
147
+ end
148
+ end
@@ -0,0 +1,80 @@
1
+ require 'railroader/checks/base_check'
2
+
3
+ #Check for bypassing mass assignment protection
4
+ #with without_protection => true
5
+ #
6
+ #Only for Rails 3.1
7
+ class Railroader::CheckWithoutProtection < Railroader::BaseCheck
8
+ Railroader::Checks.add self
9
+
10
+ @description = "Check for mass assignment using without_protection"
11
+
12
+ def run_check
13
+ if version_between? "0.0.0", "3.0.99"
14
+ return
15
+ end
16
+
17
+ return if active_record_models.empty?
18
+
19
+ Railroader.debug "Finding all mass assignments"
20
+ calls = tracker.find_call :targets => active_record_models.keys, :methods => [:new,
21
+ :attributes=,
22
+ :update_attributes,
23
+ :update_attributes!,
24
+ :create,
25
+ :create!]
26
+
27
+ Railroader.debug "Processing all mass assignments"
28
+ calls.each do |result|
29
+ process_result result
30
+ end
31
+ end
32
+
33
+ #All results should be Model.new(...) or Model.attributes=() calls
34
+ def process_result res
35
+ call = res[:call]
36
+ last_arg = call.last_arg
37
+
38
+ if hash? last_arg and not call.original_line and not duplicate? res
39
+
40
+ if value = hash_access(last_arg, :without_protection)
41
+ if true? value
42
+ add_result res
43
+
44
+ if input = include_user_input?(call.arglist)
45
+ confidence = :high
46
+ elsif all_literals? call
47
+ return
48
+ else
49
+ confidence = :medium
50
+ end
51
+
52
+ warn :result => res,
53
+ :warning_type => "Mass Assignment",
54
+ :warning_code => :mass_assign_without_protection,
55
+ :message => "Unprotected mass assignment",
56
+ :code => call,
57
+ :user_input => input,
58
+ :confidence => confidence
59
+
60
+ end
61
+ end
62
+ end
63
+ end
64
+
65
+ def all_literals? call
66
+ call.each_arg do |arg|
67
+ if hash? arg
68
+ hash_iterate arg do |k, v|
69
+ unless node_type? k, :str, :lit, :false, :true and node_type? v, :str, :lit, :false, :true
70
+ return false
71
+ end
72
+ end
73
+ else
74
+ return false
75
+ end
76
+ end
77
+
78
+ true
79
+ end
80
+ end
@@ -0,0 +1,45 @@
1
+ require 'railroader/checks/base_check'
2
+
3
+ class Railroader::CheckXMLDoS < Railroader::BaseCheck
4
+ Railroader::Checks.add self
5
+
6
+ @description = "Checks for XML denial of service (CVE-2015-3227)"
7
+
8
+ def run_check
9
+ version = rails_version
10
+
11
+ fix_version = case
12
+ when version_between?("2.0.0", "3.2.21")
13
+ "3.2.22"
14
+ when version_between?("4.1.0", "4.1.10")
15
+ "4.1.11"
16
+ when version_between?("4.2.0", "4.2.1")
17
+ "4.2.2"
18
+ when version_between?("4.0.0", "4.0.99")
19
+ "4.2.2"
20
+ else
21
+ return
22
+ end
23
+
24
+ return if has_workaround?
25
+
26
+ message = "Rails #{version} is vulnerable to denial of service via XML parsing (CVE-2015-3227). Upgrade to Rails version #{fix_version}"
27
+
28
+ warn :warning_type => "Denial of Service",
29
+ :warning_code => :CVE_2015_3227,
30
+ :message => message,
31
+ :confidence => :medium,
32
+ :gem_info => gemfile_or_environment,
33
+ :link_path => "https://groups.google.com/d/msg/rubyonrails-security/bahr2JLnxvk/x4EocXnHPp8J"
34
+ end
35
+
36
+ def has_workaround?
37
+ tracker.check_initializers(:"ActiveSupport::XmlMini", :backend=).any? do |match|
38
+ arg = match.call.first_arg
39
+ if string? arg
40
+ value = arg.value
41
+ value == 'Nokogiri' or value == 'LibXML'
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,121 @@
1
+ require 'railroader/checks/base_check'
2
+
3
+ class Railroader::CheckYAMLParsing < Railroader::BaseCheck
4
+ Railroader::Checks.add self
5
+
6
+ @description = "Checks for YAML parsing vulnerabilities (CVE-2013-0156)"
7
+
8
+ def run_check
9
+ return unless version_between? "0.0.0", "2.3.14" or
10
+ version_between? "3.0.0", "3.0.18" or
11
+ version_between? "3.1.0", "3.1.9" or
12
+ version_between? "3.2.0", "3.2.10"
13
+
14
+ unless disabled_xml_parser? or disabled_xml_dangerous_types?
15
+ new_version = if version_between? "0.0.0", "2.3.14"
16
+ "2.3.15"
17
+ elsif version_between? "3.0.0", "3.0.18"
18
+ "3.0.19"
19
+ elsif version_between? "3.1.0", "3.1.9"
20
+ "3.1.10"
21
+ elsif version_between? "3.2.0", "3.2.10"
22
+ "3.2.11"
23
+ end
24
+
25
+ message = "Rails #{rails_version} has a remote code execution vulnerability: upgrade to #{new_version} or disable XML parsing"
26
+
27
+ warn :warning_type => "Remote Code Execution",
28
+ :warning_code => :CVE_2013_0156,
29
+ :message => message,
30
+ :confidence => :high,
31
+ :gem_info => gemfile_or_environment,
32
+ :link_path => "https://groups.google.com/d/topic/rubyonrails-security/61bkgvnSGTQ/discussion"
33
+ end
34
+
35
+ #Warn if app accepts YAML
36
+ if version_between?("0.0.0", "2.3.14") and enabled_yaml_parser?
37
+ message = "Parsing YAML request parameters enables remote code execution: disable YAML parser"
38
+
39
+ warn :warning_type => "Remote Code Execution",
40
+ :warning_code => :CVE_2013_0156,
41
+ :message => message,
42
+ :confidence => :high,
43
+ :gem_info => gemfile_or_environment,
44
+ :link_path => "https://groups.google.com/d/topic/rubyonrails-security/61bkgvnSGTQ/discussion"
45
+ end
46
+ end
47
+
48
+ def disabled_xml_parser?
49
+ if version_between? "0.0.0", "2.3.14"
50
+ #Look for ActionController::Base.param_parsers.delete(Mime::XML)
51
+ params_parser = s(:call,
52
+ s(:colon2, s(:const, :ActionController), :Base),
53
+ :param_parsers)
54
+
55
+ matches = tracker.check_initializers(params_parser, :delete)
56
+ else
57
+ #Look for ActionDispatch::ParamsParser::DEFAULT_PARSERS.delete(Mime::XML)
58
+ matches = tracker.check_initializers(:"ActionDispatch::ParamsParser::DEFAULT_PARSERS", :delete)
59
+ end
60
+
61
+ unless matches.empty?
62
+ mime_xml = s(:colon2, s(:const, :Mime), :XML)
63
+
64
+ matches.each do |result|
65
+ if result.call.first_arg == mime_xml
66
+ return true
67
+ end
68
+ end
69
+ end
70
+
71
+ false
72
+ end
73
+
74
+ #Look for ActionController::Base.param_parsers[Mime::YAML] = :yaml
75
+ #in Rails 2.x apps
76
+ def enabled_yaml_parser?
77
+ param_parsers = s(:call,
78
+ s(:colon2, s(:const, :ActionController), :Base),
79
+ :param_parsers)
80
+
81
+ matches = tracker.check_initializers(param_parsers, :[]=)
82
+
83
+ mime_yaml = s(:colon2, s(:const, :Mime), :YAML)
84
+
85
+ matches.each do |result|
86
+ if result.call.first_arg == mime_yaml and
87
+ symbol? result.call.second_arg and
88
+ result.call.second_arg.value == :yaml
89
+
90
+ return true
91
+ end
92
+ end
93
+
94
+ false
95
+ end
96
+
97
+ def disabled_xml_dangerous_types?
98
+ if version_between? "0.0.0", "2.3.14"
99
+ matches = tracker.check_initializers(:"ActiveSupport::CoreExtensions::Hash::Conversions::XML_PARSING", :delete)
100
+ else
101
+ matches = tracker.check_initializers(:"ActiveSupport::XmlMini::PARSING", :delete)
102
+ end
103
+
104
+ symbols_off = false
105
+ yaml_off = false
106
+
107
+ matches.each do |result|
108
+ arg = result.call.first_arg
109
+
110
+ if string? arg
111
+ if arg.value == "yaml"
112
+ yaml_off = true
113
+ elsif arg.value == "symbol"
114
+ symbols_off = true
115
+ end
116
+ end
117
+ end
118
+
119
+ symbols_off and yaml_off
120
+ end
121
+ end
@@ -0,0 +1,209 @@
1
+ require 'thread'
2
+ require 'railroader/differ'
3
+
4
+ #Collects up results from running different checks.
5
+ #
6
+ #Checks can be added with +Check.add(check_class)+
7
+ #
8
+ #All .rb files in checks/ will be loaded.
9
+ class Railroader::Checks
10
+ @checks = []
11
+ @optional_checks = []
12
+
13
+ attr_reader :warnings, :controller_warnings, :model_warnings, :template_warnings, :checks_run
14
+
15
+ #Add a check. This will call +_klass_.new+ when running tests
16
+ def self.add klass
17
+ @checks << klass unless @checks.include? klass
18
+ end
19
+
20
+ #Add an optional check
21
+ def self.add_optional klass
22
+ @optional_checks << klass unless @checks.include? klass
23
+ end
24
+
25
+ def self.checks
26
+ @checks + @optional_checks
27
+ end
28
+
29
+ def self.optional_checks
30
+ @optional_checks
31
+ end
32
+
33
+ def self.initialize_checks check_directory = ""
34
+ #Load all files in check_directory
35
+ Dir.glob(File.join(check_directory, "*.rb")).sort.each do |f|
36
+ require f
37
+ end
38
+ end
39
+
40
+ def self.missing_checks included_checks, excluded_checks
41
+ included_checks = included_checks.map(&:to_s).to_set
42
+ excluded_checks = excluded_checks.map(&:to_s).to_set
43
+
44
+ if included_checks == Set['CheckNone']
45
+ return []
46
+ else
47
+ loaded = self.checks.map { |name| name.to_s.gsub('Railroader::', '') }.to_set
48
+ missing = (included_checks - loaded) + (excluded_checks - loaded)
49
+
50
+ unless missing.empty?
51
+ return missing
52
+ end
53
+ end
54
+
55
+ []
56
+ end
57
+
58
+ #No need to use this directly.
59
+ def initialize options = { }
60
+ if options[:min_confidence]
61
+ @min_confidence = options[:min_confidence]
62
+ else
63
+ @min_confidence = Railroader.get_defaults[:min_confidence]
64
+ end
65
+
66
+ @warnings = []
67
+ @template_warnings = []
68
+ @model_warnings = []
69
+ @controller_warnings = []
70
+ @checks_run = []
71
+ end
72
+
73
+ #Add Warning to list of warnings to report.
74
+ #Warnings are split into four different arrays
75
+ #for template, controller, model, and generic warnings.
76
+ #
77
+ #Will not add warnings which are below the minimum confidence level.
78
+ def add_warning warning
79
+ unless warning.confidence > @min_confidence
80
+ case warning.warning_set
81
+ when :template
82
+ @template_warnings << warning
83
+ when :warning
84
+ @warnings << warning
85
+ when :controller
86
+ @controller_warnings << warning
87
+ when :model
88
+ @model_warnings << warning
89
+ else
90
+ raise "Unknown warning: #{warning.warning_set}"
91
+ end
92
+ end
93
+ end
94
+
95
+ #Return a hash of arrays of new and fixed warnings
96
+ #
97
+ # diff = checks.diff old_checks
98
+ # diff[:fixed] # [...]
99
+ # diff[:new] # [...]
100
+ def diff other_checks
101
+ my_warnings = self.all_warnings
102
+ other_warnings = other_checks.all_warnings
103
+ Railroader::Differ.new(my_warnings, other_warnings).diff
104
+ end
105
+
106
+ #Return an array of all warnings found.
107
+ def all_warnings
108
+ @warnings + @template_warnings + @controller_warnings + @model_warnings
109
+ end
110
+
111
+ #Run all the checks on the given Tracker.
112
+ #Returns a new instance of Checks with the results.
113
+ def self.run_checks(app_tree, tracker)
114
+ checks = self.checks_to_run(tracker)
115
+ check_runner = self.new :min_confidence => tracker.options[:min_confidence]
116
+ self.actually_run_checks(checks, check_runner, app_tree, tracker)
117
+ end
118
+
119
+ def self.actually_run_checks(checks, check_runner, app_tree, tracker)
120
+ threads = [] # Results for parallel
121
+ results = [] # Results for sequential
122
+ parallel = tracker.options[:parallel_checks]
123
+ error_mutex = Mutex.new
124
+
125
+ checks.each do |c|
126
+ check_name = get_check_name c
127
+ Railroader.notify " - #{check_name}"
128
+
129
+ if parallel
130
+ threads << Thread.new do
131
+ self.run_a_check(c, error_mutex, app_tree, tracker)
132
+ end
133
+ else
134
+ results << self.run_a_check(c, error_mutex, app_tree, tracker)
135
+ end
136
+
137
+ #Maintain list of which checks were run
138
+ #mainly for reporting purposes
139
+ check_runner.checks_run << check_name[5..-1]
140
+ end
141
+
142
+ threads.each { |t| t.join }
143
+
144
+ Railroader.notify "Checks finished, collecting results..."
145
+
146
+ if parallel
147
+ threads.each do |thread|
148
+ thread.value.each do |warning|
149
+ check_runner.add_warning warning
150
+ end
151
+ end
152
+ else
153
+ results.each do |warnings|
154
+ warnings.each do |warning|
155
+ check_runner.add_warning warning
156
+ end
157
+ end
158
+ end
159
+
160
+ check_runner
161
+ end
162
+
163
+ private
164
+
165
+ def self.get_check_name check_class
166
+ check_class.to_s.split("::").last
167
+ end
168
+
169
+ def self.checks_to_run tracker
170
+ to_run = if tracker.options[:run_all_checks] or tracker.options[:run_checks]
171
+ @checks + @optional_checks
172
+ else
173
+ @checks
174
+ end
175
+
176
+ self.filter_checks to_run, tracker
177
+ end
178
+
179
+ def self.filter_checks checks, tracker
180
+ skipped = tracker.options[:skip_checks]
181
+ explicit = tracker.options[:run_checks]
182
+
183
+ checks.reject do |c|
184
+ check_name = self.get_check_name(c)
185
+
186
+ skipped.include? check_name or
187
+ (explicit and not explicit.include? check_name)
188
+ end
189
+ end
190
+
191
+ def self.run_a_check klass, mutex, app_tree, tracker
192
+ check = klass.new(app_tree, tracker)
193
+
194
+ begin
195
+ check.run_check
196
+ rescue => e
197
+ mutex.synchronize do
198
+ tracker.error e
199
+ end
200
+ end
201
+
202
+ check.warnings
203
+ end
204
+ end
205
+
206
+ #Load all files in checks/ directory
207
+ Dir.glob("#{File.expand_path(File.dirname(__FILE__))}/checks/*.rb").sort.each do |f|
208
+ require f.match(/(railroader\/checks\/.*)\.rb$/)[0]
209
+ end
@@ -0,0 +1,97 @@
1
+ require 'pathname'
2
+
3
+ module Railroader
4
+ module Codeclimate
5
+ class EngineConfiguration
6
+
7
+ def initialize(engine_config = {})
8
+ @engine_config = engine_config
9
+ end
10
+
11
+ def options
12
+ default_options.merge(configured_options)
13
+ end
14
+
15
+ private
16
+
17
+ attr_reader :engine_config
18
+
19
+ def default_options
20
+ @default_options = {
21
+ :output_format => :codeclimate,
22
+ :quiet => true,
23
+ :pager => false,
24
+ :app_path => Dir.pwd
25
+ }
26
+ if system("test -w /dev/stdout")
27
+ @default_options[:output_files] = ["/dev/stdout"]
28
+ end
29
+ @default_options
30
+ end
31
+
32
+ def configured_options
33
+ @configured_options = {}
34
+ # ATM this gets parsed as a string instead of bool: "config":{ "debug":"true" }
35
+ if railroader_configuration["debug"] && railroader_configuration["debug"].to_s.downcase == "true"
36
+ @configured_options[:debug] = true
37
+ @configured_options[:report_progress] = false
38
+ end
39
+
40
+ if active_include_paths
41
+ @configured_options[:only_files] = active_include_paths
42
+ end
43
+
44
+ if railroader_configuration["app_path"]
45
+ @configured_options[:path_prefix] = railroader_configuration["app_path"]
46
+ path = Pathname.new(Dir.pwd) + railroader_configuration["app_path"]
47
+ @configured_options[:app_path] = path.to_s
48
+ end
49
+ @configured_options
50
+ end
51
+
52
+ def railroader_configuration
53
+ if engine_config["config"]
54
+ engine_config["config"]
55
+ else
56
+ {}
57
+ end
58
+ end
59
+
60
+ def active_include_paths
61
+ @active_include_paths ||=
62
+ if railroader_configuration["app_path"]
63
+ stripped_include_paths(railroader_configuration["app_path"])
64
+ else
65
+ engine_config["include_paths"] && engine_config["include_paths"].compact
66
+ end
67
+ end
68
+
69
+ def stripped_include_paths(prefix)
70
+ subprefixes = path_subprefixes(prefix)
71
+ engine_config["include_paths"] && engine_config["include_paths"].map do |path|
72
+ next unless path
73
+ stripped_include_path(prefix, subprefixes, path)
74
+ end.compact
75
+ end
76
+
77
+ def path_subprefixes(path)
78
+ Pathname.new(path).each_filename.inject([]) do |memo, piece|
79
+ memo <<
80
+ if memo.any?
81
+ File.join(memo.last, piece)
82
+ else
83
+ File.join(piece)
84
+ end
85
+ end
86
+ end
87
+
88
+ def stripped_include_path(prefix, subprefixes, path)
89
+ if path.start_with?(prefix)
90
+ path.sub(%r{^#{prefix}/?}, "/")
91
+ elsif subprefixes.any? { |subprefix| path =~ %r{^#{subprefix}/?$} }
92
+ "/"
93
+ end
94
+ end
95
+ end
96
+ end
97
+ end