site-inspector 1.0.2 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +7 -0
  3. data/.ruby-version +1 -0
  4. data/.travis.yml +6 -0
  5. data/Gemfile +3 -0
  6. data/Guardfile +8 -0
  7. data/README.md +175 -0
  8. data/Rakefile +8 -0
  9. data/bin/site-inspector +48 -21
  10. data/lib/site-inspector.rb +38 -613
  11. data/lib/site-inspector/cache.rb +9 -52
  12. data/lib/site-inspector/checks/check.rb +41 -0
  13. data/lib/site-inspector/checks/content.rb +67 -0
  14. data/lib/site-inspector/checks/dns.rb +129 -0
  15. data/lib/site-inspector/checks/headers.rb +83 -0
  16. data/lib/site-inspector/checks/hsts.rb +78 -0
  17. data/lib/site-inspector/checks/https.rb +40 -0
  18. data/lib/site-inspector/checks/sniffer.rb +42 -0
  19. data/lib/site-inspector/disk_cache.rb +38 -0
  20. data/lib/site-inspector/domain.rb +248 -0
  21. data/lib/site-inspector/endpoint.rb +200 -0
  22. data/lib/site-inspector/rails_cache.rb +11 -0
  23. data/lib/site-inspector/version.rb +3 -0
  24. data/script/bootstrap +1 -0
  25. data/script/cibuild +7 -0
  26. data/script/console +1 -0
  27. data/script/release +38 -0
  28. data/site-inspector.gemspec +33 -0
  29. data/spec/checks/site_inspector_endpoint_check_spec.rb +34 -0
  30. data/spec/checks/site_inspector_endpoint_content_spec.rb +89 -0
  31. data/spec/checks/site_inspector_endpoint_dns_spec.rb +167 -0
  32. data/spec/checks/site_inspector_endpoint_headers_spec.rb +74 -0
  33. data/spec/checks/site_inspector_endpoint_hsts_spec.rb +91 -0
  34. data/spec/checks/site_inspector_endpoint_https_spec.rb +48 -0
  35. data/spec/checks/site_inspector_endpoint_sniffer_spec.rb +52 -0
  36. data/spec/site_inspector_cache_spec.rb +13 -0
  37. data/spec/site_inspector_disc_cache_spec.rb +31 -0
  38. data/spec/site_inspector_domain_spec.rb +252 -0
  39. data/spec/site_inspector_endpoint_spec.rb +224 -0
  40. data/spec/site_inspector_spec.rb +46 -0
  41. data/spec/spec_helper.rb +17 -0
  42. metadata +75 -57
  43. data/lib/site-inspector/compliance.rb +0 -19
  44. data/lib/site-inspector/dns.rb +0 -92
  45. data/lib/site-inspector/headers.rb +0 -59
  46. data/lib/site-inspector/sniffer.rb +0 -26
@@ -0,0 +1,91 @@
1
+ require 'spec_helper'
2
+
3
+ describe SiteInspector::Endpoint::Hsts do
4
+
5
+ subject do
6
+ headers = { "strict-transport-security" => "max-age=31536000; includeSubDomains;" }
7
+ stub_request(:get, "http://example.com/").
8
+ to_return(:status => 200, :headers => headers )
9
+ endpoint = SiteInspector::Endpoint.new("http://example.com")
10
+ SiteInspector::Endpoint::Hsts.new(endpoint)
11
+ end
12
+
13
+ def stub_header(value)
14
+ allow(subject).to receive(:header) { value }
15
+ end
16
+
17
+ it "returns the headers" do
18
+ expect(subject.send(:headers).class).to eql(SiteInspector::Endpoint::Headers)
19
+ end
20
+
21
+ it "returns the HSTS header" do
22
+ expect(subject.send(:header)).to eql("max-age=31536000; includeSubDomains;")
23
+ end
24
+
25
+ it "it parses the directives" do
26
+ expect(subject.send(:directives).count).to eql(2)
27
+ expect(subject.send(:directives).first).to eql("max-age=31536000")
28
+ expect(subject.send(:directives).last).to eql("includeSubDomains")
29
+ end
30
+
31
+ it "parses pairs" do
32
+ expect(subject.send(:pairs).keys).to include(:"max-age")
33
+ expect(subject.send(:pairs)[:"max-age"]).to eql("31536000")
34
+ end
35
+
36
+ it "knows if the header is valid" do
37
+ expect(subject.valid?).to eql(true)
38
+
39
+ allow(subject).to receive(:pairs) { ["fo o" => "bar"] }
40
+ expect(subject.valid?).to eql(false)
41
+
42
+ allow(subject).to receive(:pairs) { ["fo'o" => "bar"] }
43
+ expect(subject.valid?).to eql(false)
44
+ end
45
+
46
+ it "knows the max age" do
47
+ expect(subject.max_age).to eql(31536000)
48
+ end
49
+
50
+ it "knows if subdomains are included" do
51
+ expect(subject.include_subdomains?).to eql(true)
52
+ allow(subject).to receive(:pairs) { {:foo => "bar"} }
53
+ expect(subject.include_subdomains?).to eql(false)
54
+ end
55
+
56
+ it "knows if it's preloaded" do
57
+ expect(subject.preload?).to eql(false)
58
+ allow(subject).to receive(:pairs) { {:preload => nil } }
59
+ expect(subject.preload?).to eql(true)
60
+ end
61
+
62
+ it "knows if it's enabled" do
63
+ expect(subject.enabled?).to eql(true)
64
+
65
+ allow(subject).to receive(:pairs) { {:"max-age" => 0 } }
66
+ expect(subject.preload?).to eql(false)
67
+
68
+ allow(subject).to receive(:pairs) { {:foo => "bar" } }
69
+ expect(subject.preload?).to eql(false)
70
+ end
71
+
72
+ it "knows if it's preload ready" do
73
+ expect(subject.preload_ready?).to eql(false)
74
+
75
+ pairs = {:"max-age" => 10886401, :preload => nil, :includesubdomains => nil }
76
+ allow(subject).to receive(:pairs) { pairs }
77
+ expect(subject.preload_ready?).to eql(true)
78
+
79
+ pairs = {:"max-age" => 10886401, :includesubdomains => nil }
80
+ allow(subject).to receive(:pairs) { pairs }
81
+ expect(subject.preload_ready?).to eql(false)
82
+
83
+ pairs = {:"max-age" => 10886401, :preload => nil, :includesubdomains => nil }
84
+ allow(subject).to receive(:pairs) { pairs }
85
+ expect(subject.preload_ready?).to eql(true)
86
+
87
+ pairs = {:"max-age" => 5, :preload => nil, :includesubdomains => nil }
88
+ allow(subject).to receive(:pairs) { pairs }
89
+ expect(subject.preload_ready?).to eql(false)
90
+ end
91
+ end
@@ -0,0 +1,48 @@
1
+ require 'spec_helper'
2
+
3
+ describe SiteInspector::Endpoint::Https do
4
+
5
+ subject do
6
+ stub_request(:get, "https://example.com/").
7
+ to_return(:status => 200 )
8
+ endpoint = SiteInspector::Endpoint.new("https://example.com")
9
+ allow(endpoint.response).to receive(:return_code) { :ok }
10
+ SiteInspector::Endpoint::Https.new(endpoint)
11
+ end
12
+
13
+ it "knows the scheme" do
14
+ expect(subject.send(:scheme)).to eql("https")
15
+ end
16
+
17
+ it "knows if the scheme is https" do
18
+ expect(subject.scheme?).to eql(true)
19
+ allow(subject).to receive(:scheme) { "http" }
20
+ expect(subject.scheme?).to eql(false)
21
+ end
22
+
23
+ it "knows if it's valid" do
24
+ expect(subject.valid?).to eql(true)
25
+ end
26
+
27
+ it "knows when there's a bad chain" do
28
+ expect(subject.bad_chain?).to eql(false)
29
+
30
+ url = Addressable::URI.parse("https://example.com")
31
+ response = Typhoeus::Response.new(:return_code => :ssl_cacert)
32
+ response.request = Typhoeus::Request.new(url)
33
+
34
+ allow(subject).to receive(:response) { response }
35
+ expect(subject.bad_chain?).to eql(true)
36
+ end
37
+
38
+ it "knows when there's a bad name" do
39
+ expect(subject.bad_name?).to eql(false)
40
+
41
+ url = Addressable::URI.parse("https://example.com")
42
+ response = Typhoeus::Response.new(:return_code => :peer_failed_verification)
43
+ response.request = Typhoeus::Request.new(url)
44
+
45
+ allow(subject).to receive(:response) { response }
46
+ expect(subject.bad_name?).to eql(true)
47
+ end
48
+ end
@@ -0,0 +1,52 @@
1
+ require 'spec_helper'
2
+
3
+ describe SiteInspector::Endpoint::Sniffer do
4
+
5
+ subject do
6
+ body = <<-eos
7
+ <html>
8
+ <head>
9
+ <link rel='stylesheet' href='/wp-content/themes/foo/style.css type='text/css' media='all' />
10
+ </head>
11
+ <body>
12
+ <h1>Some page</h1>
13
+ <script>
14
+ jQuery(); googletag.pubads();
15
+ </script>
16
+ <script>
17
+ var _gaq=[['_setAccount','UA-12345678-1'],['_trackPageview']];
18
+ (function(d,t){var g=d.createElement(t),s=d.getElementsByTagName(t)[0];
19
+ g.src=('https:'==location.protocol?'//ssl':'//www')+'.google-analytics.com/ga.js';
20
+ s.parentNode.insertBefore(g,s)}(document,'script'));
21
+ </script>
22
+ </body>
23
+ </html>
24
+ eos
25
+
26
+ stub_request(:get, "http://example.com/").
27
+ to_return(:status => 200, :body => body )
28
+ endpoint = SiteInspector::Endpoint.new("http://example.com")
29
+ SiteInspector::Endpoint::Sniffer.new(endpoint)
30
+ end
31
+
32
+ it "sniffs" do
33
+ sniff = subject.send(:sniff, :cms)
34
+ expect(sniff.keys.first).to eql(:wordpress)
35
+ end
36
+
37
+ it "detects the CMS" do
38
+ expect(subject.cms.keys.first).to eql(:wordpress)
39
+ end
40
+
41
+ it "detects the analytics" do
42
+ expect(subject.analytics.keys.first).to eql(:google_analytics)
43
+ end
44
+
45
+ it "detects javascript" do
46
+ expect(subject.javascript.keys.first).to eql(:jquery)
47
+ end
48
+
49
+ it "detects advertising" do
50
+ expect(subject.advertising.keys.first).to eql(:adsense)
51
+ end
52
+ end
@@ -0,0 +1,13 @@
1
+ require 'spec_helper'
2
+
3
+ describe SiteInspector::Cache do
4
+ it "stores a cache value" do
5
+ subject.set "foo", "bar"
6
+ expect(subject.instance_variable_get("@memory")["foo"]).to eql("bar")
7
+ end
8
+
9
+ it "retrieves values from the cache" do
10
+ subject.instance_variable_set("@memory", {"foo" => "bar"})
11
+ expect(subject.get("foo")).to eql("bar")
12
+ end
13
+ end
@@ -0,0 +1,31 @@
1
+ require 'spec_helper'
2
+
3
+ describe SiteInspector::DiskCache do
4
+ subject { SiteInspector::DiskCache.new(tmpdir) }
5
+
6
+ before do
7
+ FileUtils.rm_rf(tmpdir)
8
+ Dir.mkdir(tmpdir)
9
+ end
10
+
11
+ it "should write a value to disk" do
12
+ path = File.expand_path "foo", tmpdir
13
+ expect(File.exists?(path)).to eql(false)
14
+
15
+ subject.set "foo", "bar"
16
+
17
+ expect(File.exists?(path)).to eql(true)
18
+ expect(File.open(path).read).to eql("I\"bar:ET")
19
+ end
20
+
21
+ it "should read a value from disk" do
22
+ path = File.expand_path "foo", tmpdir
23
+ File.write(path, "I\"bar:ET")
24
+ expect(subject.get("foo")).to eql("bar")
25
+ end
26
+
27
+ it "should calculate a file's path" do
28
+ path = File.expand_path "foo", tmpdir
29
+ expect(subject.send(:path, "foo")).to eql(path)
30
+ end
31
+ end
@@ -0,0 +1,252 @@
1
+ require 'spec_helper'
2
+
3
+ describe SiteInspector::Domain do
4
+
5
+ subject { SiteInspector::Domain.new("example.com") }
6
+
7
+ context "domain parsing" do
8
+ it "downcases the domain" do
9
+ domain = SiteInspector::Domain.new("EXAMPLE.com")
10
+ expect(domain.host).to eql("example.com")
11
+ end
12
+
13
+ it "strips http from the domain" do
14
+ domain = SiteInspector::Domain.new("http://example.com")
15
+ expect(domain.host).to eql("example.com")
16
+ end
17
+
18
+ it "strips https from the domain" do
19
+ domain = SiteInspector::Domain.new("https://example.com")
20
+ expect(domain.host).to eql("example.com")
21
+ end
22
+
23
+ it "strips www from the domain" do
24
+ domain = SiteInspector::Domain.new("www.example.com")
25
+ expect(domain.host).to eql("example.com")
26
+ end
27
+
28
+ it "strips http://www from the domain" do
29
+ domain = SiteInspector::Domain.new("http://www.example.com")
30
+ expect(domain.host).to eql("example.com")
31
+ end
32
+
33
+ it "strips paths from the domain" do
34
+ domain = SiteInspector::Domain.new("http://www.example.com/foo")
35
+ expect(domain.host).to eql("example.com")
36
+ end
37
+
38
+ it "strips trailing slashes from the domain" do
39
+ domain = SiteInspector::Domain.new("http://www.example.com/")
40
+ expect(domain.host).to eql("example.com")
41
+ end
42
+ end
43
+
44
+ context "endpoints" do
45
+ it "generates the endpoints" do
46
+ endpoints = subject.endpoints
47
+ expect(endpoints.count).to eql(4)
48
+ expect(endpoints[0].to_s).to eql("https://example.com")
49
+ expect(endpoints[1].to_s).to eql("https://www.example.com")
50
+ expect(endpoints[2].to_s).to eql("http://example.com")
51
+ expect(endpoints[3].to_s).to eql("http://www.example.com")
52
+ end
53
+ end
54
+
55
+ it "knows the canonical domain" do
56
+ stub_request(:get, "https://example.com/").to_return(:status => 500)
57
+ stub_request(:get, "https://www.example.com/").to_return(:status => 500)
58
+ stub_request(:get, "http://www.example.com/").to_return(:status => 200)
59
+ stub_request(:get, "http://example.com/").to_return(:status => 200)
60
+ expect(subject.canonical_endpoint.to_s).to eql("http://example.com")
61
+ end
62
+
63
+ it "knows if a domain is a government domain" do
64
+ expect(subject.government?).to eql(false)
65
+
66
+ domain = SiteInspector::Domain.new("whitehouse.gov")
67
+ expect(domain.government?).to eql(true)
68
+ end
69
+
70
+ context "up" do
71
+ it "considers an domain up if at least one endpoint is up" do
72
+ stub_request(:get, "https://example.com/").to_return(:status => 500)
73
+ stub_request(:get, "https://www.example.com/").to_return(:status => 500)
74
+ stub_request(:get, "http://example.com/").to_return(:status => 500)
75
+ stub_request(:get, "http://www.example.com/").to_return(:status => 200)
76
+
77
+ expect(subject.up?).to eql(true)
78
+ end
79
+
80
+ it "doesn't consider an endpoint up when all endpoints are down" do
81
+ stub_request(:get, "https://example.com/").to_return(:status => 500)
82
+ stub_request(:get, "https://www.example.com/").to_return(:status => 500)
83
+ stub_request(:get, "http://example.com/").to_return(:status => 500)
84
+ stub_request(:get, "http://www.example.com/").to_return(:status => 500)
85
+
86
+ expect(subject.up?).to eql(false)
87
+ end
88
+ end
89
+
90
+ context "www" do
91
+ it "considers a site www when at least one endpoint is www" do
92
+ stub_request(:get, "https://example.com/").to_return(:status => 200)
93
+ stub_request(:get, "https://www.example.com/").to_return(:status => 500)
94
+ stub_request(:get, "http://example.com/").to_return(:status => 500)
95
+ stub_request(:get, "http://www.example.com/").to_return(:status => 200)
96
+
97
+ expect(subject.up?).to eql(true)
98
+ end
99
+
100
+ it "doesn't consider a site www when no endpoint is www" do
101
+ stub_request(:get, "https://example.com/").to_return(:status => 200)
102
+ stub_request(:get, "https://www.example.com/").to_return(:status => 500)
103
+ stub_request(:get, "http://example.com/").to_return(:status => 200)
104
+ stub_request(:get, "http://www.example.com/").to_return(:status => 500)
105
+
106
+ expect(subject.up?).to eql(true)
107
+ end
108
+ end
109
+
110
+ context "root" do
111
+ it "considers a domain root if you can connect without www" do
112
+ stub_request(:get, "https://example.com/").to_return(:status => 200)
113
+ stub_request(:get, "https://www.example.com/").to_return(:status => 500)
114
+ stub_request(:get, "http://example.com/").to_return(:status => 500)
115
+ stub_request(:get, "http://www.example.com/").to_return(:status => 500)
116
+
117
+ expect(subject.root?).to eql(true)
118
+ end
119
+
120
+ it "doesn't call a www-only domain root" do
121
+ stub_request(:get, "https://example.com/").to_return(:status => 500)
122
+ stub_request(:get, "https://www.example.com/").to_return(:status => 200)
123
+ stub_request(:get, "http://example.com/").to_return(:status => 500)
124
+ stub_request(:get, "http://www.example.com/").to_return(:status => 200)
125
+
126
+ expect(subject.root?).to eql(false)
127
+ end
128
+ end
129
+
130
+ context "https" do
131
+ it "knows when a domain supports https" do
132
+ stub_request(:get, "https://example.com/").to_return(:status => 200)
133
+ stub_request(:get, "https://www.example.com/").to_return(:status => 200)
134
+ stub_request(:get, "http://example.com/").to_return(:status => 200)
135
+ stub_request(:get, "http://www.example.com/").to_return(:status => 200)
136
+ allow(subject.endpoints.first.https).to receive(:valid?) { true }
137
+
138
+ expect(subject.https?).to eql(true)
139
+ end
140
+
141
+ it "knows when a domain doesn't support https" do
142
+ stub_request(:get, "https://example.com/").to_return(:status => 500)
143
+ stub_request(:get, "https://www.example.com/").to_return(:status => 500)
144
+ stub_request(:get, "http://example.com/").to_return(:status => 200)
145
+ stub_request(:get, "http://www.example.com/").to_return(:status => 200)
146
+
147
+ expect(subject.https?).to eql(false)
148
+ end
149
+
150
+ it "considers HTTPS inforced when no http endpoint responds" do
151
+ stub_request(:get, "https://example.com/").to_return(:status => 200)
152
+ stub_request(:get, "https://www.example.com/").to_return(:status => 500)
153
+ stub_request(:get, "http://example.com/").to_return(:status => 500)
154
+ stub_request(:get, "http://www.example.com/").to_return(:status => 500)
155
+
156
+ #expect(subject.enforces_https?).to eql(true)
157
+ end
158
+
159
+ it "doesn't consider HTTPS inforced when an http endpoint responds" do
160
+ stub_request(:get, "https://example.com/").to_return(:status => 200)
161
+ stub_request(:get, "https://www.example.com/").to_return(:status => 500)
162
+ stub_request(:get, "http://example.com/").to_return(:status => 500)
163
+ stub_request(:get, "http://www.example.com/").to_return(:status => 200)
164
+
165
+ expect(subject.enforces_https?).to eql(false)
166
+ end
167
+
168
+ it "detects when a domain downgrades to http" do
169
+
170
+ end
171
+
172
+ it "detects when a domain enforces https" do
173
+
174
+ end
175
+ end
176
+
177
+ context "canonical" do
178
+ context "www" do
179
+ it "detects a domain as canonically www when root is down" do
180
+ stub_request(:get, "https://example.com/").to_return(:status => 500)
181
+ stub_request(:get, "https://www.example.com/").to_return(:status => 500)
182
+ stub_request(:get, "http://example.com/").to_return(:status => 500)
183
+ stub_request(:get, "http://www.example.com/").to_return(:status => 200)
184
+
185
+ expect(subject.canonically_www?).to eql(true)
186
+ end
187
+
188
+ it "detects a domain as canonically www when root redirects" do
189
+ stub_request(:get, "https://example.com/").to_return(:status => 500)
190
+ stub_request(:get, "https://www.example.com/").to_return(:status => 500)
191
+ stub_request(:get, "http://example.com/").
192
+ to_return(:status => 301, :headers => { :location => "http://www.example.com" } )
193
+ stub_request(:get, "http://www.example.com/").to_return(:status => 200)
194
+
195
+ expect(subject.canonically_www?).to eql(true)
196
+ end
197
+ end
198
+
199
+ context "https" do
200
+ it "detects a domain as canonically https when http is down" do
201
+ stub_request(:get, "https://example.com/").to_return(:status => 200)
202
+ stub_request(:get, "https://www.example.com/").to_return(:status => 200)
203
+ stub_request(:get, "http://example.com/").to_return(:status => 500)
204
+ stub_request(:get, "http://www.example.com/").to_return(:status => 500)
205
+ allow(subject.endpoints.first.https).to receive(:valid?) { true }
206
+
207
+ expect(subject.canonically_https?).to eql(true)
208
+ end
209
+
210
+ it "detects a domain as canonically https when http redirect" do
211
+ stub_request(:get, "https://example.com/").to_return(:status => 200)
212
+ stub_request(:get, "https://www.example.com/").to_return(:status => 200)
213
+ stub_request(:get, "http://example.com/").
214
+ to_return(:status => 301, :headers => { :location => "https://example.com" } )
215
+ stub_request(:get, "http://www.example.com/").to_return(:status => 500)
216
+ allow(subject.endpoints.first.https).to receive(:valid?) { true }
217
+
218
+ expect(subject.canonically_https?).to eql(true)
219
+ end
220
+ end
221
+ end
222
+
223
+ context "redirects" do
224
+ it "knows when a domain redirects" do
225
+ stub_request(:get, "https://example.com/").to_return(:status => 500)
226
+ stub_request(:get, "https://www.example.com/").to_return(:status => 500)
227
+ stub_request(:get, "http://example.com/").
228
+ to_return(:status => 301, :headers => { :location => "http://foo.example.com" } )
229
+ stub_request(:get, "http://www.example.com/").to_return(:status => 500)
230
+
231
+ expect(subject.redirect?).to eql(true)
232
+ end
233
+ end
234
+
235
+ context "hsts" do
236
+ it "enabled" do
237
+
238
+ end
239
+
240
+ it "subdomains" do
241
+
242
+ end
243
+
244
+ it "preload ready" do
245
+
246
+ end
247
+ end
248
+
249
+ it "returns the host as a string" do
250
+ expect(subject.to_s).to eql("example.com")
251
+ end
252
+ end