metasploit_data_models 0.24.4 → 0.24.5
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/.rspec +3 -3
- data/.travis.yml +3 -6
- data/CONTRIBUTING.md +52 -10
- data/Gemfile +8 -1
- data/Rakefile +0 -23
- data/app/models/mdm/api_key.rb +1 -41
- data/app/models/mdm/client.rb +1 -41
- data/app/models/mdm/cred.rb +19 -107
- data/app/models/mdm/event.rb +1 -48
- data/app/models/mdm/exploit_attempt.rb +16 -65
- data/app/models/mdm/exploited_host.rb +1 -28
- data/app/models/mdm/host_detail.rb +1 -45
- data/app/models/mdm/host_tag.rb +8 -6
- data/app/models/mdm/listener.rb +1 -53
- data/app/models/mdm/macro.rb +0 -42
- data/app/models/mdm/mod_ref.rb +0 -21
- data/app/models/mdm/module/action.rb +0 -15
- data/app/models/mdm/module/arch.rb +0 -10
- data/app/models/mdm/module/author.rb +0 -16
- data/app/models/mdm/module/mixin.rb +0 -13
- data/app/models/mdm/module/platform.rb +0 -11
- data/app/models/mdm/module/target.rb +0 -18
- data/app/models/mdm/nexpose_console.rb +4 -82
- data/app/models/mdm/profile.rb +0 -36
- data/app/models/mdm/route.rb +5 -17
- data/app/models/mdm/session_event.rb +1 -33
- data/app/models/mdm/tag.rb +10 -49
- data/app/models/mdm/task.rb +45 -94
- data/app/models/mdm/task_cred.rb +0 -29
- data/app/models/mdm/task_host.rb +0 -25
- data/app/models/mdm/task_service.rb +0 -25
- data/app/models/mdm/task_session.rb +0 -25
- data/app/models/mdm/user.rb +6 -188
- data/app/models/mdm/vuln_attempt.rb +12 -37
- data/app/models/mdm/vuln_detail.rb +5 -139
- data/app/models/mdm/vuln_ref.rb +1 -4
- data/app/models/mdm/web_form.rb +1 -35
- data/app/models/mdm/web_page.rb +1 -70
- data/app/models/mdm/web_site.rb +1 -51
- data/app/models/mdm/wmap_request.rb +0 -85
- data/app/models/mdm/wmap_target.rb +0 -40
- data/app/models/mdm/workspace.rb +14 -152
- data/app/models/metasploit_data_models/automatic_exploitation.rb +16 -0
- data/app/models/metasploit_data_models/automatic_exploitation/match.rb +24 -19
- data/app/models/metasploit_data_models/automatic_exploitation/match_result.rb +5 -33
- data/app/models/metasploit_data_models/automatic_exploitation/match_set.rb +4 -22
- data/app/models/metasploit_data_models/automatic_exploitation/run.rb +3 -13
- data/app/models/metasploit_data_models/ip_address/v4/segmented.rb +1 -1
- data/app/models/metasploit_data_models/module_run.rb +1 -1
- data/app/models/metasploit_data_models/search/visitor/where.rb +1 -1
- data/app/validators/ip_format_validator.rb +0 -4
- data/app/validators/parameters_validator.rb +0 -12
- data/app/validators/password_is_strong_validator.rb +1 -10
- data/lib/mdm/host/operating_system_normalization.rb +10 -7
- data/lib/metasploit_data_models.rb +0 -4
- data/lib/metasploit_data_models/engine.rb +0 -2
- data/lib/metasploit_data_models/serialized_prefs.rb +0 -6
- data/lib/metasploit_data_models/version.rb +10 -24
- data/lib/tasks/yard.rake +33 -0
- data/metasploit_data_models.gemspec +2 -9
- data/spec/app/models/mdm/api_key_spec.rb +3 -1
- data/spec/app/models/mdm/client_spec.rb +11 -9
- data/spec/app/models/mdm/cred_spec.rb +54 -42
- data/spec/app/models/mdm/event_spec.rb +23 -21
- data/spec/app/models/mdm/exploit_attempt_spec.rb +21 -19
- data/spec/app/models/mdm/exploited_host_spec.rb +13 -11
- data/spec/app/models/mdm/host_detail_spec.rb +17 -15
- data/spec/app/models/mdm/host_spec.rb +260 -261
- data/spec/app/models/mdm/host_tag_spec.rb +8 -6
- data/spec/app/models/mdm/listener_spec.rb +32 -30
- data/spec/app/models/mdm/loot_spec.rb +23 -21
- data/spec/app/models/mdm/macro_spec.rb +3 -1
- data/spec/app/models/mdm/mod_ref_spec.rb +3 -1
- data/spec/app/models/mdm/module/action_spec.rb +12 -10
- data/spec/app/models/mdm/module/arch_spec.rb +12 -10
- data/spec/app/models/mdm/module/author_spec.rb +17 -22
- data/spec/app/models/mdm/module/detail_spec.rb +75 -184
- data/spec/app/models/mdm/module/mixin_spec.rb +12 -10
- data/spec/app/models/mdm/module/platform_spec.rb +12 -10
- data/spec/app/models/mdm/module/ref_spec.rb +12 -10
- data/spec/app/models/mdm/module/target_spec.rb +15 -13
- data/spec/app/models/mdm/nexpose_console_spec.rb +37 -35
- data/spec/app/models/mdm/note_spec.rb +25 -23
- data/spec/app/models/mdm/profile_spec.rb +3 -1
- data/spec/app/models/mdm/ref_spec.rb +12 -10
- data/spec/app/models/mdm/route_spec.rb +8 -6
- data/spec/app/models/mdm/service_spec.rb +40 -38
- data/spec/app/models/mdm/session_event_spec.rb +12 -10
- data/spec/app/models/mdm/session_spec.rb +15 -13
- data/spec/app/models/mdm/tag_spec.rb +29 -29
- data/spec/app/models/mdm/task_cred_spec.rb +11 -9
- data/spec/app/models/mdm/task_host_spec.rb +11 -9
- data/spec/app/models/mdm/task_service_spec.rb +11 -9
- data/spec/app/models/mdm/task_session_spec.rb +9 -7
- data/spec/app/models/mdm/task_spec.rb +29 -27
- data/spec/app/models/mdm/user_spec.rb +19 -17
- data/spec/app/models/mdm/vuln_attempt_spec.rb +16 -14
- data/spec/app/models/mdm/vuln_detail_spec.rb +28 -26
- data/spec/app/models/mdm/vuln_ref_spec.rb +10 -8
- data/spec/app/models/mdm/vuln_spec.rb +26 -24
- data/spec/app/models/mdm/web_form_spec.rb +13 -11
- data/spec/app/models/mdm/web_page_spec.rb +21 -19
- data/spec/app/models/mdm/web_site_spec.rb +23 -21
- data/spec/app/models/mdm/web_vuln_spec.rb +65 -63
- data/spec/app/models/mdm/wmap_request_spec.rb +3 -1
- data/spec/app/models/mdm/wmap_target_spec.rb +3 -1
- data/spec/app/models/mdm/workspace_spec.rb +100 -97
- data/spec/app/models/metasploit_data_models/automatic_exploitation/match_result_spec.rb +5 -3
- data/spec/app/models/metasploit_data_models/automatic_exploitation/match_set_spec.rb +15 -13
- data/spec/app/models/metasploit_data_models/automatic_exploitation/match_spec.rb +3 -1
- data/spec/app/models/metasploit_data_models/automatic_exploitation/run_spec.rb +3 -1
- data/spec/app/models/metasploit_data_models/ip_address/v4/cidr_spec.rb +12 -10
- data/spec/app/models/metasploit_data_models/ip_address/v4/nmap_spec.rb +6 -4
- data/spec/app/models/metasploit_data_models/ip_address/v4/range_spec.rb +23 -21
- data/spec/app/models/metasploit_data_models/ip_address/v4/segment/nmap/list_spec.rb +11 -9
- data/spec/app/models/metasploit_data_models/ip_address/v4/segment/nmap/range_spec.rb +23 -21
- data/spec/app/models/metasploit_data_models/ip_address/v4/segment/segmented_spec.rb +6 -4
- data/spec/app/models/metasploit_data_models/ip_address/v4/segment/single_spec.rb +15 -22
- data/spec/app/models/metasploit_data_models/ip_address/v4/single_spec.rb +6 -4
- data/spec/app/models/metasploit_data_models/module_run_spec.rb +3 -1
- data/spec/app/models/metasploit_data_models/search/operation/ip_address_spec.rb +20 -18
- data/spec/app/models/metasploit_data_models/search/operation/port/number_spec.rb +8 -6
- data/spec/app/models/metasploit_data_models/search/operation/port/range_spec.rb +10 -8
- data/spec/app/models/metasploit_data_models/search/operation/range_spec.rb +10 -8
- data/spec/app/models/metasploit_data_models/search/operator/ip_address_spec.rb +4 -2
- data/spec/app/models/metasploit_data_models/search/operator/multitext_spec.rb +10 -8
- data/spec/app/models/metasploit_data_models/search/operator/port/list_spec.rb +8 -6
- data/spec/app/models/metasploit_data_models/search/visitor/attribute_spec.rb +11 -9
- data/spec/app/models/metasploit_data_models/search/visitor/includes_spec.rb +7 -5
- data/spec/app/models/metasploit_data_models/search/visitor/joins_spec.rb +19 -17
- data/spec/app/models/metasploit_data_models/search/visitor/method_spec.rb +7 -5
- data/spec/app/models/metasploit_data_models/search/visitor/relation_spec.rb +23 -61
- data/spec/app/models/metasploit_data_models/search/visitor/where_spec.rb +10 -8
- data/spec/app/validators/parameters_validator_spec.rb +29 -29
- data/spec/app/validators/password_is_strong_validator_spec.rb +46 -54
- data/spec/dummy/db/structure.sql +3403 -0
- data/spec/factories/mdm/module/details.rb +1 -1
- data/spec/lib/base64_serializer_spec.rb +19 -19
- data/spec/lib/metasploit_data_models/ip_address/cidr_spec.rb +12 -18
- data/spec/lib/metasploit_data_models/ip_address/range_spec.rb +6 -4
- data/spec/lib/metasploit_data_models/match/child_spec.rb +4 -2
- data/spec/lib/metasploit_data_models/match/parent_spec.rb +6 -4
- data/spec/lib/metasploit_data_models/version_spec.rb +141 -3
- data/spec/spec_helper.rb +12 -86
- data/spec/support/shared/examples/mdm/module/detail/does_not_support_stance_with_mtype.rb +2 -2
- data/spec/support/shared/examples/mdm/module/detail/supports_stance_with_mtype.rb +4 -4
- data/spec/support/shared/examples/metasploit_data_models/search/operation/ipaddress/match.rb +2 -2
- data/spec/support/shared/examples/metasploit_data_models/search/visitor/includes/visit/with_children.rb +5 -5
- data/spec/support/shared/examples/metasploit_data_models/search/visitor/includes/visit/with_metasploit_model_search_operation_base.rb +5 -5
- data/spec/support/shared/examples/metasploit_data_models/search/visitor/where/visit/with_equality.rb +3 -3
- data/spec/support/shared/examples/metasploit_data_models/search/visitor/where/visit/with_metasploit_model_search_group_base.rb +6 -7
- metadata +9 -67
- data/CHANGELOG.md +0 -6
- data/RELEASING.md +0 -88
- data/UPGRADING.md +0 -1
- data/lib/metasploit_data_models/automatic_exploitation.rb +0 -25
- data/spec/lib/metasploit_data_models_spec.rb +0 -4
|
@@ -1,15 +1,17 @@
|
|
|
1
|
-
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
describe Mdm::HostTag do
|
|
2
4
|
it_should_behave_like 'Metasploit::Concern.run'
|
|
3
5
|
|
|
4
6
|
context 'associations' do
|
|
5
|
-
it {
|
|
6
|
-
it {
|
|
7
|
+
it { should belong_to(:host).class_name('Mdm::Host') }
|
|
8
|
+
it { should belong_to(:tag).class_name('Mdm::Tag') }
|
|
7
9
|
end
|
|
8
10
|
|
|
9
11
|
context 'database' do
|
|
10
12
|
context 'columns' do
|
|
11
|
-
it {
|
|
12
|
-
it {
|
|
13
|
+
it { should have_db_column(:host_id).of_type(:integer) }
|
|
14
|
+
it { should have_db_column(:tag_id).of_type(:integer) }
|
|
13
15
|
end
|
|
14
16
|
end
|
|
15
17
|
|
|
@@ -19,7 +21,7 @@ RSpec.describe Mdm::HostTag, type: :model do
|
|
|
19
21
|
FactoryGirl.build(:mdm_host_tag)
|
|
20
22
|
end
|
|
21
23
|
|
|
22
|
-
it {
|
|
24
|
+
it { should be_valid }
|
|
23
25
|
end
|
|
24
26
|
end
|
|
25
27
|
|
|
@@ -1,35 +1,37 @@
|
|
|
1
|
-
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
describe Mdm::Listener do
|
|
2
4
|
it_should_behave_like 'Metasploit::Concern.run'
|
|
3
5
|
|
|
4
6
|
context 'associations' do
|
|
5
|
-
it {
|
|
6
|
-
it {
|
|
7
|
+
it { should belong_to(:workspace).class_name('Mdm::Workspace') }
|
|
8
|
+
it { should belong_to(:task).class_name('Mdm::Task') }
|
|
7
9
|
end
|
|
8
10
|
|
|
9
11
|
context 'database' do
|
|
10
12
|
|
|
11
13
|
context 'timestamps'do
|
|
12
|
-
it {
|
|
13
|
-
it {
|
|
14
|
+
it { should have_db_column(:created_at).of_type(:datetime).with_options(:null => false) }
|
|
15
|
+
it { should have_db_column(:updated_at).of_type(:datetime).with_options(:null => false) }
|
|
14
16
|
end
|
|
15
17
|
|
|
16
18
|
context 'columns' do
|
|
17
|
-
it {
|
|
18
|
-
it {
|
|
19
|
-
it {
|
|
20
|
-
it {
|
|
21
|
-
it {
|
|
22
|
-
it {
|
|
23
|
-
it {
|
|
24
|
-
it {
|
|
25
|
-
it {
|
|
19
|
+
it { should have_db_column(:workspace_id).of_type(:integer).with_options(:null => false, :default =>1) }
|
|
20
|
+
it { should have_db_column(:task_id).of_type(:integer) }
|
|
21
|
+
it { should have_db_column(:enabled).of_type(:boolean).with_options(:default => true) }
|
|
22
|
+
it { should have_db_column(:owner).of_type(:text) }
|
|
23
|
+
it { should have_db_column(:payload).of_type(:text) }
|
|
24
|
+
it { should have_db_column(:address).of_type(:text) }
|
|
25
|
+
it { should have_db_column(:port).of_type(:integer) }
|
|
26
|
+
it { should have_db_column(:options).of_type(:binary) }
|
|
27
|
+
it { should have_db_column(:macro).of_type(:text) }
|
|
26
28
|
end
|
|
27
29
|
end
|
|
28
30
|
|
|
29
31
|
context 'factory' do
|
|
30
32
|
it 'should be valid' do
|
|
31
33
|
listener = FactoryGirl.build(:mdm_listener)
|
|
32
|
-
|
|
34
|
+
listener.should be_valid
|
|
33
35
|
end
|
|
34
36
|
end
|
|
35
37
|
|
|
@@ -49,55 +51,55 @@ RSpec.describe Mdm::Listener, type: :model do
|
|
|
49
51
|
context 'port' do
|
|
50
52
|
it 'should require a port' do
|
|
51
53
|
portless_listener = FactoryGirl.build(:mdm_listener, :port => nil)
|
|
52
|
-
|
|
53
|
-
|
|
54
|
+
portless_listener.should_not be_valid
|
|
55
|
+
portless_listener.errors[:port].should include("can't be blank")
|
|
54
56
|
end
|
|
55
57
|
|
|
56
58
|
it 'should not be valid for out-of-range numbers' do
|
|
57
59
|
out_of_range = FactoryGirl.build(:mdm_listener, :port => 70000)
|
|
58
|
-
|
|
59
|
-
|
|
60
|
+
out_of_range.should_not be_valid
|
|
61
|
+
out_of_range.errors[:port].should include("is not included in the list")
|
|
60
62
|
end
|
|
61
63
|
|
|
62
64
|
it 'should not be valid for port 0' do
|
|
63
65
|
out_of_range = FactoryGirl.build(:mdm_listener, :port => 0)
|
|
64
|
-
|
|
65
|
-
|
|
66
|
+
out_of_range.should_not be_valid
|
|
67
|
+
out_of_range.errors[:port].should include("is not included in the list")
|
|
66
68
|
end
|
|
67
69
|
|
|
68
70
|
it 'should not be valid for decimal numbers' do
|
|
69
71
|
out_of_range = FactoryGirl.build(:mdm_listener, :port => 5.67)
|
|
70
|
-
|
|
71
|
-
|
|
72
|
+
out_of_range.should_not be_valid
|
|
73
|
+
out_of_range.errors[:port].should include("must be an integer")
|
|
72
74
|
end
|
|
73
75
|
|
|
74
76
|
it 'should not be valid for a negative number' do
|
|
75
77
|
out_of_range = FactoryGirl.build(:mdm_listener, :port => -8)
|
|
76
|
-
|
|
77
|
-
|
|
78
|
+
out_of_range.should_not be_valid
|
|
79
|
+
out_of_range.errors[:port].should include("is not included in the list")
|
|
78
80
|
end
|
|
79
81
|
end
|
|
80
82
|
|
|
81
83
|
context 'address' do
|
|
82
84
|
it 'should require an address' do
|
|
83
85
|
addressless_listener = FactoryGirl.build(:mdm_listener, :address => nil)
|
|
84
|
-
|
|
85
|
-
|
|
86
|
+
addressless_listener.should_not be_valid
|
|
87
|
+
addressless_listener.errors[:address].should include("can't be blank")
|
|
86
88
|
end
|
|
87
89
|
|
|
88
90
|
it 'should be valid for IPv4 format' do
|
|
89
91
|
ipv4_listener = FactoryGirl.build(:mdm_listener, :address => '192.168.1.120')
|
|
90
|
-
|
|
92
|
+
ipv4_listener.should be_valid
|
|
91
93
|
end
|
|
92
94
|
|
|
93
95
|
it 'should be valid for IPv6 format' do
|
|
94
96
|
ipv6_listener = FactoryGirl.build(:mdm_listener, :address => '2001:0db8:85a3:0000:0000:8a2e:0370:7334')
|
|
95
|
-
|
|
97
|
+
ipv6_listener.should be_valid
|
|
96
98
|
end
|
|
97
99
|
|
|
98
100
|
it 'should not be valid for strings not conforming to IPv4 or IPv6' do
|
|
99
101
|
invalid_listener = FactoryGirl.build(:mdm_listener, :address => '1234-fark')
|
|
100
|
-
|
|
102
|
+
invalid_listener.should_not be_valid
|
|
101
103
|
end
|
|
102
104
|
|
|
103
105
|
end
|
|
@@ -1,37 +1,39 @@
|
|
|
1
|
-
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
describe Mdm::Loot do
|
|
2
4
|
it_should_behave_like 'Metasploit::Concern.run'
|
|
3
5
|
|
|
4
6
|
context 'associations' do
|
|
5
|
-
it {
|
|
6
|
-
it {
|
|
7
|
-
it {
|
|
8
|
-
it {
|
|
7
|
+
it { should belong_to(:workspace).class_name('Mdm::Workspace') }
|
|
8
|
+
it { should belong_to(:service).class_name('Mdm::Service') }
|
|
9
|
+
it { should belong_to(:host).class_name('Mdm::Host') }
|
|
10
|
+
it { should belong_to(:module_run).class_name('MetasploitDataModels::ModuleRun') }
|
|
9
11
|
end
|
|
10
12
|
|
|
11
13
|
context 'database' do
|
|
12
14
|
|
|
13
15
|
context 'timestamps'do
|
|
14
|
-
it {
|
|
15
|
-
it {
|
|
16
|
+
it { should have_db_column(:created_at).of_type(:datetime).with_options(:null => false) }
|
|
17
|
+
it { should have_db_column(:updated_at).of_type(:datetime).with_options(:null => false) }
|
|
16
18
|
end
|
|
17
19
|
|
|
18
20
|
context 'columns' do
|
|
19
|
-
it {
|
|
20
|
-
it {
|
|
21
|
-
it {
|
|
22
|
-
it {
|
|
23
|
-
it {
|
|
24
|
-
it {
|
|
25
|
-
it {
|
|
26
|
-
it {
|
|
27
|
-
it {
|
|
21
|
+
it { should have_db_column(:workspace_id).of_type(:integer).with_options(:null => false, :default =>1) }
|
|
22
|
+
it { should have_db_column(:host_id).of_type(:integer) }
|
|
23
|
+
it { should have_db_column(:service_id).of_type(:integer) }
|
|
24
|
+
it { should have_db_column(:ltype).of_type(:string) }
|
|
25
|
+
it { should have_db_column(:path).of_type(:string) }
|
|
26
|
+
it { should have_db_column(:data).of_type(:text) }
|
|
27
|
+
it { should have_db_column(:content_type).of_type(:string) }
|
|
28
|
+
it { should have_db_column(:name).of_type(:text) }
|
|
29
|
+
it { should have_db_column(:info).of_type(:text) }
|
|
28
30
|
end
|
|
29
31
|
end
|
|
30
32
|
|
|
31
33
|
context 'factory' do
|
|
32
34
|
it 'should be valid' do
|
|
33
35
|
loot = FactoryGirl.build(:mdm_loot)
|
|
34
|
-
|
|
36
|
+
loot.should be_valid
|
|
35
37
|
end
|
|
36
38
|
end
|
|
37
39
|
|
|
@@ -51,17 +53,17 @@ RSpec.describe Mdm::Loot, type: :model do
|
|
|
51
53
|
context 'search' do
|
|
52
54
|
it 'should match on ltype' do
|
|
53
55
|
myloot = FactoryGirl.create(:mdm_loot, :ltype => 'find.this.ltype')
|
|
54
|
-
|
|
56
|
+
Mdm::Loot.search('find.this.ltype').should include(myloot)
|
|
55
57
|
end
|
|
56
58
|
|
|
57
59
|
it 'should match on name' do
|
|
58
60
|
myloot = FactoryGirl.create(:mdm_loot, :name => 'Find This')
|
|
59
|
-
|
|
61
|
+
Mdm::Loot.search('Find This').should include(myloot)
|
|
60
62
|
end
|
|
61
63
|
|
|
62
64
|
it 'should match on info' do
|
|
63
65
|
myloot = FactoryGirl.create(:mdm_loot, :info => 'Find This')
|
|
64
|
-
|
|
66
|
+
Mdm::Loot.search('Find This').should include(myloot)
|
|
65
67
|
end
|
|
66
68
|
end
|
|
67
69
|
end
|
|
@@ -70,7 +72,7 @@ RSpec.describe Mdm::Loot, type: :model do
|
|
|
70
72
|
context 'before_destroy' do
|
|
71
73
|
it 'should call #delete_file' do
|
|
72
74
|
myloot = FactoryGirl.create(:mdm_loot)
|
|
73
|
-
|
|
75
|
+
myloot.should_receive(:delete_file)
|
|
74
76
|
myloot.destroy
|
|
75
77
|
end
|
|
76
78
|
end
|
|
@@ -1,19 +1,21 @@
|
|
|
1
|
-
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
describe Mdm::Module::Action do
|
|
2
4
|
|
|
3
5
|
it_should_behave_like 'Metasploit::Concern.run'
|
|
4
6
|
|
|
5
7
|
context 'associations' do
|
|
6
|
-
it {
|
|
8
|
+
it { should belong_to(:detail).class_name('Mdm::Module::Detail') }
|
|
7
9
|
end
|
|
8
10
|
|
|
9
11
|
context 'database' do
|
|
10
12
|
context 'columns' do
|
|
11
|
-
it {
|
|
12
|
-
it {
|
|
13
|
+
it { should have_db_column(:detail_id).of_type(:integer) }
|
|
14
|
+
it { should have_db_column(:name).of_type(:text) }
|
|
13
15
|
end
|
|
14
16
|
|
|
15
17
|
context 'indices' do
|
|
16
|
-
it {
|
|
18
|
+
it { should have_db_index(:detail_id) }
|
|
17
19
|
end
|
|
18
20
|
end
|
|
19
21
|
|
|
@@ -23,17 +25,17 @@ RSpec.describe Mdm::Module::Action, type: :model do
|
|
|
23
25
|
FactoryGirl.build(:mdm_module_action)
|
|
24
26
|
end
|
|
25
27
|
|
|
26
|
-
it {
|
|
28
|
+
it { should be_valid }
|
|
27
29
|
end
|
|
28
30
|
end
|
|
29
31
|
|
|
30
32
|
context 'mass assignment security' do
|
|
31
|
-
it {
|
|
32
|
-
it {
|
|
33
|
+
it { should_not allow_mass_assignment_of(:detail_id) }
|
|
34
|
+
it { should allow_mass_assignment_of(:name) }
|
|
33
35
|
end
|
|
34
36
|
|
|
35
37
|
context 'validations' do
|
|
36
|
-
it {
|
|
37
|
-
it {
|
|
38
|
+
it { should validate_presence_of(:detail) }
|
|
39
|
+
it { should validate_presence_of(:name) }
|
|
38
40
|
end
|
|
39
41
|
end
|
|
@@ -1,19 +1,21 @@
|
|
|
1
|
-
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
describe Mdm::Module::Arch do
|
|
2
4
|
|
|
3
5
|
it_should_behave_like 'Metasploit::Concern.run'
|
|
4
6
|
|
|
5
7
|
context 'associations' do
|
|
6
|
-
it {
|
|
8
|
+
it { should belong_to(:detail).class_name('Mdm::Module::Detail') }
|
|
7
9
|
end
|
|
8
10
|
|
|
9
11
|
context 'database' do
|
|
10
12
|
context 'columns' do
|
|
11
|
-
it {
|
|
12
|
-
it {
|
|
13
|
+
it { should have_db_column(:detail_id).of_type(:integer) }
|
|
14
|
+
it { should have_db_column(:name).of_type(:text) }
|
|
13
15
|
end
|
|
14
16
|
|
|
15
17
|
context 'indices' do
|
|
16
|
-
it {
|
|
18
|
+
it { should have_db_index(:detail_id) }
|
|
17
19
|
end
|
|
18
20
|
end
|
|
19
21
|
|
|
@@ -23,17 +25,17 @@ RSpec.describe Mdm::Module::Arch, type: :model do
|
|
|
23
25
|
FactoryGirl.build(:mdm_module_arch)
|
|
24
26
|
end
|
|
25
27
|
|
|
26
|
-
it {
|
|
28
|
+
it { should be_valid }
|
|
27
29
|
end
|
|
28
30
|
end
|
|
29
31
|
|
|
30
32
|
context 'mass assignment security' do
|
|
31
|
-
it {
|
|
32
|
-
it {
|
|
33
|
+
it { should_not allow_mass_assignment_of(:detail_id) }
|
|
34
|
+
it { should allow_mass_assignment_of(:name) }
|
|
33
35
|
end
|
|
34
36
|
|
|
35
37
|
context 'validations' do
|
|
36
|
-
it {
|
|
37
|
-
it {
|
|
38
|
+
it { should validate_presence_of(:detail) }
|
|
39
|
+
it { should validate_presence_of(:name) }
|
|
38
40
|
end
|
|
39
41
|
end
|
|
@@ -1,20 +1,22 @@
|
|
|
1
|
-
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
describe Mdm::Module::Author do
|
|
2
4
|
|
|
3
5
|
it_should_behave_like 'Metasploit::Concern.run'
|
|
4
6
|
|
|
5
7
|
context 'associations' do
|
|
6
|
-
it {
|
|
8
|
+
it { should belong_to(:detail).class_name('Mdm::Module::Detail') }
|
|
7
9
|
end
|
|
8
10
|
|
|
9
11
|
context 'database' do
|
|
10
12
|
context 'columns' do
|
|
11
|
-
it {
|
|
12
|
-
it {
|
|
13
|
-
it {
|
|
13
|
+
it { should have_db_column(:detail_id).of_type(:integer) }
|
|
14
|
+
it { should have_db_column(:name).of_type(:text) }
|
|
15
|
+
it { should have_db_column(:email).of_type(:text) }
|
|
14
16
|
end
|
|
15
17
|
|
|
16
18
|
context 'indices' do
|
|
17
|
-
it {
|
|
19
|
+
it { should have_db_index(:detail_id) }
|
|
18
20
|
end
|
|
19
21
|
end
|
|
20
22
|
|
|
@@ -24,15 +26,8 @@ RSpec.describe Mdm::Module::Author, type: :model do
|
|
|
24
26
|
FactoryGirl.build :full_mdm_module_author
|
|
25
27
|
end
|
|
26
28
|
|
|
27
|
-
it {
|
|
28
|
-
|
|
29
|
-
context 'email' do
|
|
30
|
-
subject(:email) {
|
|
31
|
-
full_mdm_module_author.email
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
it { is_expected.not_to be_nil }
|
|
35
|
-
end
|
|
29
|
+
it { should be_valid }
|
|
30
|
+
its(:email) { should_not be_nil }
|
|
36
31
|
end
|
|
37
32
|
|
|
38
33
|
context 'mdm_module_author' do
|
|
@@ -40,19 +35,19 @@ RSpec.describe Mdm::Module::Author, type: :model do
|
|
|
40
35
|
FactoryGirl.build :mdm_module_author
|
|
41
36
|
end
|
|
42
37
|
|
|
43
|
-
it {
|
|
38
|
+
it { should be_valid }
|
|
44
39
|
end
|
|
45
40
|
end
|
|
46
41
|
|
|
47
42
|
context 'mass assignment security' do
|
|
48
|
-
it {
|
|
49
|
-
it {
|
|
50
|
-
it {
|
|
43
|
+
it { should_not allow_mass_assignment_of(:detail_id) }
|
|
44
|
+
it { should allow_mass_assignment_of(:email) }
|
|
45
|
+
it { should allow_mass_assignment_of(:name) }
|
|
51
46
|
end
|
|
52
47
|
|
|
53
48
|
context 'validations' do
|
|
54
|
-
it {
|
|
55
|
-
it {
|
|
56
|
-
it {
|
|
49
|
+
it { should validate_presence_of(:detail) }
|
|
50
|
+
it { should_not validate_presence_of(:email) }
|
|
51
|
+
it { should validate_presence_of(:name) }
|
|
57
52
|
end
|
|
58
53
|
end
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
describe Mdm::Module::Detail do
|
|
2
4
|
subject(:detail) do
|
|
3
5
|
FactoryGirl.build(
|
|
4
6
|
:mdm_module_detail,
|
|
@@ -48,13 +50,13 @@ RSpec.describe Mdm::Module::Detail, type: :model do
|
|
|
48
50
|
it_should_behave_like 'Metasploit::Concern.run'
|
|
49
51
|
|
|
50
52
|
context 'associations' do
|
|
51
|
-
it {
|
|
52
|
-
it {
|
|
53
|
-
it {
|
|
54
|
-
it {
|
|
55
|
-
it {
|
|
56
|
-
it {
|
|
57
|
-
it {
|
|
53
|
+
it { should have_many(:actions).class_name('Mdm::Module::Action').dependent(:destroy) }
|
|
54
|
+
it { should have_many(:archs).class_name('Mdm::Module::Arch').dependent(:destroy) }
|
|
55
|
+
it { should have_many(:authors).class_name('Mdm::Module::Author').dependent(:destroy) }
|
|
56
|
+
it { should have_many(:mixins).class_name('Mdm::Module::Mixin').dependent(:destroy) }
|
|
57
|
+
it { should have_many(:platforms).class_name('Mdm::Module::Platform').dependent(:destroy) }
|
|
58
|
+
it { should have_many(:refs).class_name('Mdm::Module::Ref').dependent(:destroy) }
|
|
59
|
+
it { should have_many(:targets).class_name('Mdm::Module::Target').dependent(:destroy) }
|
|
58
60
|
end
|
|
59
61
|
|
|
60
62
|
context 'CONSTANTS' do
|
|
@@ -63,29 +65,12 @@ RSpec.describe Mdm::Module::Detail, type: :model do
|
|
|
63
65
|
described_class::DIRECTORY_BY_TYPE
|
|
64
66
|
end
|
|
65
67
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
end
|
|
73
|
-
|
|
74
|
-
it "maps 'exploit' to 'exploits'" do
|
|
75
|
-
expect(directory_by_type['exploit']).to eq('exploits')
|
|
76
|
-
end
|
|
77
|
-
|
|
78
|
-
it "maps 'nop' to 'nops'" do
|
|
79
|
-
expect(directory_by_type['nop']).to eq('nops')
|
|
80
|
-
end
|
|
81
|
-
|
|
82
|
-
it "maps 'payload' to 'payloads'" do
|
|
83
|
-
expect(directory_by_type['payload']).to eq('payloads')
|
|
84
|
-
end
|
|
85
|
-
|
|
86
|
-
it "maps 'post' to 'post'" do
|
|
87
|
-
expect(directory_by_type['post']).to eq('post')
|
|
88
|
-
end
|
|
68
|
+
its(['auxiliary']) { should == 'auxiliary' }
|
|
69
|
+
its(['encoder']) { should == 'encoders' }
|
|
70
|
+
its(['exploit']) { should == 'exploits' }
|
|
71
|
+
its(['nop']) { should == 'nops' }
|
|
72
|
+
its(['payload']) { should == 'payloads' }
|
|
73
|
+
its(['post']) { should == 'post' }
|
|
89
74
|
end
|
|
90
75
|
|
|
91
76
|
context 'PRIVILEGES' do
|
|
@@ -94,8 +79,8 @@ RSpec.describe Mdm::Module::Detail, type: :model do
|
|
|
94
79
|
end
|
|
95
80
|
|
|
96
81
|
it 'should contain both Boolean values' do
|
|
97
|
-
|
|
98
|
-
|
|
82
|
+
privileges.should include(false)
|
|
83
|
+
privileges.should include(true)
|
|
99
84
|
end
|
|
100
85
|
end
|
|
101
86
|
|
|
@@ -104,33 +89,13 @@ RSpec.describe Mdm::Module::Detail, type: :model do
|
|
|
104
89
|
described_class::RANK_BY_NAME
|
|
105
90
|
end
|
|
106
91
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
it "maps 'Average' to 200" do
|
|
116
|
-
expect(rank_by_name['Average']).to eq(200)
|
|
117
|
-
end
|
|
118
|
-
|
|
119
|
-
it "maps 'Normal' to 300" do
|
|
120
|
-
expect(rank_by_name['Normal']).to eq(300)
|
|
121
|
-
end
|
|
122
|
-
|
|
123
|
-
it "maps 'Good' to 400" do
|
|
124
|
-
expect(rank_by_name['Good']).to eq(400)
|
|
125
|
-
end
|
|
126
|
-
|
|
127
|
-
it "maps 'Great' to 500" do
|
|
128
|
-
expect(rank_by_name['Great']).to eq(500)
|
|
129
|
-
end
|
|
130
|
-
|
|
131
|
-
it "maps 'Excellent' to 600" do
|
|
132
|
-
expect(rank_by_name['Excellent']).to eq(600)
|
|
133
|
-
end
|
|
92
|
+
its(['Manual']) { should == 0 }
|
|
93
|
+
its(['Low']) { should == 100 }
|
|
94
|
+
its(['Average']) { should == 200 }
|
|
95
|
+
its(['Normal']) { should == 300 }
|
|
96
|
+
its(['Good']) { should == 400 }
|
|
97
|
+
its(['Great']) { should == 500 }
|
|
98
|
+
its(['Excellent']) { should == 600 }
|
|
134
99
|
end
|
|
135
100
|
|
|
136
101
|
context 'STANCES' do
|
|
@@ -138,34 +103,34 @@ RSpec.describe Mdm::Module::Detail, type: :model do
|
|
|
138
103
|
described_class::STANCES
|
|
139
104
|
end
|
|
140
105
|
|
|
141
|
-
it {
|
|
142
|
-
it {
|
|
106
|
+
it { should include('aggressive') }
|
|
107
|
+
it { should include('passive') }
|
|
143
108
|
end
|
|
144
109
|
end
|
|
145
110
|
|
|
146
111
|
context 'database' do
|
|
147
112
|
context 'columns' do
|
|
148
|
-
it {
|
|
149
|
-
it {
|
|
150
|
-
it {
|
|
151
|
-
it {
|
|
152
|
-
it {
|
|
153
|
-
it {
|
|
154
|
-
it {
|
|
155
|
-
it {
|
|
156
|
-
it {
|
|
157
|
-
it {
|
|
158
|
-
it {
|
|
159
|
-
it {
|
|
160
|
-
it {
|
|
161
|
-
it {
|
|
113
|
+
it { should have_db_column(:default_target).of_type(:integer) }
|
|
114
|
+
it { should have_db_column(:description).of_type(:text) }
|
|
115
|
+
it { should have_db_column(:disclosure_date).of_type(:datetime)}
|
|
116
|
+
it { should have_db_column(:file).of_type(:text) }
|
|
117
|
+
it { should have_db_column(:fullname).of_type(:text) }
|
|
118
|
+
it { should have_db_column(:license).of_type(:string) }
|
|
119
|
+
it { should have_db_column(:mtime).of_type(:datetime) }
|
|
120
|
+
it { should have_db_column(:mtype).of_type(:string) }
|
|
121
|
+
it { should have_db_column(:name).of_type(:text) }
|
|
122
|
+
it { should have_db_column(:privileged).of_type(:boolean) }
|
|
123
|
+
it { should have_db_column(:rank).of_type(:integer) }
|
|
124
|
+
it { should have_db_column(:ready).of_type(:boolean) }
|
|
125
|
+
it { should have_db_column(:refname).of_type(:text) }
|
|
126
|
+
it { should have_db_column(:stance).of_type(:string).with_options(:null => true) }
|
|
162
127
|
end
|
|
163
128
|
|
|
164
129
|
context 'indices' do
|
|
165
|
-
it {
|
|
166
|
-
it {
|
|
167
|
-
it {
|
|
168
|
-
it {
|
|
130
|
+
it { should have_db_index(:description) }
|
|
131
|
+
it { should have_db_index(:mtype) }
|
|
132
|
+
it { should have_db_index(:name) }
|
|
133
|
+
it { should have_db_index(:refname) }
|
|
169
134
|
end
|
|
170
135
|
end
|
|
171
136
|
|
|
@@ -175,7 +140,7 @@ RSpec.describe Mdm::Module::Detail, type: :model do
|
|
|
175
140
|
FactoryGirl.build(:mdm_module_detail)
|
|
176
141
|
end
|
|
177
142
|
|
|
178
|
-
it {
|
|
143
|
+
it { should be_valid }
|
|
179
144
|
|
|
180
145
|
context 'stance' do
|
|
181
146
|
subject(:mdm_module_detail) do
|
|
@@ -187,23 +152,10 @@ RSpec.describe Mdm::Module::Detail, type: :model do
|
|
|
187
152
|
'exploit'
|
|
188
153
|
end
|
|
189
154
|
|
|
190
|
-
it {
|
|
191
|
-
|
|
192
|
-
context '#stance' do
|
|
193
|
-
subject(:stance) {
|
|
194
|
-
mdm_module_detail.stance
|
|
195
|
-
}
|
|
155
|
+
it { should be_valid }
|
|
196
156
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
context '#supports_stance?' do
|
|
201
|
-
subject(:supports_stance?) {
|
|
202
|
-
mdm_module_detail.supports_stance?
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
it { is_expected.to eq(true) }
|
|
206
|
-
end
|
|
157
|
+
its(:stance) { should_not be_nil }
|
|
158
|
+
its(:supports_stance?) { should be_true }
|
|
207
159
|
end
|
|
208
160
|
|
|
209
161
|
context 'without supports_stance?' do
|
|
@@ -211,40 +163,27 @@ RSpec.describe Mdm::Module::Detail, type: :model do
|
|
|
211
163
|
'post'
|
|
212
164
|
end
|
|
213
165
|
|
|
214
|
-
it {
|
|
215
|
-
|
|
216
|
-
context '#stance' do
|
|
217
|
-
subject(:stance) {
|
|
218
|
-
mdm_module_detail.stance
|
|
219
|
-
}
|
|
166
|
+
it { should be_valid }
|
|
220
167
|
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
context '#supports_stance?' do
|
|
225
|
-
subject(:supports_stance?) {
|
|
226
|
-
mdm_module_detail.supports_stance?
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
it { is_expected.to eq(false) }
|
|
230
|
-
end
|
|
168
|
+
its(:stance) { should be_nil }
|
|
169
|
+
its(:supports_stance?) { should be_false }
|
|
231
170
|
end
|
|
232
171
|
end
|
|
233
172
|
end
|
|
234
173
|
end
|
|
235
174
|
|
|
236
175
|
context 'validations' do
|
|
237
|
-
it {
|
|
176
|
+
it { should ensure_inclusion_of(:mtype).in_array(types) }
|
|
238
177
|
|
|
239
178
|
# Because the boolean field will cast most strings to false,
|
|
240
179
|
# ensure_inclusion_of(:privileged).in_array([true, false]) will fail on the disallowed values check.
|
|
241
180
|
|
|
242
181
|
context 'rank' do
|
|
243
|
-
it {
|
|
244
|
-
it {
|
|
182
|
+
it { should validate_numericality_of(:rank).only_integer }
|
|
183
|
+
it { should ensure_inclusion_of(:rank).in_array(ranks) }
|
|
245
184
|
end
|
|
246
185
|
|
|
247
|
-
it {
|
|
186
|
+
it { should validate_presence_of(:refname) }
|
|
248
187
|
|
|
249
188
|
context 'stance' do
|
|
250
189
|
context 'mtype' do
|
|
@@ -286,13 +225,9 @@ RSpec.describe Mdm::Module::Detail, type: :model do
|
|
|
286
225
|
detail.actions.last
|
|
287
226
|
end
|
|
288
227
|
|
|
289
|
-
it {
|
|
228
|
+
it { should be_valid }
|
|
290
229
|
|
|
291
|
-
|
|
292
|
-
it 'is name passed to add_action' do
|
|
293
|
-
expect(module_action.name).to eq(name)
|
|
294
|
-
end
|
|
295
|
-
end
|
|
230
|
+
its(:name) { should == name }
|
|
296
231
|
end
|
|
297
232
|
end
|
|
298
233
|
|
|
@@ -318,13 +253,9 @@ RSpec.describe Mdm::Module::Detail, type: :model do
|
|
|
318
253
|
detail.archs.last
|
|
319
254
|
end
|
|
320
255
|
|
|
321
|
-
it {
|
|
256
|
+
it { should be_valid }
|
|
322
257
|
|
|
323
|
-
|
|
324
|
-
it 'is name passed to add_arch' do
|
|
325
|
-
expect(module_arch.name).to eq(name)
|
|
326
|
-
end
|
|
327
|
-
end
|
|
258
|
+
its(:name) { should == name }
|
|
328
259
|
end
|
|
329
260
|
end
|
|
330
261
|
|
|
@@ -355,19 +286,10 @@ RSpec.describe Mdm::Module::Detail, type: :model do
|
|
|
355
286
|
detail.authors.last
|
|
356
287
|
end
|
|
357
288
|
|
|
358
|
-
it {
|
|
289
|
+
it { should be_valid }
|
|
359
290
|
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
expect(module_author.email).to eq(email)
|
|
363
|
-
end
|
|
364
|
-
end
|
|
365
|
-
|
|
366
|
-
context '#name' do
|
|
367
|
-
it 'is name passed to add_author' do
|
|
368
|
-
expect(module_author.name).to eq(name)
|
|
369
|
-
end
|
|
370
|
-
end
|
|
291
|
+
its(:email) { should == email }
|
|
292
|
+
its(:name) { should == name }
|
|
371
293
|
end
|
|
372
294
|
end
|
|
373
295
|
|
|
@@ -389,21 +311,10 @@ RSpec.describe Mdm::Module::Detail, type: :model do
|
|
|
389
311
|
detail.authors.last
|
|
390
312
|
end
|
|
391
313
|
|
|
392
|
-
it {
|
|
393
|
-
|
|
394
|
-
context '#email' do
|
|
395
|
-
subject(:module_author_email) {
|
|
396
|
-
module_author.email
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
it { is_expected.to be_nil }
|
|
400
|
-
end
|
|
314
|
+
it { should be_valid }
|
|
401
315
|
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
expect(module_author.name).to eq(name)
|
|
405
|
-
end
|
|
406
|
-
end
|
|
316
|
+
its(:email) { should be_nil }
|
|
317
|
+
its(:name) { should == name }
|
|
407
318
|
end
|
|
408
319
|
end
|
|
409
320
|
end
|
|
@@ -424,19 +335,14 @@ RSpec.describe Mdm::Module::Detail, type: :model do
|
|
|
424
335
|
end
|
|
425
336
|
|
|
426
337
|
context 'new Mdm::ModuleMixin' do
|
|
427
|
-
subject
|
|
338
|
+
subject do
|
|
428
339
|
add_mixin
|
|
429
340
|
|
|
430
341
|
detail.mixins.last
|
|
431
342
|
end
|
|
432
343
|
|
|
433
|
-
it {
|
|
434
|
-
|
|
435
|
-
context '#name' do
|
|
436
|
-
it 'is name passed to add_mixin' do
|
|
437
|
-
expect(mdm_module_mixin.name).to eq(name)
|
|
438
|
-
end
|
|
439
|
-
end
|
|
344
|
+
it { should be_valid }
|
|
345
|
+
its(:name) { should == name }
|
|
440
346
|
end
|
|
441
347
|
end
|
|
442
348
|
|
|
@@ -462,13 +368,8 @@ RSpec.describe Mdm::Module::Detail, type: :model do
|
|
|
462
368
|
detail.platforms.last
|
|
463
369
|
end
|
|
464
370
|
|
|
465
|
-
it {
|
|
466
|
-
|
|
467
|
-
context '#name' do
|
|
468
|
-
it 'is name passed to add_platform' do
|
|
469
|
-
expect(module_platform.name).to eq(name)
|
|
470
|
-
end
|
|
471
|
-
end
|
|
371
|
+
it { should be_valid }
|
|
372
|
+
its(:name) { should == name }
|
|
472
373
|
end
|
|
473
374
|
end
|
|
474
375
|
|
|
@@ -494,13 +395,8 @@ RSpec.describe Mdm::Module::Detail, type: :model do
|
|
|
494
395
|
detail.refs.last
|
|
495
396
|
end
|
|
496
397
|
|
|
497
|
-
it {
|
|
498
|
-
|
|
499
|
-
context '#name' do
|
|
500
|
-
it 'is name passed to add_ref' do
|
|
501
|
-
expect(module_ref.name).to eq(name)
|
|
502
|
-
end
|
|
503
|
-
end
|
|
398
|
+
it { should be_valid }
|
|
399
|
+
its(:name) { should == name }
|
|
504
400
|
end
|
|
505
401
|
end
|
|
506
402
|
|
|
@@ -530,13 +426,8 @@ RSpec.describe Mdm::Module::Detail, type: :model do
|
|
|
530
426
|
detail.targets.last
|
|
531
427
|
end
|
|
532
428
|
|
|
533
|
-
it {
|
|
534
|
-
|
|
535
|
-
context '#name' do
|
|
536
|
-
it 'is name passed to add_target' do
|
|
537
|
-
expect(module_target.name).to eq(name)
|
|
538
|
-
end
|
|
539
|
-
end
|
|
429
|
+
it { should be_valid }
|
|
430
|
+
its(:name) { should == name }
|
|
540
431
|
end
|
|
541
432
|
end
|
|
542
433
|
end
|