dradis-projects 3.0.1 → 3.6.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -8,8 +8,8 @@ module Dradis
8
8
 
9
9
  module VERSION
10
10
  MAJOR = 3
11
- MINOR = 0
12
- TINY = 1
11
+ MINOR = 6
12
+ TINY = 0
13
13
  PRE = nil
14
14
 
15
15
  STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
@@ -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
- logger.info { 'Loading XML state file' }
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
- content_service: content_service,
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
- logger.info { 'Moving attachments to their final destinations' }
48
- node_lookup.each do |oldid,newid|
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
- # we need this to be able to convert from old category_id to the new
43
- # category_id once the categories are added to the DB (the ID may have
44
- # changed)
45
- category_lookup = {}
46
- # the same applies to Nodes (think parent_id)
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
- validate_and_save(activity)
332
- end
333
-
334
- def set_activity_user(activity, email)
335
- if Activity.column_names.include?('user')
336
- activity.user = email
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
- # Cache users to cut down on excess SQL requests
343
- def user_id_for_email(email)
344
- return -1 if email.blank?
345
- @users ||= begin
346
- User.select([:id, :email]).all.each_with_object({}) do |user, hash|
347
- hash[user.email] = user.id
348
- end
349
- end
350
- @users[email] || -1
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
- def validate_and_save(instance)
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