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.
@@ -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