dradis-projects 3.0.1 → 3.6.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.md +10 -0
- data/app/controllers/dradis/plugins/projects/packages_controller.rb +5 -9
- data/app/controllers/dradis/plugins/projects/templates_controller.rb +5 -7
- data/dradis-projects.gemspec +1 -1
- data/lib/dradis/plugins/projects.rb +4 -0
- data/lib/dradis/plugins/projects/engine.rb +10 -1
- data/lib/dradis/plugins/projects/export/package.rb +11 -6
- data/lib/dradis/plugins/projects/export/template.rb +10 -155
- data/lib/dradis/plugins/projects/export/v1/template.rb +167 -0
- data/lib/dradis/plugins/projects/gem_version.rb +2 -2
- data/lib/dradis/plugins/projects/upload/package.rb +11 -11
- data/lib/dradis/plugins/projects/upload/template.rb +37 -313
- data/lib/dradis/plugins/projects/upload/v1/template.rb +388 -0
- data/lib/tasks/thorfile.rb +25 -13
- metadata +7 -4
@@ -21,7 +21,7 @@ module Dradis::Plugins::Projects::Upload
|
|
21
21
|
FileUtils.mkdir Rails.root.join('tmp', 'zip')
|
22
22
|
|
23
23
|
begin
|
24
|
-
logger.info { 'Uncompressing the file' }
|
24
|
+
logger.info { 'Uncompressing the file...' }
|
25
25
|
#TODO: this could be improved by only uncompressing the XML, then parsing
|
26
26
|
# it to get the node_lookup table and then uncompressing each entry to its
|
27
27
|
# final destination
|
@@ -33,19 +33,18 @@ module Dradis::Plugins::Projects::Upload
|
|
33
33
|
end
|
34
34
|
logger.info { 'Done.' }
|
35
35
|
|
36
|
-
|
36
|
+
|
37
|
+
logger.info { 'Loading XML template file...' }
|
38
|
+
template_file = Rails.root.join('tmp', 'zip', 'dradis-repository.xml')
|
37
39
|
importer = Template::Importer.new(
|
38
|
-
|
39
|
-
logger: logger,
|
40
|
-
template_service: template_service
|
41
|
-
)
|
42
|
-
node_lookup = importer.import(
|
43
|
-
file: Rails.root.
|
44
|
-
join('tmp', 'zip', 'dradis-repository.xml')
|
40
|
+
options.merge plugin: Dradis::Plugins::Projects::Upload::Template
|
45
41
|
)
|
42
|
+
lookup_table = importer.import(file: template_file)
|
43
|
+
logger.info { 'Done.' }
|
46
44
|
|
47
|
-
|
48
|
-
|
45
|
+
|
46
|
+
logger.info { 'Moving attachments to their final destinations...' }
|
47
|
+
lookup_table[:nodes].each do |oldid,newid|
|
49
48
|
if File.directory? Rails.root.join('tmp', 'zip', oldid)
|
50
49
|
FileUtils.mkdir_p Attachment.pwd.join(newid.to_s)
|
51
50
|
|
@@ -54,6 +53,7 @@ module Dradis::Plugins::Projects::Upload
|
|
54
53
|
end
|
55
54
|
end
|
56
55
|
end
|
56
|
+
logger.info { 'Done.' }
|
57
57
|
|
58
58
|
success = true
|
59
59
|
rescue Exception => e
|
@@ -10,6 +10,7 @@ module Dradis::Plugins::Projects::Upload
|
|
10
10
|
end
|
11
11
|
|
12
12
|
class Importer < Dradis::Plugins::Upload::Importer
|
13
|
+
attr_accessor :lookup_table, :template_version
|
13
14
|
|
14
15
|
# The import method is invoked by the framework to process a template file
|
15
16
|
# that has just been uploaded using the 'Import from file...' dialog.
|
@@ -39,326 +40,49 @@ module Dradis::Plugins::Projects::Upload
|
|
39
40
|
return false
|
40
41
|
end
|
41
42
|
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
node_lookup = {}
|
48
|
-
# and to issues
|
49
|
-
issue_lookup = {}
|
50
|
-
|
51
|
-
# evidence is parsed when nodes are parsed, but cannot be saved until issues
|
52
|
-
# have been created. Therefore, parse evidence into arrays until time for
|
53
|
-
# creation
|
54
|
-
evidence_array = []
|
55
|
-
|
56
|
-
# likewise we also need to hold on to the XML about evidence activities
|
57
|
-
# until after the evidence has been saved
|
58
|
-
evidence_activity_xml_array = []
|
59
|
-
|
60
|
-
# all children nodes, we will need to find the new ID of their parents
|
61
|
-
orphan_nodes = []
|
62
|
-
|
63
|
-
# if the note has an attachment screenshot (i.e. !.../nodes/i/attachments/...!)
|
64
|
-
# we will fix the URL to point to the new Node ID.
|
65
|
-
#
|
66
|
-
# WARNING: we need a lookup table because one note may be referencing a
|
67
|
-
# different (yet unprocessed) node's attachments.
|
68
|
-
attachment_notes = []
|
69
|
-
|
70
|
-
# go through the categories, keep a translation table between the old
|
71
|
-
# category id and the new ones so we know to which category we should
|
72
|
-
# assign our notes
|
73
|
-
template.xpath('dradis-template/categories/category').each do |xml_category|
|
74
|
-
old_id = xml_category.at_xpath('id').text.strip
|
75
|
-
name = xml_category.at_xpath('name').text.strip
|
76
|
-
category = nil
|
77
|
-
|
78
|
-
# Prevent creating duplicate categories
|
79
|
-
logger.info { "Looking for category: #{name}" }
|
80
|
-
category = Category.find_or_create_by!(name: name)
|
81
|
-
category_lookup[old_id] = category.id
|
82
|
-
end
|
83
|
-
|
84
|
-
|
85
|
-
# ------------------------------------------------------------------- Nodes
|
86
|
-
# Re generate the Node tree structure
|
87
|
-
template.xpath('dradis-template/nodes/node').each do |xml_node|
|
88
|
-
element = xml_node.at_xpath('type-id')
|
89
|
-
type_id = element.text.nil? ? nil : element.text.strip
|
90
|
-
label = xml_node.at_xpath('label').text.strip
|
91
|
-
element = xml_node.at_xpath('parent-id')
|
92
|
-
parent_id = element.text.nil? ? nil : element.text.strip
|
93
|
-
|
94
|
-
# Node positions
|
95
|
-
element = xml_node.at_xpath('position')
|
96
|
-
position = (element && !element.text.nil?) ? element.text.strip : nil
|
97
|
-
|
98
|
-
# Node properties
|
99
|
-
element = xml_node.at_xpath('properties')
|
100
|
-
properties = (element && !element.text.blank?) ? element.text.strip : nil
|
101
|
-
|
102
|
-
created_at = xml_node.at_xpath('created-at')
|
103
|
-
updated_at = xml_node.at_xpath('updated-at')
|
104
|
-
|
105
|
-
logger.info { "New node detected: #{label}, parent_id: #{parent_id}, type_id: #{type_id}" }
|
106
|
-
|
107
|
-
# There is one exception to the rule, the Configuration.uploadsNode node,
|
108
|
-
# it does not make sense to have more than one of this nodes, in any
|
109
|
-
# given tree
|
110
|
-
node = nil
|
111
|
-
note = nil
|
112
|
-
evidence = nil
|
113
|
-
if (label == Configuration.plugin_uploads_node)
|
114
|
-
node = Node.create_with(type_id: type_id, parent_id: parent_id).
|
115
|
-
find_or_create_by!(label: label)
|
116
|
-
else
|
117
|
-
node = Node.create!(
|
118
|
-
type_id: type_id,
|
119
|
-
label: label,
|
120
|
-
parent_id: parent_id,
|
121
|
-
position: position
|
122
|
-
)
|
123
|
-
end
|
124
|
-
|
125
|
-
if properties
|
126
|
-
node.raw_properties = properties
|
127
|
-
end
|
128
|
-
|
129
|
-
node.update_attribute(:created_at, created_at.text.strip) if created_at
|
130
|
-
node.update_attribute(:updated_at, updated_at.text.strip) if updated_at
|
131
|
-
|
132
|
-
return false unless validate_and_save(node)
|
133
|
-
return false unless create_activities(node, xml_node)
|
134
|
-
|
135
|
-
xml_node.xpath('notes/note').each do |xml_note|
|
136
|
-
|
137
|
-
if xml_note.at_xpath('author') != nil
|
138
|
-
old_id = xml_note.at_xpath('category-id').text.strip
|
139
|
-
new_id = category_lookup[old_id]
|
140
|
-
|
141
|
-
created_at = xml_note.at_xpath('created-at')
|
142
|
-
updated_at = xml_note.at_xpath('updated-at')
|
143
|
-
|
144
|
-
logger.info { "Note category rewrite, used to be #{old_id}, now is #{new_id}" }
|
145
|
-
note = Note.create!(
|
146
|
-
author: xml_note.at_xpath('author').text.strip,
|
147
|
-
node_id: node.id,
|
148
|
-
category_id: new_id,
|
149
|
-
text: xml_note.at_xpath('text').text
|
150
|
-
)
|
151
|
-
|
152
|
-
note.update_attribute(:created_at, created_at.text.strip) if created_at
|
153
|
-
note.update_attribute(:updated_at, updated_at.text.strip) if updated_at
|
154
|
-
|
155
|
-
return false unless validate_and_save(note)
|
156
|
-
|
157
|
-
if note.text =~ %r{^!(.*)/nodes/(\d+)/attachments/(.+)!$}
|
158
|
-
attachment_notes << note
|
159
|
-
end
|
160
|
-
|
161
|
-
return false unless create_activities(note, xml_note)
|
162
|
-
|
163
|
-
logger.info { "\tNew note added detected." }
|
164
|
-
end
|
165
|
-
end
|
166
|
-
|
167
|
-
# Create array of evidence from xml input. Cannot store in DB until we
|
168
|
-
# have a new issue id
|
169
|
-
xml_node.xpath('evidence/evidence').each do |xml_evidence|
|
170
|
-
if xml_evidence.at_xpath('author') != nil
|
171
|
-
created_at = xml_evidence.at_xpath('created-at')
|
172
|
-
updated_at = xml_evidence.at_xpath('updated-at')
|
173
|
-
|
174
|
-
evidence = Evidence.new(
|
175
|
-
author: xml_evidence.at_xpath('author').text.strip,
|
176
|
-
node_id: node.id,
|
177
|
-
content: xml_evidence.at_xpath('content').text,
|
178
|
-
issue_id: xml_evidence.at_xpath('issue-id').text.strip
|
179
|
-
)
|
180
|
-
|
181
|
-
evidence.update_attribute(:created_at, created_at.text.strip) if created_at
|
182
|
-
evidence.update_attribute(:updated_at, updated_at.text.strip) if updated_at
|
183
|
-
evidence_array << evidence
|
184
|
-
|
185
|
-
evidence_activity_xml_array << xml_evidence.xpath("activities/activity")
|
186
|
-
|
187
|
-
logger.info { "\tNew evidence added." }
|
188
|
-
end
|
189
|
-
end
|
190
|
-
|
191
|
-
# keep track of reassigned ids
|
192
|
-
node_lookup[xml_node.at_xpath('id').text.strip] = node.id
|
193
|
-
|
194
|
-
if node.parent_id != nil
|
195
|
-
# keep track of orphaned nodes
|
196
|
-
orphan_nodes << node
|
197
|
-
end
|
198
|
-
end
|
199
|
-
|
200
|
-
|
201
|
-
# ------------------------------------------------------------------- Issues
|
202
|
-
issue = nil
|
203
|
-
issue_category = Category.issue
|
204
|
-
issue_library = Node.issue_library
|
205
|
-
# go through the issues, keep a translation table between the old
|
206
|
-
# issue id and the new ones. This is important for importing evidence
|
207
|
-
# Will need to adjust node ID after generating node structure
|
208
|
-
template.xpath('dradis-template/issues/issue').each do |xml_issue|
|
209
|
-
old_id = xml_issue.at_xpath('id').text.strip
|
210
|
-
|
211
|
-
# TODO: Need to find some way of checking for dups
|
212
|
-
# May be combination of text, category_id and created_at
|
213
|
-
issue = Issue.new
|
214
|
-
issue.author = xml_issue.at_xpath('author').text.strip
|
215
|
-
issue.text = xml_issue.at_xpath('text').text
|
216
|
-
issue.node = issue_library
|
217
|
-
issue.category = issue_category
|
218
|
-
|
219
|
-
return false unless validate_and_save(issue)
|
220
|
-
|
221
|
-
return false unless create_activities(issue, xml_issue)
|
222
|
-
|
223
|
-
if issue.text =~ %r{^!(.*)/nodes/(\d+)/attachments/(.+)!$}
|
224
|
-
attachment_notes << issue
|
225
|
-
end
|
226
|
-
|
227
|
-
issue_lookup[old_id] = issue.id
|
228
|
-
logger.info{ "New issue detected: #{issue.title}" }
|
229
|
-
end
|
230
|
-
|
231
|
-
# ----------------------------------------------------------- Methodologies
|
232
|
-
methodology_category = Category.default
|
233
|
-
methodology_library = Node.methodology_library
|
234
|
-
template.xpath('dradis-template/methodologies/methodology').each do |xml_methodology|
|
235
|
-
# FIXME: this is wrong in a few levels, we should be able to save a
|
236
|
-
# Methodology instance calling .save() but the current implementation
|
237
|
-
# of the model would consider this a 'methodology template' and not an
|
238
|
-
# instance.
|
239
|
-
#
|
240
|
-
# Also, methodology notes don't have a valid author, see
|
241
|
-
# MethodologiesController#create action (i.e. 'methodology builder' is
|
242
|
-
# used).
|
243
|
-
Note.create!(
|
244
|
-
author: 'methodology importer',
|
245
|
-
node_id: methodology_library.id,
|
246
|
-
category_id: methodology_category.id,
|
247
|
-
text: xml_methodology.at_xpath('text').text
|
248
|
-
)
|
249
|
-
end
|
250
|
-
|
251
|
-
# -------------------------------------------------------------------- Tags
|
252
|
-
template.xpath('dradis-template/tags/tag').each do |xml_tag|
|
253
|
-
name = xml_tag.at_xpath('name').text()
|
254
|
-
tag = Tag.find_or_create_by!(name: name)
|
255
|
-
@logger.info { "New tag detected: #{name}" }
|
256
|
-
|
257
|
-
xml_tag.xpath('./taggings/tagging').each do |xml_tagging|
|
258
|
-
old_taggable_id = xml_tagging.at_xpath('taggable-id').text()
|
259
|
-
taggable_type = xml_tagging.at_xpath('taggable-type').text()
|
260
|
-
|
261
|
-
new_taggable_id = case taggable_type
|
262
|
-
when 'Note'
|
263
|
-
issue_lookup[old_taggable_id]
|
264
|
-
end
|
265
|
-
|
266
|
-
Tagging.create! tag: tag, taggable_id: new_taggable_id, taggable_type: taggable_type
|
267
|
-
end
|
268
|
-
end
|
269
|
-
|
270
|
-
# ----------------------------------------------------------------- Wrap up
|
271
|
-
|
272
|
-
logger.info { "Wrapping up..." }
|
273
|
-
|
274
|
-
# Save the Evidence instance to the DB now that we have populated Issues
|
275
|
-
# the original issues
|
276
|
-
evidence_array.each_with_index do |evidence, i|
|
277
|
-
logger.info { "Setting issue_id for evidence" }
|
278
|
-
evidence.issue_id = issue_lookup[evidence.issue_id.to_s]
|
279
|
-
|
280
|
-
new_content = evidence.content.gsub(%r{^!(.*)/nodes/(\d+)/attachments/(.+)!$}) do |_|
|
281
|
-
"!%s/nodes/%d/attachments/%s!" % [$1, node_lookup[$2], $3]
|
282
|
-
end
|
283
|
-
evidence.content = new_content
|
284
|
-
|
285
|
-
return false unless validate_and_save(evidence)
|
286
|
-
|
287
|
-
evidence_activity_xml_array[i].each do |xml_activity|
|
288
|
-
return false unless create_activity(evidence, xml_activity)
|
289
|
-
end
|
290
|
-
end
|
291
|
-
|
292
|
-
# Fix relationships between nodes to ensure parents and childrens match
|
293
|
-
# with the new assigned :ids
|
294
|
-
orphan_nodes.each do |node|
|
295
|
-
logger.info { "Finding parent for orphaned node: #{node.label}. Former parent was #{node.parent_id}" }
|
296
|
-
node.parent_id = node_lookup[node.parent_id.to_s]
|
297
|
-
return false unless validate_and_save(node)
|
298
|
-
end
|
299
|
-
|
300
|
-
# Adjust attachment URLs for new Node IDs
|
301
|
-
attachment_notes.each do |note|
|
302
|
-
@logger.info{ "Adjusting screenshot URLs: Note ##{note.id}" }
|
303
|
-
new_text = note.text.gsub(%r{^!(.*)/nodes/(\d+)/attachments/(.+)!$}) do |_|
|
304
|
-
"!%s/nodes/%d/attachments/%s!" % [$1, node_lookup[$2], $3]
|
305
|
-
end
|
306
|
-
note.text = new_text
|
307
|
-
return false unless validate_and_save(note)
|
308
|
-
end
|
309
|
-
|
310
|
-
return node_lookup
|
311
|
-
end
|
312
|
-
|
313
|
-
private
|
314
|
-
|
315
|
-
def create_activities(trackable, xml_trackable)
|
316
|
-
xml_trackable.xpath('activities/activity').each do |xml_activity|
|
317
|
-
# if 'validate_and_save(activity)' returns false, it needs
|
318
|
-
# to bubble up to the 'import' method so we can stop execution
|
319
|
-
return false unless create_activity(trackable, xml_activity)
|
43
|
+
if template.xpath('/dradis-template').empty?
|
44
|
+
error = "The uploaded file doesn't look like a Dradis project template (/dradis-template)."
|
45
|
+
logger.fatal{ error }
|
46
|
+
content_service.create_note text: error
|
47
|
+
return false
|
320
48
|
end
|
321
|
-
end
|
322
|
-
|
323
|
-
def create_activity(trackable, xml_activity)
|
324
|
-
activity = trackable.activities.new(
|
325
|
-
action: xml_activity.at_xpath("action").text,
|
326
|
-
created_at: Time.at(xml_activity.at_xpath("created_at").text.to_i)
|
327
|
-
)
|
328
|
-
|
329
|
-
set_activity_user(activity, xml_activity.at_xpath("user_email").text)
|
330
49
|
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
else
|
338
|
-
activity.user_id = user_id_for_email(email)
|
339
|
-
end
|
50
|
+
# :options contains all the options we've received from the framework.
|
51
|
+
#
|
52
|
+
# See:
|
53
|
+
# Dradis::Plugins::Upload::Importer#initialize
|
54
|
+
Rails.application.config.dradis.projects.template_uploader.new(options)
|
55
|
+
.parse(template)
|
340
56
|
end
|
341
57
|
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
|
58
|
+
def parse(template)
|
59
|
+
@template_version = template.root[:version].try(:to_i) || 1
|
60
|
+
logger.info { "Parsing Dradis template version #{template_version.inspect}" }
|
61
|
+
|
62
|
+
parse_categories(template)
|
63
|
+
parse_nodes(template)
|
64
|
+
parse_issues(template)
|
65
|
+
parse_methodologies(template)
|
66
|
+
parse_tags(template)
|
67
|
+
finalize(template)
|
68
|
+
# FIXME: returning this is gross
|
69
|
+
lookup_table
|
70
|
+
rescue Exception => e
|
71
|
+
logger.fatal { e.message }
|
72
|
+
logger.fatal { e.backtrace } if Rails.env.development?
|
73
|
+
return false
|
351
74
|
end
|
352
75
|
|
353
|
-
|
354
|
-
if instance.save
|
355
|
-
return true
|
356
|
-
else
|
357
|
-
@logger.info{ "Malformed #{ instance.class.name } detected: #{ instance.errors.full_messages }" }
|
358
|
-
return false
|
359
|
-
end
|
360
|
-
end
|
76
|
+
private
|
361
77
|
|
78
|
+
def finalize(template); raise NotImplementedError; end
|
79
|
+
def parse_categories(template); raise NotImplementedError; end
|
80
|
+
def parse_issues(template); raise NotImplementedError; end
|
81
|
+
def parse_methodologies(template); raise NotImplementedError; end
|
82
|
+
def parse_nodes(template); raise NotImplementedError; end
|
83
|
+
def parse_tags(template); raise NotImplementedError; end
|
362
84
|
end
|
363
85
|
end
|
364
86
|
end
|
87
|
+
|
88
|
+
require_relative 'v1/template'
|
@@ -0,0 +1,388 @@
|
|
1
|
+
module Dradis::Plugins::Projects::Upload::V1
|
2
|
+
module Template
|
3
|
+
|
4
|
+
class Importer < Dradis::Plugins::Projects::Upload::Template::Importer
|
5
|
+
|
6
|
+
attr_accessor :attachment_notes, :logger, :pending_changes
|
7
|
+
|
8
|
+
def post_initialize(args={})
|
9
|
+
@lookup_table = {
|
10
|
+
categories: {},
|
11
|
+
nodes: {},
|
12
|
+
issues: {}
|
13
|
+
}
|
14
|
+
|
15
|
+
@pending_changes = {
|
16
|
+
# If the note has an attachment screenshot (i.e. !.../nodes/i/attachments/...!)
|
17
|
+
# we will fix the URL to point to the new Node ID.
|
18
|
+
#
|
19
|
+
# WARNING: we need a lookup table because one note may be referencing a
|
20
|
+
# different (yet unprocessed) node's attachments.
|
21
|
+
attachment_notes: [],
|
22
|
+
|
23
|
+
# evidence is parsed when nodes are parsed, but cannot be saved until
|
24
|
+
# issues have been created. Therefore, parse evidence into arrays until
|
25
|
+
# time for creation
|
26
|
+
evidence: [],
|
27
|
+
|
28
|
+
# likewise we also need to hold on to the XML about evidence activities
|
29
|
+
# until after the evidence has been saved
|
30
|
+
evidence_activity: [],
|
31
|
+
|
32
|
+
# all children nodes, we will need to find the ID of their new parents.
|
33
|
+
orphan_nodes: []
|
34
|
+
}
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
def create_activities(trackable, xml_trackable)
|
40
|
+
xml_trackable.xpath('activities/activity').each do |xml_activity|
|
41
|
+
# if 'validate_and_save(activity)' returns false, it needs
|
42
|
+
# to bubble up to the 'import' method so we can stop execution
|
43
|
+
return false unless create_activity(trackable, xml_activity)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def create_activity(trackable, xml_activity)
|
48
|
+
activity = trackable.activities.new(
|
49
|
+
action: xml_activity.at_xpath("action").text,
|
50
|
+
created_at: Time.at(xml_activity.at_xpath("created_at").text.to_i)
|
51
|
+
)
|
52
|
+
|
53
|
+
set_activity_user(activity, xml_activity.at_xpath("user_email").text)
|
54
|
+
|
55
|
+
validate_and_save(activity)
|
56
|
+
end
|
57
|
+
|
58
|
+
def finalize(template)
|
59
|
+
logger.info { 'Wrapping up...' }
|
60
|
+
|
61
|
+
finalize_evidence()
|
62
|
+
finalize_nodes()
|
63
|
+
finalize_attachments()
|
64
|
+
|
65
|
+
logger.info { 'Done.' }
|
66
|
+
end
|
67
|
+
|
68
|
+
def finalize_attachments
|
69
|
+
# Adjust attachment URLs for new Node IDs
|
70
|
+
pending_changes[:attachment_notes].each do |note|
|
71
|
+
|
72
|
+
logger.info { "Adjusting screenshot URLs: Note ##{note.id}" }
|
73
|
+
|
74
|
+
new_text = note.text.gsub(%r{^!(.*)/nodes/(\d+)/attachments/(.+)!$}) do |_|
|
75
|
+
"!%s/nodes/%d/attachments/%s!" % [$1, lookup_table[:nodes][$2], $3]
|
76
|
+
end
|
77
|
+
|
78
|
+
note.text = new_text
|
79
|
+
|
80
|
+
raise "Couldn't save note attachment URL for Note ##{note.id}" unless validate_and_save(note)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
# Save the Evidence instance to the DB now that we have populated the
|
85
|
+
# original issues.
|
86
|
+
def finalize_evidence
|
87
|
+
pending_changes[:evidence].each_with_index do |evidence, i|
|
88
|
+
logger.info { "Setting issue_id for evidence" }
|
89
|
+
evidence.issue_id = lookup_table[:issues][evidence.issue_id.to_s]
|
90
|
+
|
91
|
+
new_content = evidence.content.gsub(%r{^!(.*)/nodes/(\d+)/attachments/(.+)!$}) do |_|
|
92
|
+
"!%s/nodes/%d/attachments/%s!" % [$1, lookup_table[:nodes][$2], $3]
|
93
|
+
end
|
94
|
+
evidence.content = new_content
|
95
|
+
|
96
|
+
raise "Couldn't save Evidence :issue_id / attachment URL Evidence ##{evidence.id}" unless validate_and_save(evidence)
|
97
|
+
|
98
|
+
pending_changes[:evidence_activity][i].each do |xml_activity|
|
99
|
+
raise "Couldn't create activity for Evidence ##{evidence.id}" unless create_activity(evidence, xml_activity)
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
# Fix relationships between nodes to ensure parents and childrens match
|
105
|
+
# with the new assigned :ids
|
106
|
+
def finalize_nodes
|
107
|
+
pending_changes[:orphan_nodes].each do |node|
|
108
|
+
logger.info { "Finding parent for orphaned node: #{node.label}. Former parent was #{node.parent_id}" }
|
109
|
+
node.parent_id = lookup_table[:nodes][node.parent_id.to_s]
|
110
|
+
raise "Couldn't save node parent for Node ##{node.id}" unless validate_and_save(node)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
# Go through the categories, keep a translation table between the old
|
115
|
+
# category id and the new ones so we know to which category we should
|
116
|
+
# assign our notes
|
117
|
+
def parse_categories(template)
|
118
|
+
logger.info { 'Processing Categories...' }
|
119
|
+
|
120
|
+
template.xpath('dradis-template/categories/category').each do |xml_category|
|
121
|
+
old_id = xml_category.at_xpath('id').text.strip
|
122
|
+
name = xml_category.at_xpath('name').text.strip
|
123
|
+
category = nil
|
124
|
+
|
125
|
+
# Prevent creating duplicate categories
|
126
|
+
logger.info { "Looking for category: #{name}" }
|
127
|
+
category = Category.find_or_create_by!(name: name)
|
128
|
+
lookup_table[:categories][old_id] = category.id
|
129
|
+
end
|
130
|
+
|
131
|
+
logger.info { 'Done.' }
|
132
|
+
end
|
133
|
+
|
134
|
+
# Go through the issues, keep a translation table between the old
|
135
|
+
# issue id and the new ones. This is important for importing evidence
|
136
|
+
# Will need to adjust node ID after generating node structure
|
137
|
+
def parse_issues(template)
|
138
|
+
issue = nil
|
139
|
+
issue_category = Category.issue
|
140
|
+
issue_library = Node.issue_library
|
141
|
+
|
142
|
+
logger.info { 'Processing Issues...' }
|
143
|
+
|
144
|
+
template.xpath('dradis-template/issues/issue').each do |xml_issue|
|
145
|
+
old_id = xml_issue.at_xpath('id').text.strip
|
146
|
+
|
147
|
+
# TODO: Need to find some way of checking for dups
|
148
|
+
# May be combination of text, category_id and created_at
|
149
|
+
issue = Issue.new
|
150
|
+
issue.author = xml_issue.at_xpath('author').text.strip
|
151
|
+
issue.text = xml_issue.at_xpath('text').text
|
152
|
+
issue.node = issue_library
|
153
|
+
issue.category = issue_category
|
154
|
+
|
155
|
+
return false unless validate_and_save(issue)
|
156
|
+
|
157
|
+
return false unless create_activities(issue, xml_issue)
|
158
|
+
|
159
|
+
if issue.text =~ %r{^!(.*)/nodes/(\d+)/attachments/(.+)!$}
|
160
|
+
pending_changes[:attachment_notes] << issue
|
161
|
+
end
|
162
|
+
|
163
|
+
lookup_table[:issues][old_id] = issue.id
|
164
|
+
logger.info{ "New issue detected: #{issue.title}" }
|
165
|
+
end
|
166
|
+
|
167
|
+
logger.info { 'Done.' }
|
168
|
+
|
169
|
+
end
|
170
|
+
|
171
|
+
def parse_methodologies(template)
|
172
|
+
methodology_category = Category.default
|
173
|
+
methodology_library = Node.methodology_library
|
174
|
+
|
175
|
+
logger.info { 'Processing Methodologies...' }
|
176
|
+
|
177
|
+
template.xpath('dradis-template/methodologies/methodology').each do |xml_methodology|
|
178
|
+
# FIXME: this is wrong in a few levels, we should be able to save a
|
179
|
+
# Methodology instance calling .save() but the current implementation
|
180
|
+
# of the model would consider this a 'methodology template' and not an
|
181
|
+
# instance.
|
182
|
+
#
|
183
|
+
# Also, methodology notes don't have a valid author, see
|
184
|
+
# MethodologiesController#create action (i.e. 'methodology builder' is
|
185
|
+
# used).
|
186
|
+
Note.create!(
|
187
|
+
author: 'methodology importer',
|
188
|
+
node_id: methodology_library.id,
|
189
|
+
category_id: methodology_category.id,
|
190
|
+
text: xml_methodology.at_xpath('text').text
|
191
|
+
)
|
192
|
+
end
|
193
|
+
|
194
|
+
logger.info { 'Done.' }
|
195
|
+
end
|
196
|
+
|
197
|
+
def parse_node(xml_node)
|
198
|
+
element = xml_node.at_xpath('type-id')
|
199
|
+
type_id = element.text.nil? ? nil : element.text.strip
|
200
|
+
label = xml_node.at_xpath('label').text.strip
|
201
|
+
element = xml_node.at_xpath('parent-id')
|
202
|
+
parent_id = element.text.nil? ? nil : element.text.strip
|
203
|
+
|
204
|
+
# Node positions
|
205
|
+
element = xml_node.at_xpath('position')
|
206
|
+
position = (element && !element.text.nil?) ? element.text.strip : nil
|
207
|
+
|
208
|
+
# Node properties
|
209
|
+
element = xml_node.at_xpath('properties')
|
210
|
+
properties = (element && !element.text.blank?) ? element.text.strip : nil
|
211
|
+
|
212
|
+
created_at = xml_node.at_xpath('created-at')
|
213
|
+
updated_at = xml_node.at_xpath('updated-at')
|
214
|
+
|
215
|
+
logger.info { "New node detected: #{label}, parent_id: #{parent_id}, type_id: #{type_id}" }
|
216
|
+
|
217
|
+
# There is one exception to the rule, the Configuration.uploadsNode node,
|
218
|
+
# it does not make sense to have more than one of this nodes, in any
|
219
|
+
# given tree
|
220
|
+
node = nil
|
221
|
+
note = nil
|
222
|
+
evidence = nil
|
223
|
+
if (label == Configuration.plugin_uploads_node)
|
224
|
+
node = Node.create_with(type_id: type_id, parent_id: parent_id).
|
225
|
+
find_or_create_by!(label: label)
|
226
|
+
else
|
227
|
+
node = Node.create!(
|
228
|
+
type_id: type_id,
|
229
|
+
label: label,
|
230
|
+
parent_id: parent_id,
|
231
|
+
position: position
|
232
|
+
)
|
233
|
+
end
|
234
|
+
|
235
|
+
if properties
|
236
|
+
node.raw_properties = properties
|
237
|
+
end
|
238
|
+
|
239
|
+
node.update_attribute(:created_at, created_at.text.strip) if created_at
|
240
|
+
node.update_attribute(:updated_at, updated_at.text.strip) if updated_at
|
241
|
+
|
242
|
+
raise "Couldn't save Node" unless validate_and_save(node)
|
243
|
+
raise "Couldn't create activities for Node ##{node.id}" unless create_activities(node, xml_node)
|
244
|
+
|
245
|
+
parse_node_notes(node, xml_node)
|
246
|
+
parse_node_evidence(node, xml_node)
|
247
|
+
|
248
|
+
node
|
249
|
+
end
|
250
|
+
|
251
|
+
def parse_nodes(template)
|
252
|
+
|
253
|
+
logger.info { 'Processing Nodes...' }
|
254
|
+
|
255
|
+
# Re generate the Node tree structure
|
256
|
+
template.xpath('dradis-template/nodes/node').each do |xml_node|
|
257
|
+
|
258
|
+
node = parse_node(xml_node)
|
259
|
+
|
260
|
+
# keep track of reassigned ids
|
261
|
+
lookup_table[:nodes][xml_node.at_xpath('id').text.strip] = node.id
|
262
|
+
|
263
|
+
if node.parent_id != nil
|
264
|
+
# keep track of orphaned nodes
|
265
|
+
pending_changes[:orphan_nodes] << node
|
266
|
+
end
|
267
|
+
end
|
268
|
+
|
269
|
+
logger.info { 'Done.' }
|
270
|
+
end
|
271
|
+
|
272
|
+
# Create array of evidence from xml input. Cannot store in DB until we
|
273
|
+
# have a new issue id
|
274
|
+
def parse_node_evidence(node, xml_node)
|
275
|
+
xml_node.xpath('evidence/evidence').each do |xml_evidence|
|
276
|
+
if xml_evidence.at_xpath('author') != nil
|
277
|
+
created_at = xml_evidence.at_xpath('created-at')
|
278
|
+
updated_at = xml_evidence.at_xpath('updated-at')
|
279
|
+
|
280
|
+
evidence = Evidence.new(
|
281
|
+
author: xml_evidence.at_xpath('author').text.strip,
|
282
|
+
node_id: node.id,
|
283
|
+
content: xml_evidence.at_xpath('content').text,
|
284
|
+
issue_id: xml_evidence.at_xpath('issue-id').text.strip
|
285
|
+
)
|
286
|
+
|
287
|
+
evidence.update_attribute(:created_at, created_at.text.strip) if created_at
|
288
|
+
evidence.update_attribute(:updated_at, updated_at.text.strip) if updated_at
|
289
|
+
|
290
|
+
pending_changes[:evidence] << evidence
|
291
|
+
pending_changes[:evidence_activity] << xml_evidence.xpath("activities/activity")
|
292
|
+
|
293
|
+
logger.info { "\tNew evidence added." }
|
294
|
+
end
|
295
|
+
end
|
296
|
+
end
|
297
|
+
|
298
|
+
def parse_node_notes(node, xml_node)
|
299
|
+
xml_node.xpath('notes/note').each do |xml_note|
|
300
|
+
|
301
|
+
if xml_note.at_xpath('author') != nil
|
302
|
+
old_id = xml_note.at_xpath('category-id').text.strip
|
303
|
+
new_id = lookup_table[:categories][old_id]
|
304
|
+
|
305
|
+
created_at = xml_note.at_xpath('created-at')
|
306
|
+
updated_at = xml_note.at_xpath('updated-at')
|
307
|
+
|
308
|
+
logger.info { "Note category rewrite, used to be #{old_id}, now is #{new_id}" }
|
309
|
+
note = Note.create!(
|
310
|
+
author: xml_note.at_xpath('author').text.strip,
|
311
|
+
node_id: node.id,
|
312
|
+
category_id: new_id,
|
313
|
+
text: xml_note.at_xpath('text').text
|
314
|
+
)
|
315
|
+
|
316
|
+
note.update_attribute(:created_at, created_at.text.strip) if created_at
|
317
|
+
note.update_attribute(:updated_at, updated_at.text.strip) if updated_at
|
318
|
+
|
319
|
+
raise "Couldn't save Note" unless validate_and_save(note)
|
320
|
+
|
321
|
+
if note.text =~ %r{^!(.*)/nodes/(\d+)/attachments/(.+)!$}
|
322
|
+
pending_changes[:attachment_notes] << note
|
323
|
+
end
|
324
|
+
|
325
|
+
raise "Couldn't create activities for Note ##{note.id}" unless create_activities(note, xml_note)
|
326
|
+
|
327
|
+
logger.info { "\tNew note added." }
|
328
|
+
end
|
329
|
+
end
|
330
|
+
end
|
331
|
+
|
332
|
+
def parse_tags(template)
|
333
|
+
|
334
|
+
logger.info { 'Processing Tags...' }
|
335
|
+
|
336
|
+
template.xpath('dradis-template/tags/tag').each do |xml_tag|
|
337
|
+
name = xml_tag.at_xpath('name').text()
|
338
|
+
tag = Tag.find_or_create_by!(name: name)
|
339
|
+
logger.info { "New tag detected: #{name}" }
|
340
|
+
|
341
|
+
xml_tag.xpath('./taggings/tagging').each do |xml_tagging|
|
342
|
+
old_taggable_id = xml_tagging.at_xpath('taggable-id').text()
|
343
|
+
taggable_type = xml_tagging.at_xpath('taggable-type').text()
|
344
|
+
|
345
|
+
new_taggable_id = case taggable_type
|
346
|
+
when 'Note'
|
347
|
+
lookup_table[:issues][old_taggable_id]
|
348
|
+
end
|
349
|
+
|
350
|
+
Tagging.create! tag: tag,
|
351
|
+
taggable_id: new_taggable_id, taggable_type: taggable_type
|
352
|
+
end
|
353
|
+
end
|
354
|
+
|
355
|
+
logger.info { 'Done.' }
|
356
|
+
end
|
357
|
+
|
358
|
+
def set_activity_user(activity, email)
|
359
|
+
if Activity.column_names.include?('user')
|
360
|
+
activity.user = email
|
361
|
+
else
|
362
|
+
activity.user_id = user_id_for_email(email)
|
363
|
+
end
|
364
|
+
end
|
365
|
+
|
366
|
+
# Cache users to cut down on excess SQL requests
|
367
|
+
def user_id_for_email(email)
|
368
|
+
return -1 if email.blank?
|
369
|
+
@users ||= begin
|
370
|
+
User.select([:id, :email]).all.each_with_object({}) do |user, hash|
|
371
|
+
hash[user.email] = user.id
|
372
|
+
end
|
373
|
+
end
|
374
|
+
@users[email] || -1
|
375
|
+
end
|
376
|
+
|
377
|
+
def validate_and_save(instance)
|
378
|
+
if instance.save
|
379
|
+
return true
|
380
|
+
else
|
381
|
+
@logger.info{ "Malformed #{ instance.class.name } detected: #{ instance.errors.full_messages }" }
|
382
|
+
return false
|
383
|
+
end
|
384
|
+
end
|
385
|
+
end
|
386
|
+
|
387
|
+
end
|
388
|
+
end
|