moby.rb 1.0.0 → 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.
- checksums.yaml +4 -4
- data/CHANGELOG +120 -0
- data/Gemfile +4 -0
- data/bin/moby +24 -27
- data/lib/Moby/Backends/MechanizeBackend.rb +1 -1
- data/lib/Moby/Backends/SeleniumBackend.rb +28 -22
- data/lib/Moby/Backends.rb +20 -0
- data/lib/Moby/Configuration.rb +17 -11
- data/lib/Moby/VERSION.rb +1 -1
- data/lib/Moby.rb +15 -160
- data/{moby.gemspec → moby.rb.gemspec} +6 -5
- data/test/Moby/Backends/MechanizeBackend_test.rb +83 -0
- data/test/Moby/Backends/SeleniumBackend_test.rb +44 -0
- data/test/Moby/Backends_test.rb +27 -0
- data/test/Moby/Configuration_test.rb +134 -0
- data/test/Moby/RandomInputGenerator_test.rb +37 -0
- data/test/Moby/VERSION_test.rb +2 -2
- data/test/moby_test.rb +6 -282
- data/test/test_helper.rb +15 -0
- metadata +26 -8
- data/CHANGES.txt +0 -32
- data/lib/File/self.collect.rb +0 -10
- data/lib/Thoran/File/SelfCollect/self.collect.rb +0 -22
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# test/Moby/Backends/MechanizeBackend_test.rb
|
|
2
|
+
|
|
3
|
+
require_relative '../../test_helper'
|
|
4
|
+
|
|
5
|
+
describe Moby::Backends::MechanizeBackend do
|
|
6
|
+
let(:url){'https://phishing.example.com/login'}
|
|
7
|
+
let(:generator){Moby::RandomInputGenerator.new(word_list_path: TestFixtures::WORD_LIST_PATH)}
|
|
8
|
+
|
|
9
|
+
def backend(**config_args)
|
|
10
|
+
config = Moby::Configuration.new(url: url, **config_args)
|
|
11
|
+
Moby::Backends::MechanizeBackend.new(config: config, random_input_generator: generator)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
before do
|
|
15
|
+
WebMock.disable_net_connect!
|
|
16
|
+
stub_request(:get, url)
|
|
17
|
+
.to_return(status: 200, body: TestFixtures::SIMPLE_LOGIN_FORM, headers: {'Content-Type' => 'text/html'})
|
|
18
|
+
stub_request(:post, url).to_return(status: 200, body: 'Success')
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
after do
|
|
22
|
+
WebMock.reset!
|
|
23
|
+
WebMock.allow_net_connect!
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
describe "page loading" do
|
|
27
|
+
it "loads the target url once and memoises the page" do
|
|
28
|
+
b = backend
|
|
29
|
+
page1 = b.send(:page)
|
|
30
|
+
page2 = b.send(:page)
|
|
31
|
+
|
|
32
|
+
_(page1).must_be_instance_of(Mechanize::Page)
|
|
33
|
+
_(page1).must_be_same_as(page2)
|
|
34
|
+
assert_requested(:get, url, times: 1)
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
describe "form finding" do
|
|
39
|
+
it "finds the form by name" do
|
|
40
|
+
form = backend(form_name: 'login').send(:form)
|
|
41
|
+
|
|
42
|
+
_(form).must_be_instance_of(Mechanize::Form)
|
|
43
|
+
_(form.name).must_equal('login')
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
it "finds the form by number" do
|
|
47
|
+
multi_url = 'https://multi.example.com/page'
|
|
48
|
+
stub_request(:get, multi_url)
|
|
49
|
+
.to_return(status: 200, body: TestFixtures::MULTIPLE_FORMS, headers: {'Content-Type' => 'text/html'})
|
|
50
|
+
config = Moby::Configuration.new(url: multi_url, form_number: 1)
|
|
51
|
+
b = Moby::Backends::MechanizeBackend.new(config: config, random_input_generator: generator)
|
|
52
|
+
|
|
53
|
+
_(b.send(:form).node['id']).must_equal('login_form')
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
describe "field finding" do
|
|
58
|
+
it "finds the username and password fields by name" do
|
|
59
|
+
b = backend(form_name: 'login', username_field_name: 'username', password_field_name: 'password')
|
|
60
|
+
|
|
61
|
+
_(b.send(:username_field).name).must_equal('username')
|
|
62
|
+
_(b.send(:password_field).name).must_equal('password')
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
describe "submitting" do
|
|
67
|
+
it "fills both fields and submits" do
|
|
68
|
+
backend.send(:fill_and_submit_login_form)
|
|
69
|
+
|
|
70
|
+
assert_requested(:post, url) do |request|
|
|
71
|
+
request.body.match?(/username=[^&]+/) && request.body.match?(/password=[^&]+/)
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
it "submits only the password when the username is prefilled" do
|
|
76
|
+
backend(username_prefilled: true).send(:fill_and_submit_login_form)
|
|
77
|
+
|
|
78
|
+
assert_requested(:post, url) do |request|
|
|
79
|
+
request.body.match?(/username=(&|\z)/) && request.body.match?(/password=[^&]+/)
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# test/Moby/Backends/SeleniumBackend_test.rb
|
|
2
|
+
|
|
3
|
+
require_relative '../../test_helper'
|
|
4
|
+
|
|
5
|
+
describe Moby::Backends::SeleniumBackend do
|
|
6
|
+
let(:url){'https://phishing.example.com/login'}
|
|
7
|
+
let(:generator){Moby::RandomInputGenerator.new(word_list_path: TestFixtures::WORD_LIST_PATH)}
|
|
8
|
+
|
|
9
|
+
def backend(**config_args)
|
|
10
|
+
config = Moby::Configuration.new(url: url, backend: :selenium, **config_args)
|
|
11
|
+
Moby::Backends::SeleniumBackend.new(config: config, random_input_generator: generator)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# Stub the real browser launch so no Chrome/Firefox process is started.
|
|
15
|
+
def with_stubbed_driver
|
|
16
|
+
built = 0
|
|
17
|
+
fake_driver = Object.new
|
|
18
|
+
original = Selenium::WebDriver.method(:for)
|
|
19
|
+
Selenium::WebDriver.define_singleton_method(:for) do |*_args, **_kwargs|
|
|
20
|
+
built += 1
|
|
21
|
+
fake_driver
|
|
22
|
+
end
|
|
23
|
+
begin
|
|
24
|
+
yield(fake_driver, ->{built})
|
|
25
|
+
ensure
|
|
26
|
+
Selenium::WebDriver.define_singleton_method(:for, original)
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
describe "#driver" do
|
|
31
|
+
it "builds the driver once and memoises it (returning a non-nil driver)" do
|
|
32
|
+
with_stubbed_driver do |fake_driver, build_count|
|
|
33
|
+
b = backend
|
|
34
|
+
first = b.send(:driver)
|
|
35
|
+
second = b.send(:driver)
|
|
36
|
+
|
|
37
|
+
_(first).wont_be_nil
|
|
38
|
+
_(first).must_be_same_as(fake_driver)
|
|
39
|
+
_(first).must_be_same_as(second)
|
|
40
|
+
_(build_count.call).must_equal(1)
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# test/Moby/Backends_test.rb
|
|
2
|
+
|
|
3
|
+
require_relative '../test_helper'
|
|
4
|
+
|
|
5
|
+
describe Moby::Backends do
|
|
6
|
+
let(:url){'https://phishing.example.com/login'}
|
|
7
|
+
let(:generator){Moby::RandomInputGenerator.new(word_list_path: TestFixtures::WORD_LIST_PATH)}
|
|
8
|
+
|
|
9
|
+
def backend_for(**config_args)
|
|
10
|
+
config = Moby::Configuration.new(url: url, **config_args)
|
|
11
|
+
Moby::Backends.for(config: config, random_input_generator: generator)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
describe ".for" do
|
|
15
|
+
it "returns a MechanizeBackend for the mechanize configuration" do
|
|
16
|
+
_(backend_for).must_be_instance_of(Moby::Backends::MechanizeBackend)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
it "returns a SeleniumBackend for the selenium configuration" do
|
|
20
|
+
_(backend_for(backend: :selenium)).must_be_instance_of(Moby::Backends::SeleniumBackend)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
it "raises for an unknown backend" do
|
|
24
|
+
_(proc{backend_for(backend: :ftp)}).must_raise(RuntimeError)
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
# test/Moby/Configuration_test.rb
|
|
2
|
+
|
|
3
|
+
require_relative '../test_helper'
|
|
4
|
+
|
|
5
|
+
describe Moby::Configuration do
|
|
6
|
+
let(:url){'https://phishing.example.com/login'}
|
|
7
|
+
|
|
8
|
+
describe "defaults" do
|
|
9
|
+
let(:config){Moby::Configuration.new(url: url)}
|
|
10
|
+
|
|
11
|
+
it "keeps the url" do
|
|
12
|
+
_(config.url).must_equal(url)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
it "defaults the backend to mechanize" do
|
|
16
|
+
_(config.backend).must_equal(:mechanize)
|
|
17
|
+
_(config.mechanize?).must_equal(true)
|
|
18
|
+
_(config.selenium?).must_equal(false)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
it "defaults the browser to chrome" do
|
|
22
|
+
_(config.browser).must_equal(:chrome)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
it "defaults form_number to 0" do
|
|
26
|
+
_(config.form_number).must_equal(0)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
it "defaults the field names to username and password" do
|
|
30
|
+
_(config.username_field_name).must_equal('username')
|
|
31
|
+
_(config.password_field_name).must_equal('password')
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
it "defaults the field numbers to 0 and 1" do
|
|
35
|
+
_(config.username_field_number).must_equal(0)
|
|
36
|
+
_(config.password_field_number).must_equal(1)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
it "runs indefinitely by default" do
|
|
40
|
+
_(config.iterations).must_be_nil
|
|
41
|
+
_(config.fixed_number_of_submissions?).must_equal(false)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
it "defaults the delay to 0" do
|
|
45
|
+
_(config.delay).must_equal(0)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
it "is not prefilled, email, verbose or debug by default" do
|
|
49
|
+
_(config.username_prefilled?).must_equal(false)
|
|
50
|
+
_(config.username_is_email_address?).must_equal(false)
|
|
51
|
+
_(config.verbose?).must_equal(false)
|
|
52
|
+
_(config.debug?).must_equal(false)
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
describe "backend and browser selection" do
|
|
57
|
+
it "selects selenium" do
|
|
58
|
+
_(Moby::Configuration.new(url: url, backend: :selenium).selenium?).must_equal(true)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
it "coerces a string backend to a symbol" do
|
|
62
|
+
_(Moby::Configuration.new(url: url, backend: 'selenium').backend).must_equal(:selenium)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
it "falls back to mechanize when the backend is nil" do
|
|
66
|
+
_(Moby::Configuration.new(url: url, backend: nil).backend).must_equal(:mechanize)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
it "falls back to chrome when the browser is nil" do
|
|
70
|
+
_(Moby::Configuration.new(url: url, browser: nil).browser).must_equal(:chrome)
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
describe "coercion of string inputs" do
|
|
75
|
+
let(:config) do
|
|
76
|
+
Moby::Configuration.new(
|
|
77
|
+
url: url,
|
|
78
|
+
form_number: '2',
|
|
79
|
+
username_field_number: '3',
|
|
80
|
+
password_field_number: '4',
|
|
81
|
+
delay: '5',
|
|
82
|
+
iterations: '6'
|
|
83
|
+
)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
it "coerces the numeric switches to integers" do
|
|
87
|
+
_(config.form_number).must_equal(2)
|
|
88
|
+
_(config.username_field_number).must_equal(3)
|
|
89
|
+
_(config.password_field_number).must_equal(4)
|
|
90
|
+
_(config.delay).must_equal(5)
|
|
91
|
+
_(config.iterations).must_equal(6)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
it "treats a given iterations count as a fixed number of submissions" do
|
|
95
|
+
_(config.fixed_number_of_submissions?).must_equal(true)
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
describe "#username_is_email_address?" do
|
|
100
|
+
it "is true when set explicitly" do
|
|
101
|
+
_(Moby::Configuration.new(url: url, username_is_email_address: true).username_is_email_address?).must_equal(true)
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
it "is truthy when a username hostname is given" do
|
|
105
|
+
_(Moby::Configuration.new(url: url, username_hostname: 'example.com').username_is_email_address?).wont_equal(false)
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
it "is truthy when the username field is named email" do
|
|
109
|
+
_(Moby::Configuration.new(url: url, username_field_name: 'email').username_is_email_address?).wont_equal(false)
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
describe "#user_agent" do
|
|
114
|
+
it "returns a random agent when not set" do
|
|
115
|
+
agent = Moby::Configuration.new(url: url).user_agent
|
|
116
|
+
_(agent).must_be_kind_of(String)
|
|
117
|
+
_(agent).wont_equal('Mechanize')
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
it "returns the set agent" do
|
|
121
|
+
_(Moby::Configuration.new(url: url, user_agent: 'CustomAgent').user_agent).must_equal('CustomAgent')
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
describe "#using_form_name?" do
|
|
126
|
+
it "is false without a form name" do
|
|
127
|
+
_(Moby::Configuration.new(url: url).using_form_name?).must_equal(false)
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
it "is true with a form name" do
|
|
131
|
+
_(Moby::Configuration.new(url: url, form_name: 'login').using_form_name?).must_equal(true)
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# test/Moby/RandomInputGenerator_test.rb
|
|
2
|
+
|
|
3
|
+
require_relative '../test_helper'
|
|
4
|
+
|
|
5
|
+
describe Moby::RandomInputGenerator do
|
|
6
|
+
let(:word_list_path){TestFixtures::WORD_LIST_PATH}
|
|
7
|
+
|
|
8
|
+
def generator(**args)
|
|
9
|
+
Moby::RandomInputGenerator.new(word_list_path: word_list_path, **args)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
describe "#random_username" do
|
|
13
|
+
it "returns a word from the list by default" do
|
|
14
|
+
_(TestFixtures::WORDS).must_include(generator.random_username)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
it "returns an email address when username_is_email_address is true" do
|
|
18
|
+
_(generator(username_is_email_address: true).random_username).must_match(/\A[^@]+@[^@]+\z/)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
it "uses the given hostname for the email address" do
|
|
22
|
+
username = generator(username_hostname: 'example.com', username_is_email_address: true).random_username
|
|
23
|
+
_(username).must_match(/@example\.com\z/)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
it "generates more than one distinct username" do
|
|
27
|
+
usernames = 20.times.map{generator.random_username}
|
|
28
|
+
_(usernames.uniq.length).must_be(:>, 1)
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
describe "#random_password" do
|
|
33
|
+
it "returns a word from the list" do
|
|
34
|
+
_(TestFixtures::WORDS).must_include(generator.random_password)
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
data/test/Moby/VERSION_test.rb
CHANGED
data/test/moby_test.rb
CHANGED
|
@@ -4,292 +4,16 @@ require_relative './test_helper'
|
|
|
4
4
|
|
|
5
5
|
describe Moby do
|
|
6
6
|
let(:url){'https://phishing.example.com/login'}
|
|
7
|
-
let(:moby){Moby.new(url: url)}
|
|
8
7
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
# Stub the login page
|
|
13
|
-
stub_request(:get, url)
|
|
14
|
-
.to_return(
|
|
15
|
-
status: 200,
|
|
16
|
-
body: TestFixtures::SIMPLE_LOGIN_FORM,
|
|
17
|
-
headers: {'Content-Type' => 'text/html'}
|
|
18
|
-
)
|
|
19
|
-
|
|
20
|
-
# Stub form submission
|
|
21
|
-
stub_request(:post, 'https://phishing.example.com/login')
|
|
22
|
-
.to_return(status: 200, body: 'Success')
|
|
23
|
-
end
|
|
24
|
-
|
|
25
|
-
after do
|
|
26
|
-
WebMock.reset!
|
|
27
|
-
WebMock.allow_net_connect!
|
|
28
|
-
end
|
|
29
|
-
|
|
30
|
-
describe "initialization" do
|
|
31
|
-
it "accepts url parameter" do
|
|
32
|
-
_(moby.url).must_equal(url)
|
|
33
|
-
end
|
|
34
|
-
|
|
35
|
-
it "sets default form_number to 0" do
|
|
36
|
-
_(moby.form_number).must_equal(0)
|
|
37
|
-
end
|
|
38
|
-
|
|
39
|
-
it "sets default username_field_name to 'username'" do
|
|
40
|
-
_(moby.username_field_name).must_equal('username')
|
|
41
|
-
end
|
|
42
|
-
|
|
43
|
-
it "sets default password_field_name to 'password'" do
|
|
44
|
-
_(moby.password_field_name).must_equal('password')
|
|
45
|
-
end
|
|
46
|
-
|
|
47
|
-
it "sets default username_field_number to 0" do
|
|
48
|
-
_(moby.username_field_number).must_equal(0)
|
|
49
|
-
end
|
|
50
|
-
|
|
51
|
-
it "sets default password_field_number to 1" do
|
|
52
|
-
_(moby.password_field_number).must_equal(1)
|
|
53
|
-
end
|
|
54
|
-
|
|
55
|
-
it "accepts form_name parameter" do
|
|
56
|
-
m = Moby.new(url: url, form_name: 'login')
|
|
57
|
-
|
|
58
|
-
_(m.form_name).must_equal('login')
|
|
59
|
-
end
|
|
60
|
-
|
|
61
|
-
it "accepts username_hostname parameter" do
|
|
62
|
-
m = Moby.new(url: url, username_hostname: 'example.com')
|
|
63
|
-
|
|
64
|
-
_(m.username_hostname).must_equal('example.com')
|
|
65
|
-
end
|
|
66
|
-
|
|
67
|
-
it "accepts username_is_email_address parameter" do
|
|
68
|
-
m = Moby.new(url: url, username_is_email_address: true)
|
|
69
|
-
|
|
70
|
-
_(m.username_is_email_address).must_equal(true)
|
|
71
|
-
end
|
|
72
|
-
end
|
|
73
|
-
|
|
74
|
-
describe "mechanize setup" do
|
|
75
|
-
it "creates Mechanize instance lazily" do
|
|
76
|
-
mech = moby.instance_variable_get(:@mechanize)
|
|
77
|
-
|
|
78
|
-
_(mech).must_be_nil
|
|
79
|
-
|
|
80
|
-
moby.send(:mechanize)
|
|
81
|
-
mech = moby.instance_variable_get(:@mechanize)
|
|
82
|
-
|
|
83
|
-
_(mech).must_be_instance_of(Mechanize)
|
|
84
|
-
end
|
|
85
|
-
|
|
86
|
-
it "reuses same mechanize instance" do
|
|
87
|
-
mech1 = moby.send(:mechanize)
|
|
88
|
-
mech2 = moby.send(:mechanize)
|
|
89
|
-
|
|
90
|
-
_(mech1).must_be_same_as(mech2)
|
|
91
|
-
end
|
|
92
|
-
end
|
|
93
|
-
|
|
94
|
-
describe "page loading" do
|
|
95
|
-
it "loads the target URL" do
|
|
96
|
-
page = moby.send(:page)
|
|
97
|
-
|
|
98
|
-
_(page).must_be_instance_of(Mechanize::Page)
|
|
99
|
-
assert_requested(:get, url, times: 1)
|
|
100
|
-
end
|
|
101
|
-
|
|
102
|
-
it "reuses loaded page" do
|
|
103
|
-
page1 = moby.send(:page)
|
|
104
|
-
page2 = moby.send(:page)
|
|
105
|
-
|
|
106
|
-
_(page1).must_be_same_as(page2)
|
|
107
|
-
assert_requested(:get, url, times: 1)
|
|
108
|
-
end
|
|
109
|
-
|
|
110
|
-
it "handles meta refresh redirects" do
|
|
111
|
-
redirect_body = '<html><head><meta http-equiv="Refresh" content="0; url=https://phishing.example.com/real-login"></head></html>'
|
|
112
|
-
|
|
113
|
-
stub_request(:get, url)
|
|
114
|
-
.to_return(status: 200, body: redirect_body, headers: {'Content-Type' => 'text/html'})
|
|
115
|
-
|
|
116
|
-
stub_request(:get, 'https://phishing.example.com/real-login')
|
|
117
|
-
.to_return(status: 200, body: TestFixtures::SIMPLE_LOGIN_FORM, headers: {'Content-Type' => 'text/html'})
|
|
118
|
-
|
|
119
|
-
m = Moby.new(url: url)
|
|
120
|
-
page = m.send(:page)
|
|
121
|
-
|
|
122
|
-
assert_requested(:get, url, times: 1)
|
|
123
|
-
assert_requested(:get, 'https://phishing.example.com/real-login', times: 1)
|
|
124
|
-
end
|
|
125
|
-
end
|
|
126
|
-
|
|
127
|
-
describe "form finding" do
|
|
128
|
-
describe "finding by name" do
|
|
129
|
-
let(:moby) do
|
|
130
|
-
Moby.new(url: url, form_name: 'login')
|
|
131
|
-
end
|
|
132
|
-
|
|
133
|
-
it "finds form by name when form_name is set" do
|
|
134
|
-
moby.send(:page)
|
|
135
|
-
form = moby.send(:form)
|
|
136
|
-
|
|
137
|
-
_(form).must_be_instance_of(Mechanize::Form)
|
|
138
|
-
_(form.name).must_equal('login')
|
|
139
|
-
end
|
|
140
|
-
end
|
|
141
|
-
|
|
142
|
-
describe "finding by number" do
|
|
143
|
-
let(:moby) do
|
|
144
|
-
Moby.new(url: 'https://multi.example.com/page', form_number: 1)
|
|
145
|
-
end
|
|
146
|
-
|
|
147
|
-
it "finds form by number when form_number is set" do
|
|
148
|
-
stub_request(:get, 'https://multi.example.com/page')
|
|
149
|
-
.to_return(
|
|
150
|
-
status: 200,
|
|
151
|
-
body: TestFixtures::MULTIPLE_FORMS,
|
|
152
|
-
headers: {'Content-Type' => 'text/html'}
|
|
153
|
-
)
|
|
154
|
-
moby.send(:page)
|
|
155
|
-
form = moby.send(:form)
|
|
156
|
-
|
|
157
|
-
_(form).must_be_instance_of(Mechanize::Form)
|
|
158
|
-
_(form.node['id']).must_equal('login_form')
|
|
159
|
-
end
|
|
160
|
-
end
|
|
161
|
-
|
|
162
|
-
describe "deafult form" do
|
|
163
|
-
it "defaults to first form" do
|
|
164
|
-
moby.send(:page)
|
|
165
|
-
form = moby.send(:form)
|
|
166
|
-
|
|
167
|
-
_(form).must_be_instance_of(Mechanize::Form)
|
|
168
|
-
end
|
|
169
|
-
end
|
|
170
|
-
end
|
|
171
|
-
|
|
172
|
-
describe "field finding" do
|
|
173
|
-
describe "finding by name" do
|
|
174
|
-
let(:moby) do
|
|
175
|
-
Moby.new(url: url, form_name: 'login', username_field_name: 'username', password_field_name: 'password')
|
|
176
|
-
end
|
|
177
|
-
|
|
178
|
-
it "finds username field by name" do
|
|
179
|
-
moby.send(:page)
|
|
180
|
-
moby.send(:form)
|
|
181
|
-
field = moby.send(:username_field)
|
|
182
|
-
|
|
183
|
-
_(field.name).must_equal('username')
|
|
184
|
-
end
|
|
185
|
-
|
|
186
|
-
it "finds password field by name" do
|
|
187
|
-
moby.send(:page)
|
|
188
|
-
moby.send(:form)
|
|
189
|
-
field = moby.send(:password_field)
|
|
190
|
-
|
|
191
|
-
_(field.name).must_equal('password')
|
|
192
|
-
end
|
|
193
|
-
end
|
|
194
|
-
|
|
195
|
-
describe "finding by number" do
|
|
196
|
-
let(:moby) do
|
|
197
|
-
Moby.new(url: url, username_field_number: 0, password_field_number: 1)
|
|
198
|
-
end
|
|
199
|
-
|
|
200
|
-
it "finds username field by number" do
|
|
201
|
-
moby.send(:page)
|
|
202
|
-
moby.send(:form)
|
|
203
|
-
field = moby.send(:username_field)
|
|
204
|
-
|
|
205
|
-
_(field).wont_be_nil
|
|
206
|
-
end
|
|
207
|
-
|
|
208
|
-
it "finds password field by number" do
|
|
209
|
-
moby.send(:page)
|
|
210
|
-
moby.send(:form)
|
|
211
|
-
field = moby.send(:password_field)
|
|
212
|
-
|
|
213
|
-
_(field).wont_be_nil
|
|
214
|
-
end
|
|
215
|
-
end
|
|
216
|
-
end
|
|
217
|
-
|
|
218
|
-
describe "#username" do
|
|
219
|
-
it "generates random username" do
|
|
220
|
-
username = moby.send(:username)
|
|
221
|
-
|
|
222
|
-
_(username).must_be_kind_of(String)
|
|
223
|
-
_(username.length).must_be(:>, 0)
|
|
224
|
-
end
|
|
225
|
-
|
|
226
|
-
it "generates email address when username_is_email_address is true" do
|
|
227
|
-
m = Moby.new(url: url, username_is_email_address: true)
|
|
228
|
-
username = m.send(:username)
|
|
229
|
-
|
|
230
|
-
_(username).must_match(/@/)
|
|
231
|
-
end
|
|
232
|
-
|
|
233
|
-
it "generates email with custom hostname" do
|
|
234
|
-
m = Moby.new(url: url, username_hostname: 'example.com')
|
|
235
|
-
username = m.send(:username)
|
|
236
|
-
|
|
237
|
-
_(username).must_match(/@example\.com$/)
|
|
238
|
-
end
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
it "generates different usernames" do
|
|
242
|
-
usernames = 10.times.map{moby.send(:username)}
|
|
243
|
-
|
|
244
|
-
_(usernames.uniq.length).must_be(:>, 1)
|
|
245
|
-
end
|
|
8
|
+
it "selects the mechanize backend by default" do
|
|
9
|
+
_(Moby.new(url: url).send(:backend)).must_be_instance_of(Moby::Backends::MechanizeBackend)
|
|
246
10
|
end
|
|
247
11
|
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
password = moby.send(:password)
|
|
251
|
-
|
|
252
|
-
_(password).must_be_kind_of(String)
|
|
253
|
-
_(password.length).must_be(:>, 0)
|
|
254
|
-
end
|
|
12
|
+
it "selects the selenium backend when configured" do
|
|
13
|
+
_(Moby.new(url: url, backend: :selenium).send(:backend)).must_be_instance_of(Moby::Backends::SeleniumBackend)
|
|
255
14
|
end
|
|
256
15
|
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
agent = moby.send(:user_agent)
|
|
260
|
-
|
|
261
|
-
_(agent).must_be_kind_of(String)
|
|
262
|
-
_(agent).wont_equal('Mechanize')
|
|
263
|
-
end
|
|
264
|
-
|
|
265
|
-
it "returns set user agent when provided" do
|
|
266
|
-
m = Moby.new(url: url, user_agent: 'CustomAgent')
|
|
267
|
-
|
|
268
|
-
_(m.send(:user_agent)).must_equal('CustomAgent')
|
|
269
|
-
end
|
|
270
|
-
|
|
271
|
-
it "generates different random agents" do
|
|
272
|
-
agents = 10.times.map{Moby.new(url: url).send(:user_agent)}
|
|
273
|
-
|
|
274
|
-
_(agents.uniq.length).must_be(:>, 1)
|
|
275
|
-
end
|
|
276
|
-
end
|
|
277
|
-
|
|
278
|
-
describe "#username_is_email_address?" do
|
|
279
|
-
it "returns true when username_is_email_address is set" do
|
|
280
|
-
m = Moby.new(url: url, username_is_email_address: true)
|
|
281
|
-
|
|
282
|
-
_(m.send(:username_is_email_address?)).must_equal(true)
|
|
283
|
-
end
|
|
284
|
-
|
|
285
|
-
it "returns true when username_hostname is set" do
|
|
286
|
-
m = Moby.new(url: url, username_hostname: 'example.com')
|
|
287
|
-
|
|
288
|
-
_(m.send(:username_is_email_address?)).wont_be_nil
|
|
289
|
-
end
|
|
290
|
-
|
|
291
|
-
it "returns false by default" do
|
|
292
|
-
_(moby.send(:username_field_name)).must_equal('username')
|
|
293
|
-
end
|
|
16
|
+
it "responds to counter_phish" do
|
|
17
|
+
_(Moby.new(url: url)).must_respond_to(:counter_phish)
|
|
294
18
|
end
|
|
295
19
|
end
|
data/test/test_helper.rb
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
require 'minitest/autorun'
|
|
4
4
|
require 'minitest-spec-context'
|
|
5
5
|
require 'webmock/minitest'
|
|
6
|
+
require 'tempfile'
|
|
6
7
|
|
|
7
8
|
require_relative '../lib/moby'
|
|
8
9
|
|
|
@@ -33,4 +34,18 @@ module TestFixtures
|
|
|
33
34
|
</body>
|
|
34
35
|
</html>
|
|
35
36
|
HTML
|
|
37
|
+
|
|
38
|
+
WORDS = %w{
|
|
39
|
+
alpha bravo charlie delta echo foxtrot golf hotel india juliet
|
|
40
|
+
kilo lima mike november oscar papa quebec romeo sierra tango
|
|
41
|
+
uniform victor whiskey xray yankee zulu apple banana cherry date
|
|
42
|
+
elder fig grape honey iris jasmine kiwi lemon mango nutmeg
|
|
43
|
+
}.freeze
|
|
44
|
+
|
|
45
|
+
WORD_LIST_FILE = Tempfile.new('moby_words').tap do |file|
|
|
46
|
+
file.write(WORDS.join("\n") << "\n")
|
|
47
|
+
file.flush
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
WORD_LIST_PATH = WORD_LIST_FILE.path
|
|
36
51
|
end
|