site-inspector 1.0.2 → 2.0.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 (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