codez-tarantula 0.5.0

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 (88) hide show
  1. data/.autotest +14 -0
  2. data/.gitignore +12 -0
  3. data/.travis.yml +7 -0
  4. data/CHANGELOG +64 -0
  5. data/DSL_EXAMPLES.md +120 -0
  6. data/Gemfile +2 -0
  7. data/LICENSE +20 -0
  8. data/README.rdoc +136 -0
  9. data/Rakefile +36 -0
  10. data/ci/rails2.gemfile +4 -0
  11. data/ci/rails3.gemfile +4 -0
  12. data/laf/images/header_bg.jpg +0 -0
  13. data/laf/images/logo.png +0 -0
  14. data/laf/images/tagline.png +0 -0
  15. data/laf/javascripts/jquery-1.2.3.js +3408 -0
  16. data/laf/javascripts/jquery-ui-tabs.js +890 -0
  17. data/laf/javascripts/jquery.tablesorter.js +861 -0
  18. data/laf/javascripts/tarantula.js +10 -0
  19. data/laf/stylesheets/tarantula.css +346 -0
  20. data/lib/relevance/core_extensions/ellipsize.rb +38 -0
  21. data/lib/relevance/core_extensions/file.rb +15 -0
  22. data/lib/relevance/core_extensions/metaclass.rb +78 -0
  23. data/lib/relevance/core_extensions/response.rb +14 -0
  24. data/lib/relevance/core_extensions/test_case.rb +21 -0
  25. data/lib/relevance/tarantula.rb +55 -0
  26. data/lib/relevance/tarantula/attack.rb +22 -0
  27. data/lib/relevance/tarantula/attack_handler.rb +43 -0
  28. data/lib/relevance/tarantula/basic_attack.rb +44 -0
  29. data/lib/relevance/tarantula/crawler.rb +271 -0
  30. data/lib/relevance/tarantula/detail.html.erb +81 -0
  31. data/lib/relevance/tarantula/form.rb +29 -0
  32. data/lib/relevance/tarantula/form_submission.rb +98 -0
  33. data/lib/relevance/tarantula/html_document_handler.rb +42 -0
  34. data/lib/relevance/tarantula/html_report_helper.rb +46 -0
  35. data/lib/relevance/tarantula/html_reporter.rb +111 -0
  36. data/lib/relevance/tarantula/index.html.erb +37 -0
  37. data/lib/relevance/tarantula/invalid_html_handler.rb +27 -0
  38. data/lib/relevance/tarantula/io_reporter.rb +40 -0
  39. data/lib/relevance/tarantula/link.rb +105 -0
  40. data/lib/relevance/tarantula/log_grabber.rb +22 -0
  41. data/lib/relevance/tarantula/rails_integration_proxy.rb +90 -0
  42. data/lib/relevance/tarantula/recording.rb +12 -0
  43. data/lib/relevance/tarantula/response.rb +19 -0
  44. data/lib/relevance/tarantula/result.rb +83 -0
  45. data/lib/relevance/tarantula/test_report.html.erb +32 -0
  46. data/lib/relevance/tarantula/tidy_handler.rb +35 -0
  47. data/lib/relevance/tarantula/transform.rb +21 -0
  48. data/lib/relevance/tarantula/version.rb +5 -0
  49. data/lib/relevance/tasks/tarantula_tasks.rake +42 -0
  50. data/lib/tarantula-rails3.rb +9 -0
  51. data/spec/relevance/core_extensions/ellipsize_spec.rb +19 -0
  52. data/spec/relevance/core_extensions/file_spec.rb +7 -0
  53. data/spec/relevance/core_extensions/response_spec.rb +48 -0
  54. data/spec/relevance/core_extensions/test_case_spec.rb +19 -0
  55. data/spec/relevance/tarantula/attack_handler_spec.rb +29 -0
  56. data/spec/relevance/tarantula/basic_attack_spec.rb +12 -0
  57. data/spec/relevance/tarantula/crawler_spec.rb +409 -0
  58. data/spec/relevance/tarantula/form_spec.rb +50 -0
  59. data/spec/relevance/tarantula/form_submission_spec.rb +171 -0
  60. data/spec/relevance/tarantula/html_document_handler_spec.rb +43 -0
  61. data/spec/relevance/tarantula/html_report_helper_spec.rb +46 -0
  62. data/spec/relevance/tarantula/html_reporter_spec.rb +82 -0
  63. data/spec/relevance/tarantula/invalid_html_handler_spec.rb +33 -0
  64. data/spec/relevance/tarantula/io_reporter_spec.rb +11 -0
  65. data/spec/relevance/tarantula/link_spec.rb +132 -0
  66. data/spec/relevance/tarantula/log_grabber_spec.rb +26 -0
  67. data/spec/relevance/tarantula/rails_integration_proxy_spec.rb +100 -0
  68. data/spec/relevance/tarantula/result_spec.rb +85 -0
  69. data/spec/relevance/tarantula/tidy_handler_spec.rb +58 -0
  70. data/spec/relevance/tarantula/transform_spec.rb +20 -0
  71. data/spec/relevance/tarantula_spec.rb +23 -0
  72. data/spec/spec_helper.rb +43 -0
  73. data/tarantula.gemspec +25 -0
  74. data/template/tarantula_test.rb +22 -0
  75. data/vendor/xss-shield/MIT-LICENSE +20 -0
  76. data/vendor/xss-shield/README +76 -0
  77. data/vendor/xss-shield/init.rb +16 -0
  78. data/vendor/xss-shield/lib/xss_shield.rb +6 -0
  79. data/vendor/xss-shield/lib/xss_shield/erb_hacks.rb +111 -0
  80. data/vendor/xss-shield/lib/xss_shield/haml_hacks.rb +42 -0
  81. data/vendor/xss-shield/lib/xss_shield/safe_string.rb +47 -0
  82. data/vendor/xss-shield/lib/xss_shield/secure_helpers.rb +40 -0
  83. data/vendor/xss-shield/test/test_actionview_integration.rb +40 -0
  84. data/vendor/xss-shield/test/test_erb.rb +44 -0
  85. data/vendor/xss-shield/test/test_haml.rb +43 -0
  86. data/vendor/xss-shield/test/test_helpers.rb +25 -0
  87. data/vendor/xss-shield/test/test_safe_string.rb +55 -0
  88. metadata +247 -0
@@ -0,0 +1,27 @@
1
+ module Relevance
2
+ module Tarantula
3
+
4
+ class InvalidHtmlHandler
5
+ include Relevance::Tarantula
6
+ def handle(result)
7
+ response = result.response
8
+ unless response.html?
9
+ log "Skipping #{self.class} on url: #{result.url} because response is not html."
10
+ return
11
+ end
12
+ begin
13
+ body = HTML::Document.new(response.body, true)
14
+ rescue Exception => e
15
+ error_result = result.dup
16
+ error_result.success = false
17
+ error_result.description = "Bad HTML (Scanner)"
18
+ error_result.data = e.message
19
+ error_result
20
+ else
21
+ nil
22
+ end
23
+ end
24
+ end
25
+
26
+ end
27
+ end
@@ -0,0 +1,40 @@
1
+ module Relevance
2
+ module Tarantula
3
+
4
+ class IOReporter
5
+
6
+ include Relevance::Tarantula
7
+ attr_accessor :io, :results
8
+ delegate :successes, :failures, :to => :results
9
+
10
+ IOResultOverview = Struct.new(:code, :url)
11
+
12
+ def initialize(io)
13
+ @io = io
14
+ @results = Struct.new(:successes, :failures).new([], [])
15
+ end
16
+
17
+ def report(result)
18
+ return if result.nil?
19
+
20
+ unless result.success # collection = result.success ? results.successes : results.failures
21
+ results.failures << IOResultOverview.new(
22
+ result.code, result.url
23
+ )
24
+ end
25
+ end
26
+
27
+ def finish_report(test_name)
28
+ unless (failures).empty?
29
+ io.puts "****** FAILURES"
30
+ failures.each do |failure|
31
+ io.puts "#{failure.code}: #{failure.url}"
32
+ end
33
+ raise "#{failures.size} failures"
34
+ end
35
+ end
36
+
37
+ end
38
+
39
+ end
40
+ end
@@ -0,0 +1,105 @@
1
+ module Relevance
2
+ module Tarantula
3
+
4
+ class Link
5
+ include Relevance::Tarantula
6
+
7
+ class << self
8
+ include ActionView::Helpers::UrlHelper
9
+ # method_javascript_function needs this method
10
+ def protect_against_forgery?
11
+ false
12
+ end
13
+ #fast fix for rails3
14
+ def method_javascript_function(method, url = '', href = nil)
15
+ action = (href && url.size > 0) ? "'#{url}'" : 'this.href'
16
+ submit_function =
17
+ "var f = document.createElement('form'); f.style.display = 'none'; " +
18
+ "this.parentNode.appendChild(f); f.method = 'POST'; f.action = #{action};"
19
+
20
+ unless method == 'post'
21
+ submit_function << "var m = document.createElement('input'); m.setAttribute('type', 'hidden'); "
22
+ submit_function << "m.setAttribute('name', '_method'); m.setAttribute('value', '#{method}'); f.appendChild(m);"
23
+ end
24
+
25
+ if protect_against_forgery?
26
+ submit_function << "var s = document.createElement('input'); s.setAttribute('type', 'hidden'); "
27
+ submit_function << "s.setAttribute('name', '#{request_forgery_protection_token}'); s.setAttribute('value', '#{escape_javascript form_authenticity_token}'); f.appendChild(s);"
28
+ end
29
+ submit_function << "f.submit();"
30
+ end
31
+ end
32
+
33
+ METHOD_REGEXPS = {}
34
+ %w(put delete post patch).each do |m|
35
+ # remove submit from the end so we'll match with or without forgery protection
36
+ s = method_javascript_function(m).gsub( /f.submit();/, "" )
37
+ # don't just match this.href in case a different url was passed originally
38
+ s = Regexp.escape(s).gsub( /this.href/, ".*" )
39
+ METHOD_REGEXPS[m] = /#{s}/
40
+ end
41
+
42
+ attr_accessor :href, :crawler, :referrer
43
+
44
+ def initialize(link, crawler, referrer)
45
+ @crawler, @referrer = crawler, referrer
46
+
47
+ if String === link || link.nil?
48
+ @href = transform_url(link)
49
+ @method = 'get'
50
+ else # should be a tag
51
+ @href = link['href'] ? transform_url(link['href']) : nil
52
+ @tag = link
53
+ end
54
+ end
55
+
56
+ def crawl
57
+ response = crawler.follow(meth, href)
58
+ log "Response #{response.code} for #{self}"
59
+ crawler.handle_link_results(self, make_result(response))
60
+ end
61
+
62
+ def make_result(response)
63
+ crawler.make_result(:method => meth,
64
+ :url => href,
65
+ :response => response,
66
+ :referrer => referrer)
67
+ end
68
+
69
+ def meth
70
+ @meth ||= begin
71
+ (@tag &&
72
+ %w(put delete post patch).detect do |m| # post should be last since it's least specific
73
+ @tag['onclick'] =~ METHOD_REGEXPS[m] ||
74
+ @tag['data-method'] == m
75
+ end) ||
76
+ 'get'
77
+ end
78
+ end
79
+
80
+ def url
81
+ href
82
+ end
83
+
84
+ def transform_url(link)
85
+ crawler.transform_url(link)
86
+ end
87
+
88
+ def ==(obj)
89
+ obj.respond_to?(:href) && obj.respond_to?(:method) &&
90
+ self.href.to_s == obj.href.to_s && self.meth.to_s == obj.meth.to_s
91
+ end
92
+ alias :eql? :==
93
+
94
+ def hash
95
+ to_s.hash
96
+ end
97
+
98
+ def to_s
99
+ "<Relevance::Tarantula::Link href=#{href}, method=#{meth}>"
100
+ end
101
+
102
+ end
103
+
104
+ end
105
+ end
@@ -0,0 +1,22 @@
1
+ module Relevance
2
+ module Tarantula
3
+
4
+ class LogGrabber
5
+ attr_accessor :path
6
+ def initialize(path)
7
+ @path = path
8
+ end
9
+
10
+ def clear!
11
+ File.open(@path, "w")
12
+ end
13
+
14
+ def grab!
15
+ File.read(@path)
16
+ ensure
17
+ clear!
18
+ end
19
+ end
20
+
21
+ end
22
+ end
@@ -0,0 +1,90 @@
1
+ require 'test/unit'
2
+
3
+ module Relevance
4
+ module Tarantula
5
+
6
+ class RailsIntegrationProxy
7
+ include Relevance::Tarantula
8
+ extend Relevance::Tarantula
9
+ extend Forwardable
10
+ attr_accessor :integration_test
11
+
12
+ def self.rails_integration_test(integration_test, options = {})
13
+ t = Crawler.new
14
+ t.max_url_length = options[:max_url_length] if options[:max_url_length]
15
+ t.proxy = RailsIntegrationProxy.new(integration_test)
16
+ t.handlers << HtmlDocumentHandler.new(t)
17
+ t.handlers << InvalidHtmlHandler.new
18
+ t.log_grabber = Relevance::Tarantula::LogGrabber.new(File.join(rails_root, "log/test.log"))
19
+ t.skip_uri_patterns << /logout$/
20
+ t.transform_url_patterns += [
21
+ [/\?\d+$/, ''], # strip trailing numbers for assets
22
+ [/^http:\/\/#{integration_test.host}/, ''] # strip full path down to relative
23
+ ]
24
+ t.test_name = t.proxy.integration_test.method_name
25
+ t.reporters << Relevance::Tarantula::HtmlReporter.new(t.report_dir)
26
+ t
27
+ end
28
+
29
+ def initialize(integration_test)
30
+ @integration_test = integration_test
31
+ @integration_test.meta.attr_accessor :response
32
+ end
33
+
34
+ [:get, :post, :put, :delete].each do |verb|
35
+ define_method(verb) do |url, *args|
36
+ response = nil
37
+ begin
38
+ integration_test.send(verb, url, *args)
39
+ response = integration_test.response
40
+ rescue ActiveRecord::RecordNotFound,
41
+ ActionController::RoutingError => e
42
+ response = integration_test.response
43
+ alter_response(response, '404', e.message + "\n\n" + e.backtrace.join("\n"))
44
+ rescue Exception => e
45
+ response = integration_test.response
46
+ alter_response(response, '500', e.message + "\n\n" + e.backtrace.join("\n"))
47
+ end
48
+ patch_response(url, response)
49
+ response
50
+ end
51
+ end
52
+
53
+ def alter_response(response, code, body)
54
+ response.meta.attr_accessor :code
55
+ response.code = code
56
+ response.body = body
57
+ response
58
+ end
59
+
60
+ def patch_response(url, response)
61
+ if response.code == '404'
62
+ if File.exist?(static_content_path(url))
63
+ case ext = File.extension(url)
64
+ when /html|te?xt|css|js|jpe?g|gif|psd|png|eps|pdf|ico/
65
+ response.headers["type"] = "text/#{ext}" # readable as response.content_type
66
+ alter_response(response, '200', static_content_file(url))
67
+ else
68
+ log "Skipping unknown type #{url}"
69
+ end
70
+ end
71
+ end
72
+ # don't count on metaclass taking block, e.g.
73
+ # http://relevancellc.com/2008/2/12/how-should-metaclass-work
74
+ response.metaclass.class_eval do
75
+ include Relevance::CoreExtensions::Response
76
+ end
77
+ end
78
+
79
+
80
+ def static_content_file(url)
81
+ File.read(static_content_path(url))
82
+ end
83
+
84
+ def static_content_path(url)
85
+ File.expand_path(File.join(rails_root, "public", url))
86
+ end
87
+ end
88
+
89
+ end
90
+ end
@@ -0,0 +1,12 @@
1
+ module Recording
2
+ def self.stderr
3
+ $stderr = recorder = StringIO.new
4
+ begin
5
+ yield
6
+ ensure
7
+ $stderr = STDERR
8
+ end
9
+ recorder.rewind
10
+ recorder.read
11
+ end
12
+ end
@@ -0,0 +1,19 @@
1
+ # Used to create a stub response when we didn't get back a real response
2
+ module Relevance
3
+ module Tarantula
4
+
5
+ class Response
6
+ HASHABLE_ATTRS = [:code, :body, :content_type]
7
+ attr_accessor *HASHABLE_ATTRS
8
+
9
+ def initialize(hash)
10
+ hash.each do |k,v|
11
+ raise ArgumentError, k unless HASHABLE_ATTRS.member?(k)
12
+ self.instance_variable_set("@#{k}", v)
13
+ end
14
+ end
15
+
16
+ end
17
+
18
+ end
19
+ end
@@ -0,0 +1,83 @@
1
+ module Relevance
2
+ module Tarantula
3
+
4
+ class Result
5
+ HASHABLE_ATTRS = [:success, :method, :url, :response, :referrer, :data, :description, :log, :test_name]
6
+ DEFAULT_LOCALHOST = "http://localhost:3000"
7
+ attr_accessor *HASHABLE_ATTRS
8
+ include Relevance::Tarantula
9
+ include Relevance::Tarantula::HtmlReportHelper
10
+
11
+ def initialize(hash)
12
+ hash.each do |k,v|
13
+ raise ArgumentError, k unless HASHABLE_ATTRS.member?(k)
14
+ self.instance_variable_set("@#{k}", v)
15
+ end
16
+ end
17
+
18
+ def short_description
19
+ [method,url].join(" ")
20
+ end
21
+
22
+ def sequence_number
23
+ @sequence_number ||= (self.class.next_number += 1)
24
+ end
25
+
26
+ def file_name
27
+ "#{sequence_number}.html"
28
+ end
29
+
30
+ def code
31
+ response && response.code
32
+ end
33
+
34
+ def body
35
+ response && response.body
36
+ end
37
+
38
+ def full_url
39
+ "#{DEFAULT_LOCALHOST}#{url}"
40
+ end
41
+
42
+ ALLOW_NNN_FOR = /^allow_(\d\d\d)_for$/
43
+
44
+ class << self
45
+ attr_accessor :next_number
46
+
47
+ def handle(result)
48
+ retval = result.dup
49
+ retval.success = successful?(result.response) || can_skip_error?(result)
50
+ retval.description = "Bad HTTP Response" unless retval.success
51
+ retval
52
+ end
53
+
54
+ def success_codes
55
+ %w{200 201 302 401}
56
+ end
57
+
58
+ # allow_errors_for is a hash
59
+ # k=error code,
60
+ # v=array of matchers for urls that can skip said error
61
+ attr_accessor :allow_errors_for
62
+ def can_skip_error?(result)
63
+ coll = allow_errors_for[result.code]
64
+ return false unless coll
65
+ coll.any? {|item| item === result.url}
66
+ end
67
+
68
+ def successful?(response)
69
+ success_codes.member?(response.code)
70
+ end
71
+
72
+ def method_missing(meth, *args)
73
+ super unless ALLOW_NNN_FOR =~ meth.to_s
74
+ (allow_errors_for[$1] ||= []).push(*args)
75
+ end
76
+ end
77
+
78
+ self.allow_errors_for = {}
79
+ self.next_number = 0
80
+ end
81
+
82
+ end
83
+ end
@@ -0,0 +1,32 @@
1
+ <div id="<%= test_name %>">
2
+ <% %w{failures successes}.each do |result_type| %>
3
+ <table class="list tablesorter" cellspacing="0">
4
+ <caption><%= send(result_type).size %> <%= result_type.capitalize %></caption>
5
+ <thead>
6
+ <tr>
7
+ <th class="sort asc"><span>URL</span><span class="sort">&nbsp;</span></th>
8
+ <th><span>Action</span><span class="sort">&nbsp;</span></th>
9
+ <th><span>Response</span><span class="sort">&nbsp;</span></th>
10
+ <th class="left"><span>Description</span><span class="sort">&nbsp;</span></th>
11
+ <th><span>Referrer</span><span class="sort">&nbsp;</span></th>
12
+ </tr>
13
+ </thead>
14
+ <tfoot>
15
+ <tr><td colspan="5">&nbsp;</td></tr>
16
+ </tfoot>
17
+
18
+ <tbody>
19
+ <% send(result_type).sort{|x,y| y.code.to_s <=> x.code.to_s}.each_with_index do |result,i| %>
20
+ <tr class="<%= (i%2 == 0) ? 'even' : 'odd' %>">
21
+ <td class="left"><a href="<%= "#{test_name}/#{result.file_name}" %>"><%= result.url.ellipsize(50) %></a></td>
22
+ <td class="method"><%= result.method.to_s.upcase %></td> <!-- TODO Clean up demeter violation -->
23
+ <td><span class="<%= class_for_code(result.code) %>"><%= result.code %></span></td>
24
+ <td class="left"><%= result.description %></td>
25
+ <td class="left"><%= result.referrer.ellipsize(30) %></td>
26
+ </tr>
27
+ <% end %>
28
+ </tbody>
29
+ </table>
30
+ <br/>
31
+ <% end %>
32
+ </div>
@@ -0,0 +1,35 @@
1
+ begin
2
+ require 'tidy'
3
+ rescue LoadError
4
+ puts "Tidy gem not available -- 'gem install tidy' to get it."
5
+ end
6
+
7
+ if defined? Tidy
8
+ Tidy.path = ENV['TIDY_PATH'] if ENV['TIDY_PATH']
9
+
10
+ module Relevance
11
+ module Tarantula
12
+
13
+ class TidyHandler
14
+ include Relevance::Tarantula
15
+ def initialize(options = {})
16
+ @options = {:show_warnings=>true}.merge(options)
17
+ end
18
+ def handle(result)
19
+ response = result.response
20
+ return unless response.html?
21
+ tidy = Tidy.open(@options) do |tidy|
22
+ xml = tidy.clean(response.body)
23
+ tidy
24
+ end
25
+ unless tidy.errors.blank?
26
+ error_result = result.dup
27
+ error_result.description = "Bad HTML (Tidy)"
28
+ error_result.data = tidy.errors.inspect
29
+ error_result
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end