metasploit_data_models 0.7.0-java → 0.11.2-java
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +3 -0
- data/.travis.yml +1 -0
- data/app/models/mdm/host.rb +352 -26
- data/app/models/mdm/loot.rb +72 -7
- data/app/models/mdm/{module_action.rb → module/action.rb} +3 -3
- data/app/models/mdm/{module_arch.rb → module/arch.rb} +3 -3
- data/app/models/mdm/{module_author.rb → module/author.rb} +3 -3
- data/app/models/mdm/module/detail.rb +280 -0
- data/app/models/mdm/{module_mixin.rb → module/mixin.rb} +3 -3
- data/app/models/mdm/{module_platform.rb → module/platform.rb} +3 -3
- data/app/models/mdm/module/ref.rb +48 -0
- data/app/models/mdm/{module_target.rb → module/target.rb} +3 -3
- data/app/models/mdm/note.rb +61 -6
- data/app/models/mdm/ref.rb +39 -1
- data/app/models/mdm/service.rb +85 -7
- data/app/models/mdm/session.rb +100 -6
- data/app/models/mdm/vuln.rb +104 -24
- data/db/migrate/20130228214900_change_required_columns_to_null_false_in_web_vulns.rb +1 -17
- data/db/migrate/20130412154159_change_foreign_key_in_module_actions.rb +25 -0
- data/db/migrate/20130412171844_change_foreign_key_in_module_archs.rb +25 -0
- data/db/migrate/20130412173121_change_foreign_key_in_module_authors.rb +25 -0
- data/db/migrate/20130412173640_change_foreign_key_in_module_mixins.rb +25 -0
- data/db/migrate/20130412174254_change_foreign_key_in_module_platforms.rb +25 -0
- data/db/migrate/20130412174719_change_foreign_key_in_module_refs.rb +25 -0
- data/db/migrate/20130412175040_change_foreign_key_in_module_targets.rb +25 -0
- data/db/migrate/20130430151353_change_required_columns_to_null_false_in_hosts.rb +11 -0
- data/db/migrate/20130430162145_enforce_address_uniqueness_in_workspace_in_hosts.rb +23 -0
- data/lib/mdm/module.rb +4 -0
- data/lib/metasploit_data_models.rb +1 -0
- data/lib/metasploit_data_models/change_required_columns_to_null_false.rb +23 -0
- data/lib/metasploit_data_models/version.rb +1 -1
- data/spec/app/models/mdm/host_spec.rb +411 -0
- data/spec/app/models/mdm/host_tag_spec.rb +13 -0
- data/spec/app/models/mdm/{module_action_spec.rb → module/action_spec.rb} +6 -6
- data/spec/app/models/mdm/{module_arch_spec.rb → module/arch_spec.rb} +6 -6
- data/spec/app/models/mdm/{module_author_spec.rb → module/author_spec.rb} +6 -6
- data/spec/app/models/mdm/{module_detail_spec.rb → module/detail_spec.rb} +101 -11
- data/spec/app/models/mdm/{module_mixin_spec.rb → module/mixin_spec.rb} +6 -6
- data/spec/app/models/mdm/{module_platform_spec.rb → module/platform_spec.rb} +6 -6
- data/spec/app/models/mdm/module/ref_spec.rb +62 -0
- data/spec/app/models/mdm/{module_target_spec.rb → module/target_spec.rb} +6 -6
- data/spec/app/models/mdm/ref_spec.rb +62 -0
- data/spec/app/models/mdm/tag_spec.rb +13 -0
- data/spec/app/models/mdm/vuln_ref_spec.rb +13 -0
- data/spec/app/models/mdm/vuln_spec.rb +231 -0
- data/spec/dummy/db/schema.rb +20 -20
- data/spec/factories/mdm/host_tags.rb +9 -0
- data/spec/factories/mdm/hosts.rb +65 -0
- data/spec/factories/mdm/module/actions.rb +14 -0
- data/spec/factories/mdm/module/archs.rb +14 -0
- data/spec/factories/mdm/{module_authors.rb → module/authors.rb} +4 -4
- data/spec/factories/mdm/module/details.rb +66 -0
- data/spec/factories/mdm/module/mixins.rb +14 -0
- data/spec/factories/mdm/module/platforms.rb +14 -0
- data/spec/factories/mdm/module/refs.rb +14 -0
- data/spec/factories/mdm/{module_targets.rb → module/targets.rb} +3 -3
- data/spec/factories/mdm/refs.rb +9 -0
- data/spec/factories/mdm/tags.rb +14 -0
- data/spec/factories/mdm/vuln_refs.rb +4 -0
- data/spec/factories/mdm/vulns.rb +20 -0
- metadata +75 -42
- data/app/models/mdm/module_detail.rb +0 -59
- data/app/models/mdm/module_ref.rb +0 -24
- data/spec/app/models/mdm/module_ref_spec.rb +0 -38
- data/spec/factories/mdm/module_actions.rb +0 -14
- data/spec/factories/mdm/module_archs.rb +0 -14
- data/spec/factories/mdm/module_details.rb +0 -9
- data/spec/factories/mdm/module_mixins.rb +0 -14
- data/spec/factories/mdm/module_platforms.rb +0 -14
- data/spec/factories/mdm/module_refs.rb +0 -14
data/app/models/mdm/loot.rb
CHANGED
@@ -1,10 +1,6 @@
|
|
1
|
+
# Loot gathered from {#host} or {#service} such as files to prove you were on the system or to crack later to gain
|
2
|
+
# sessions on other machines in the network.
|
1
3
|
class Mdm::Loot < ActiveRecord::Base
|
2
|
-
#
|
3
|
-
# Callbacks
|
4
|
-
#
|
5
|
-
|
6
|
-
before_destroy :delete_file
|
7
|
-
|
8
4
|
#
|
9
5
|
# CONSTANTS
|
10
6
|
#
|
@@ -17,13 +13,78 @@ class Mdm::Loot < ActiveRecord::Base
|
|
17
13
|
]
|
18
14
|
|
19
15
|
#
|
20
|
-
#
|
16
|
+
# Associations
|
21
17
|
#
|
22
18
|
|
19
|
+
# @!attribute [rw] host
|
20
|
+
# The host from which the loot was gathered.
|
21
|
+
#
|
22
|
+
# @return [Mdm::Host]
|
23
23
|
belongs_to :host, :class_name => 'Mdm::Host'
|
24
|
+
|
25
|
+
# @!attribute [rw] service
|
26
|
+
# The service running on the {#host} from which the loot was gathered.
|
27
|
+
#
|
28
|
+
# @return [Mdm::Service]
|
24
29
|
belongs_to :service, :class_name => 'Mdm::Service'
|
30
|
+
|
31
|
+
# @!attribute [rw] workspace
|
32
|
+
# The workspace in which the loot is stored and the {#host} exists.
|
33
|
+
#
|
34
|
+
# @return [Mdm::Workspace]
|
25
35
|
belongs_to :workspace, :class_name => 'Mdm::Workspace'
|
26
36
|
|
37
|
+
#
|
38
|
+
# Attributes
|
39
|
+
#
|
40
|
+
|
41
|
+
# @!attribute [rw] content_type
|
42
|
+
# The mime/content type of the file at {#path}. Used to server the file correctly so browsers understand whether
|
43
|
+
# to render or download the file.
|
44
|
+
#
|
45
|
+
# @return [String]
|
46
|
+
|
47
|
+
# @!attribute [rw] created_at
|
48
|
+
# When the loot was created.
|
49
|
+
#
|
50
|
+
# @return [DateTime]
|
51
|
+
|
52
|
+
# @!attribute [rw] data
|
53
|
+
# Loot data not stored in file at {#path}.
|
54
|
+
#
|
55
|
+
# @return [String]
|
56
|
+
|
57
|
+
# @!attribute [rw] ltype
|
58
|
+
# The type of loot
|
59
|
+
#
|
60
|
+
# @return [String]
|
61
|
+
|
62
|
+
# @!attribute [rw] info
|
63
|
+
# Information about the loot.
|
64
|
+
#
|
65
|
+
# @return [String]
|
66
|
+
|
67
|
+
# @!attribute [rw] name
|
68
|
+
# The name of the loot.
|
69
|
+
#
|
70
|
+
# @return [String]
|
71
|
+
|
72
|
+
# @!attribute [rw] path
|
73
|
+
# The on-disk path to the loot file.
|
74
|
+
#
|
75
|
+
# @return [String]
|
76
|
+
|
77
|
+
# @!attribute [rw] updated_at
|
78
|
+
# The last time the loot was updated.
|
79
|
+
#
|
80
|
+
# @return [DateTime]
|
81
|
+
|
82
|
+
#
|
83
|
+
# Callbacks
|
84
|
+
#
|
85
|
+
|
86
|
+
before_destroy :delete_file
|
87
|
+
|
27
88
|
#
|
28
89
|
# Scopes
|
29
90
|
#
|
@@ -49,6 +110,10 @@ class Mdm::Loot < ActiveRecord::Base
|
|
49
110
|
|
50
111
|
private
|
51
112
|
|
113
|
+
# Deletes {#path} from disk.
|
114
|
+
#
|
115
|
+
# @todo https://www.pivotaltracker.com/story/show/49023795
|
116
|
+
# @return [void]
|
52
117
|
def delete_file
|
53
118
|
c = Pro::Client.get rescue nil
|
54
119
|
if c
|
@@ -1,11 +1,11 @@
|
|
1
|
-
class Mdm::
|
1
|
+
class Mdm::Module::Action < ActiveRecord::Base
|
2
2
|
self.table_name = 'module_actions'
|
3
3
|
|
4
4
|
#
|
5
5
|
# Associations
|
6
6
|
#
|
7
7
|
|
8
|
-
belongs_to :
|
8
|
+
belongs_to :detail, :class_name => 'Mdm::Module::Detail'
|
9
9
|
|
10
10
|
#
|
11
11
|
# Mass Assignment Security
|
@@ -17,7 +17,7 @@ class Mdm::ModuleAction < ActiveRecord::Base
|
|
17
17
|
# Validations
|
18
18
|
#
|
19
19
|
|
20
|
-
validates :
|
20
|
+
validates :detail, :presence => true
|
21
21
|
validates :name, :presence => true
|
22
22
|
|
23
23
|
ActiveSupport.run_load_hooks(:mdm_module_action, self)
|
@@ -1,11 +1,11 @@
|
|
1
|
-
class Mdm::
|
1
|
+
class Mdm::Module::Arch < ActiveRecord::Base
|
2
2
|
self.table_name = 'module_archs'
|
3
3
|
|
4
4
|
#
|
5
5
|
# Associations
|
6
6
|
#
|
7
7
|
|
8
|
-
belongs_to :
|
8
|
+
belongs_to :detail, :class_name => 'Mdm::Module::Detail'
|
9
9
|
|
10
10
|
#
|
11
11
|
# Mass Assignment Security
|
@@ -17,7 +17,7 @@ class Mdm::ModuleArch < ActiveRecord::Base
|
|
17
17
|
# Validations
|
18
18
|
#
|
19
19
|
|
20
|
-
validates :
|
20
|
+
validates :detail, :presence => true
|
21
21
|
validates :name, :presence => true
|
22
22
|
|
23
23
|
ActiveSupport.run_load_hooks(:mdm_module_arch, self)
|
@@ -1,11 +1,11 @@
|
|
1
|
-
class Mdm::
|
1
|
+
class Mdm::Module::Author < ActiveRecord::Base
|
2
2
|
self.table_name = 'module_authors'
|
3
3
|
|
4
4
|
#
|
5
5
|
# Associations
|
6
6
|
#
|
7
7
|
|
8
|
-
belongs_to :
|
8
|
+
belongs_to :detail, :class_name => 'Mdm::Module::Detail'
|
9
9
|
|
10
10
|
#
|
11
11
|
# Mass Assignment Security
|
@@ -18,7 +18,7 @@ class Mdm::ModuleAuthor < ActiveRecord::Base
|
|
18
18
|
# Validations
|
19
19
|
#
|
20
20
|
|
21
|
-
validates :
|
21
|
+
validates :detail, :presence => true
|
22
22
|
validates :name, :presence => true
|
23
23
|
|
24
24
|
ActiveSupport.run_load_hooks(:mdm_module_author, self)
|
@@ -0,0 +1,280 @@
|
|
1
|
+
# Details about an Msf::Module. Metadata that can be an array is stored in associations in modules under the
|
2
|
+
# {Mdm::Module} namespace.
|
3
|
+
class Mdm::Module::Detail < ActiveRecord::Base
|
4
|
+
self.table_name = 'module_details'
|
5
|
+
|
6
|
+
#
|
7
|
+
# CONSTANTS
|
8
|
+
#
|
9
|
+
|
10
|
+
# The directory for a given {#mtype} is a not always the pluralization of {#mtype}, so this maps the {#mtype} to the
|
11
|
+
# type directory that is used to generate the {#file} from the {#mtype} and {#refname}.
|
12
|
+
DIRECTORY_BY_TYPE = {
|
13
|
+
'auxiliary' => 'auxiliary',
|
14
|
+
'encoder' => 'encoders',
|
15
|
+
'exploit' => 'exploits',
|
16
|
+
'nop' => 'nops',
|
17
|
+
'payload' => 'payloads',
|
18
|
+
'post' => 'post'
|
19
|
+
}
|
20
|
+
|
21
|
+
# {#privileged} is Boolean so, valid values are just `true` and `false`, but since both the validation and
|
22
|
+
# factory need an array of valid values, this constant exists.
|
23
|
+
PRIVILEGES = [
|
24
|
+
false,
|
25
|
+
true
|
26
|
+
]
|
27
|
+
|
28
|
+
# Converts {#rank}, which is an Integer, to the name used for that rank.
|
29
|
+
RANK_BY_NAME = {
|
30
|
+
'Manual' => 0,
|
31
|
+
'Low' => 100,
|
32
|
+
'Average' => 200,
|
33
|
+
'Normal' => 300,
|
34
|
+
'Good' => 400,
|
35
|
+
'Great' => 500,
|
36
|
+
'Excellent' => 600
|
37
|
+
}
|
38
|
+
|
39
|
+
# Valid values for {#stance}.
|
40
|
+
STANCES = [
|
41
|
+
'aggressive',
|
42
|
+
'passive'
|
43
|
+
]
|
44
|
+
|
45
|
+
#
|
46
|
+
# Associations
|
47
|
+
#
|
48
|
+
|
49
|
+
# @!attribute [rw] actions
|
50
|
+
# Auxiliary actions to perform when this running this module.
|
51
|
+
#
|
52
|
+
# @return [Array<Mdm::Module::Action>]
|
53
|
+
has_many :actions, :class_name => 'Mdm::Module::Action', :dependent => :destroy
|
54
|
+
|
55
|
+
# @!attribute [rw] archs
|
56
|
+
# Architectures supported by this module.
|
57
|
+
#
|
58
|
+
# @return [Array<Mdm::Module::Arch>]
|
59
|
+
has_many :archs, :class_name => 'Mdm::Module::Arch', :dependent => :destroy
|
60
|
+
|
61
|
+
# @!attribute [rw] authors
|
62
|
+
# Authors (and their emails) of this module. Usually includes the original discoverer who wrote the
|
63
|
+
# proof-of-concept and then the people that ported the proof-of-concept to metasploit-framework.
|
64
|
+
#
|
65
|
+
# @return [Array<Mdm::Module::Mixin>]
|
66
|
+
has_many :authors, :class_name => 'Mdm::Module::Author', :dependent => :destroy
|
67
|
+
|
68
|
+
# @!attribute [rw] mixins
|
69
|
+
# Mixins used by this module.
|
70
|
+
#
|
71
|
+
# @return [Array<Mdm::Module::Mixin>]
|
72
|
+
has_many :mixins, :class_name => 'Mdm::Module::Mixin', :dependent => :destroy
|
73
|
+
|
74
|
+
# @!attribute [rw] platforms
|
75
|
+
# Platforms supported by this module.
|
76
|
+
#
|
77
|
+
# @return [Array<Mdm::Module::Platform>]
|
78
|
+
has_many :platforms, :class_name => 'Mdm::Module::Platform', :dependent => :destroy
|
79
|
+
|
80
|
+
# @!attribute [rw] refs
|
81
|
+
# External references to the vulnerabilities this module exploits.
|
82
|
+
#
|
83
|
+
# @return [Array<Mdm::Module::Ref>]
|
84
|
+
has_many :refs, :class_name => 'Mdm::Module::Ref', :dependent => :destroy
|
85
|
+
|
86
|
+
# @!attribute [rw] targets
|
87
|
+
# Names of targets with different configurations that can be exploited by this module.
|
88
|
+
#
|
89
|
+
# @return [Array<Mdm::Module::Target>]
|
90
|
+
has_many :targets, :class_name => 'Mdm::Module::Target', :dependent => :destroy
|
91
|
+
|
92
|
+
#
|
93
|
+
# Attributes
|
94
|
+
#
|
95
|
+
|
96
|
+
# @!attribute [rw] default_action
|
97
|
+
# Name of the default action in {#actions}.
|
98
|
+
#
|
99
|
+
# @return [String] {Mdm::Module::Action#name}.
|
100
|
+
|
101
|
+
# @!attribute [rw] default_target
|
102
|
+
# Name of the default target in {#targets}.
|
103
|
+
#
|
104
|
+
# @return [String] {Mdm::Module::Target#name}.
|
105
|
+
|
106
|
+
# @!attribute [rw] description
|
107
|
+
# A long, paragraph description of what the module does.
|
108
|
+
#
|
109
|
+
# @return [String]
|
110
|
+
|
111
|
+
# @!attribute [rw] disclosure_date
|
112
|
+
# The date the vulnerability exploited by this module was disclosed to the public.
|
113
|
+
#
|
114
|
+
# @return [DateTime]
|
115
|
+
|
116
|
+
# @!attribute [rw] file
|
117
|
+
# The full path to the module file on-disk.
|
118
|
+
#
|
119
|
+
# @return [String]
|
120
|
+
|
121
|
+
# @!attribute [rw] fullname
|
122
|
+
# The full name of the module. The full name is "{#mtype}/{#refname}".
|
123
|
+
#
|
124
|
+
# @return [String]
|
125
|
+
|
126
|
+
# @!attribute [rw] license
|
127
|
+
# The name of the software license for the module's code.
|
128
|
+
#
|
129
|
+
# @return [String]
|
130
|
+
|
131
|
+
# @!attribute [rw] mtime
|
132
|
+
# The modification time of the module file on-disk.
|
133
|
+
#
|
134
|
+
# @return [DateTime]
|
135
|
+
|
136
|
+
# @!attribute [rw] mtype
|
137
|
+
# The type of the module.
|
138
|
+
#
|
139
|
+
# @return [String] key in {DIRECTORY_BY_TYPE}
|
140
|
+
|
141
|
+
# @!attribute [rw] name
|
142
|
+
# The human readable name of the module. It is unrelated to {#fullname} or {#refname} and is better thought of
|
143
|
+
# as a short summary of the {#description}.
|
144
|
+
#
|
145
|
+
# @return [String]
|
146
|
+
|
147
|
+
# @!attribute [rw] privileged
|
148
|
+
# Whether this module requires priveleged access to run.
|
149
|
+
#
|
150
|
+
# @return [Boolean]
|
151
|
+
|
152
|
+
# @!attribute [rw] rank
|
153
|
+
# The reliability of the module and likelyhood that the module won't knock over the service or host being exploited.
|
154
|
+
# Bigger values is better.
|
155
|
+
#
|
156
|
+
# @return [Integer]
|
157
|
+
|
158
|
+
# @!attribute [rw] ready
|
159
|
+
# Boolean indicating whether the metadata for the module has been updated from the on-disk module.
|
160
|
+
#
|
161
|
+
# @return [false] if the associations are still being updated.
|
162
|
+
# @return [true] if this detail and its associations are up-to-date.
|
163
|
+
|
164
|
+
# @!attribute [rw] refname
|
165
|
+
# The reference name of the module.
|
166
|
+
#
|
167
|
+
# @return [String]
|
168
|
+
|
169
|
+
# @!attribute [rw] stance
|
170
|
+
# Whether the module is active or passive.
|
171
|
+
#
|
172
|
+
# @return ['active', 'passive']
|
173
|
+
|
174
|
+
#
|
175
|
+
# Validations
|
176
|
+
#
|
177
|
+
|
178
|
+
validates :mtype,
|
179
|
+
:inclusion => {
|
180
|
+
:in => DIRECTORY_BY_TYPE.keys
|
181
|
+
}
|
182
|
+
validates :privileged,
|
183
|
+
:inclusion => {
|
184
|
+
:in => PRIVILEGES
|
185
|
+
}
|
186
|
+
validates :rank,
|
187
|
+
:inclusion => {
|
188
|
+
:in => RANK_BY_NAME.values
|
189
|
+
},
|
190
|
+
:numericality => {
|
191
|
+
:only_integer => true
|
192
|
+
}
|
193
|
+
validates :refname, :presence => true
|
194
|
+
validates :stance,
|
195
|
+
:inclusion => {
|
196
|
+
:in => STANCES
|
197
|
+
}
|
198
|
+
|
199
|
+
validates_associated :actions
|
200
|
+
validates_associated :archs
|
201
|
+
validates_associated :authors
|
202
|
+
validates_associated :mixins
|
203
|
+
validates_associated :platforms
|
204
|
+
validates_associated :refs
|
205
|
+
validates_associated :targets
|
206
|
+
|
207
|
+
# Adds an {Mdm::Module::Action} with the given {Mdm::Module::Action#name} to {#actions} and immediately saves it to
|
208
|
+
# the database.
|
209
|
+
#
|
210
|
+
# @param name [String] {Mdm::Module::Action#name}.
|
211
|
+
# @return [true] if save was successful.
|
212
|
+
# @return [false] if save was unsucessful.
|
213
|
+
def add_action(name)
|
214
|
+
self.actions.build(:name => name).save
|
215
|
+
end
|
216
|
+
|
217
|
+
# Adds an {Mdm::Module::Arch} with the given {Mdm::Module::Arch#name} to {#archs} and immediately saves it to the
|
218
|
+
# database.
|
219
|
+
#
|
220
|
+
# @param name [String] {Mdm::Module::Arch#name}.
|
221
|
+
# @return [true] if save was successful.
|
222
|
+
# @return [false] if save was unsuccessful.
|
223
|
+
def add_arch(name)
|
224
|
+
self.archs.build(:name => name).save
|
225
|
+
end
|
226
|
+
|
227
|
+
# Adds an {Mdm::Module::Author} with the given {Mdm::Module::Author#name} and {Mdm::Module::Author#email} to
|
228
|
+
# {#authors} and immediately saves it to the database.
|
229
|
+
#
|
230
|
+
# @param name [String] {Mdm::Module::Author#name}.
|
231
|
+
# @param email [String] {Mdm::Module::Author#email}.
|
232
|
+
# @return [true] if save was successful.
|
233
|
+
# @return [false] if save was unsuccessful.
|
234
|
+
def add_author(name, email=nil)
|
235
|
+
self.authors.build(:name => name, :email => email).save
|
236
|
+
end
|
237
|
+
|
238
|
+
# Adds an {Mdm::Module::Mixin} with the given {Mdm::Module::Mixin#name} to {#mixins} and immediately saves it to the
|
239
|
+
# database.
|
240
|
+
#
|
241
|
+
# @param name [String] {Mdm::Module::Mixin#name}.
|
242
|
+
# @return [true] if save was successful.
|
243
|
+
# @return [false] if save was unsuccessful.
|
244
|
+
def add_mixin(name)
|
245
|
+
self.mixins.build(:name => name).save
|
246
|
+
end
|
247
|
+
|
248
|
+
# Adds an {Mdm::Module::Platform} with the given {Mdm::Module::Platform#name} to {#platforms} and immediately saves it
|
249
|
+
# to the database.
|
250
|
+
#
|
251
|
+
# @param name [String] {Mdm::Module::Platform#name}.
|
252
|
+
# @return [true] if save was successful.
|
253
|
+
# @return [false] if save was unsuccessful.
|
254
|
+
def add_platform(name)
|
255
|
+
self.platforms.build(:name => name).save
|
256
|
+
end
|
257
|
+
|
258
|
+
# Adds an {Mdm::Module::Ref} with the given {Mdm::Module::Ref#name} to {#refs} and immediately saves it to the
|
259
|
+
# database.
|
260
|
+
#
|
261
|
+
# @param name [String] {Mdm::Module::Ref#name}.
|
262
|
+
# @return [true] if save was successful.
|
263
|
+
# @return [false] if save was unsuccessful.
|
264
|
+
def add_ref(name)
|
265
|
+
self.refs.build(:name => name).save
|
266
|
+
end
|
267
|
+
|
268
|
+
# Adds an {Mdm::Module::Target} with the given {Mdm::Module::Target#index} and {Mdm::Module::Target#name} to
|
269
|
+
# {#targets} and immediately saves it to the database.
|
270
|
+
#
|
271
|
+
# @param index [Integer] index of target among other {#targets}.
|
272
|
+
# @param name [String] {Mdm::Module::Target#name}.
|
273
|
+
# @return [true] if save was successful.
|
274
|
+
# @return [false] if save was unsuccessful.
|
275
|
+
def add_target(index, name)
|
276
|
+
self.targets.build(:index => index, :name => name).save
|
277
|
+
end
|
278
|
+
|
279
|
+
ActiveSupport.run_load_hooks(:mdm_module_detail, self)
|
280
|
+
end
|