dradis-projects 4.6.0 → 4.8.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 +7 -0
- data/app/controllers/dradis/plugins/projects/packages_controller.rb +6 -1
- data/app/controllers/dradis/plugins/projects/templates_controller.rb +6 -1
- data/app/views/dradis/plugins/projects/export/_index-content.html.erb +1 -1
- data/dradis-projects.gemspec +2 -2
- data/lib/dradis/plugins/projects/engine.rb +2 -2
- data/lib/dradis/plugins/projects/export/template.rb +1 -3
- data/lib/dradis/plugins/projects/export/v1/template.rb +8 -0
- data/lib/dradis/plugins/projects/export/v2/template.rb +8 -0
- data/lib/dradis/plugins/projects/export/v3/template.rb +8 -0
- data/lib/dradis/plugins/projects/export/v4/template.rb +207 -0
- data/lib/dradis/plugins/projects/gem_version.rb +1 -1
- data/lib/dradis/plugins/projects/upload/template.rb +2 -4
- data/lib/dradis/plugins/projects/upload/v1/template.rb +8 -0
- data/lib/dradis/plugins/projects/upload/v2/template.rb +8 -0
- data/lib/dradis/plugins/projects/upload/v3/template.rb +8 -0
- data/lib/dradis/plugins/projects/upload/v4/template.rb +651 -0
- data/spec/fixtures/files/with_invalid_states.xml +111 -0
- data/spec/fixtures/files/with_states.xml +111 -0
- data/spec/lib/dradis/plugins/projects/export/v2/template_spec.rb +9 -1
- data/spec/lib/dradis/plugins/projects/export/v4/template_spec.rb +84 -0
- data/spec/lib/dradis/plugins/projects/upload/v1/template_spec.rb +8 -0
- data/spec/lib/dradis/plugins/projects/upload/v2/template_spec.rb +9 -1
- data/spec/lib/dradis/plugins/projects/upload/v4/template_spec.rb +168 -0
- metadata +20 -10
@@ -0,0 +1,651 @@
|
|
1
|
+
module Dradis::Plugins::Projects::Upload::V4
|
2
|
+
module Template
|
3
|
+
class Importer < Dradis::Plugins::Projects::Upload::Template::Importer
|
4
|
+
|
5
|
+
attr_accessor :attachment_notes, :logger, :pending_changes, :users
|
6
|
+
|
7
|
+
ATTACHMENT_URL = %r{^!(/[a-z]+)?/(?:projects/\d+/)?nodes/(\d+)/attachments/(.+)!$}
|
8
|
+
|
9
|
+
def post_initialize(args={})
|
10
|
+
@lookup_table = {
|
11
|
+
categories: {},
|
12
|
+
nodes: {},
|
13
|
+
issues: {}
|
14
|
+
}
|
15
|
+
|
16
|
+
@pending_changes = {
|
17
|
+
# If the note has an attachment screenshot (i.e. !.../nodes/i/attachments/...!)
|
18
|
+
# we will fix the URL to point to the new Node ID.
|
19
|
+
#
|
20
|
+
# WARNING: we need a lookup table because one note may be referencing a
|
21
|
+
# different (yet unprocessed) node's attachments.
|
22
|
+
attachment_notes: [],
|
23
|
+
|
24
|
+
# evidence is parsed when nodes are parsed, but cannot be saved until
|
25
|
+
# issues have been created. Therefore, parse evidence into arrays until
|
26
|
+
# time for creation
|
27
|
+
evidence: [],
|
28
|
+
|
29
|
+
# likewise we also need to hold on to the XML about evidence activities
|
30
|
+
# and comments until after the evidence has been saved
|
31
|
+
evidence_activity: [],
|
32
|
+
evidence_comments: [],
|
33
|
+
|
34
|
+
# all children nodes, we will need to find the ID of their new parents.
|
35
|
+
orphan_nodes: []
|
36
|
+
}
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def create_comments(commentable, xml_comments)
|
42
|
+
return true if xml_comments.empty?
|
43
|
+
|
44
|
+
xml_comments.each do |xml_comment|
|
45
|
+
author_email = xml_comment.at_xpath('author').text
|
46
|
+
comment = Comment.new(
|
47
|
+
commentable_id: commentable.id,
|
48
|
+
commentable_type: commentable.class.to_s,
|
49
|
+
content: xml_comment.at_xpath('content').text,
|
50
|
+
created_at: Time.at(xml_comment.at_xpath('created_at').text.to_i),
|
51
|
+
user_id: users[author_email]
|
52
|
+
)
|
53
|
+
|
54
|
+
if comment.user.nil?
|
55
|
+
comment.content = comment.content +
|
56
|
+
"\n\nOriginal author not available in this Dradis instance: "\
|
57
|
+
"#{author_email}."
|
58
|
+
end
|
59
|
+
|
60
|
+
unless validate_and_save(comment)
|
61
|
+
logger.info { "comment errors: #{comment.inspect}" }
|
62
|
+
return false
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def create_activities(trackable, xml_trackable)
|
68
|
+
xml_trackable.xpath('activities/activity').each do |xml_activity|
|
69
|
+
# if 'validate_and_save(activity)' returns false, it needs
|
70
|
+
# to bubble up to the 'import' method so we can stop execution
|
71
|
+
return false unless create_activity(trackable, xml_activity)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def create_activity(trackable, xml_activity)
|
76
|
+
activity = trackable.activities.new(
|
77
|
+
action: xml_activity.at_xpath("action").text,
|
78
|
+
created_at: Time.at(xml_activity.at_xpath("created_at").text.to_i)
|
79
|
+
)
|
80
|
+
|
81
|
+
activity.project_id = project.id if activity.respond_to?(:project)
|
82
|
+
|
83
|
+
set_activity_user(activity, xml_activity.at_xpath("user_email").text)
|
84
|
+
|
85
|
+
validate_and_save(activity)
|
86
|
+
end
|
87
|
+
|
88
|
+
def create_issue(issue, xml_issue)
|
89
|
+
# TODO: Need to find some way of checking for dups
|
90
|
+
# May be combination of text, category_id and created_at
|
91
|
+
issue.author = xml_issue.at_xpath('author').text.strip
|
92
|
+
issue.state = xml_issue.at_xpath('state')&.text || :published
|
93
|
+
issue.text = xml_issue.at_xpath('text').text
|
94
|
+
issue.node = project.issue_library
|
95
|
+
issue.category = Category.issue
|
96
|
+
|
97
|
+
return false unless validate_and_save(issue)
|
98
|
+
|
99
|
+
return false unless create_activities(issue, xml_issue)
|
100
|
+
|
101
|
+
return false unless create_comments(issue, xml_issue.xpath('comments/comment'))
|
102
|
+
|
103
|
+
true
|
104
|
+
end
|
105
|
+
|
106
|
+
def finalize(template)
|
107
|
+
logger.info { 'Wrapping up...' }
|
108
|
+
|
109
|
+
finalize_nodes()
|
110
|
+
finalize_evidence()
|
111
|
+
finalize_attachments()
|
112
|
+
|
113
|
+
logger.info { 'Done.' }
|
114
|
+
end
|
115
|
+
|
116
|
+
def finalize_attachments
|
117
|
+
# Adjust attachment URLs for new Node IDs
|
118
|
+
pending_changes[:attachment_notes].each do |item|
|
119
|
+
text_attr =
|
120
|
+
if defined?(ContentBlock) && item.is_a?(ContentBlock)
|
121
|
+
:content
|
122
|
+
else
|
123
|
+
:text
|
124
|
+
end
|
125
|
+
|
126
|
+
logger.info { "Adjusting screenshot URLs: #{item.class.name} ##{item.id}" }
|
127
|
+
|
128
|
+
new_text = update_attachment_references(item.send(text_attr))
|
129
|
+
item.send(text_attr.to_s + "=", new_text)
|
130
|
+
|
131
|
+
raise "Couldn't save note attachment URL for #{item.class.name} ##{item.id}" unless validate_and_save(item)
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
# Save the Evidence instance to the DB now that we have populated the
|
136
|
+
# original issues.
|
137
|
+
def finalize_evidence
|
138
|
+
pending_changes[:evidence].each_with_index do |evidence, i|
|
139
|
+
logger.info { "Setting issue_id for evidence" }
|
140
|
+
evidence.issue_id = lookup_table[:issues][evidence.issue_id]
|
141
|
+
|
142
|
+
evidence.content = update_attachment_references(evidence.content)
|
143
|
+
|
144
|
+
raise "Couldn't save Evidence :issue_id / attachment URL Evidence ##{evidence.id}" unless validate_and_save(evidence)
|
145
|
+
|
146
|
+
pending_changes[:evidence_activity][i].each do |xml_activity|
|
147
|
+
raise "Couldn't create activity for Evidence ##{evidence.id}" unless create_activity(evidence, xml_activity)
|
148
|
+
end
|
149
|
+
|
150
|
+
xml_comments = pending_changes[:evidence_comments][i]
|
151
|
+
raise "Couldn't create comments for Evidence ##{evidence.id}" unless create_comments(evidence, xml_comments)
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
# Fix relationships between nodes to ensure parents and childrens match
|
156
|
+
# with the new assigned :ids
|
157
|
+
def finalize_nodes
|
158
|
+
pending_changes[:orphan_nodes].each do |node|
|
159
|
+
logger.info { "Finding parent for orphaned node: #{node.label}. Former parent was #{node.parent_id}" }
|
160
|
+
node.parent_id = lookup_table[:nodes][node.parent_id]
|
161
|
+
raise "Couldn't save node parent for Node ##{node.id}" unless validate_and_save(node)
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
# Go through the categories, keep a translation table between the old
|
166
|
+
# category id and the new ones so we know to which category we should
|
167
|
+
# assign our notes
|
168
|
+
def parse_categories(template)
|
169
|
+
logger.info { 'Processing Categories...' }
|
170
|
+
|
171
|
+
template.xpath('dradis-template/categories/category').each do |xml_category|
|
172
|
+
old_id = Integer(xml_category.at_xpath('id').text.strip)
|
173
|
+
name = xml_category.at_xpath('name').text.strip
|
174
|
+
category = nil
|
175
|
+
|
176
|
+
# Prevent creating duplicate categories
|
177
|
+
logger.info { "Looking for category: #{name}" }
|
178
|
+
category = Category.find_or_create_by!(name: name)
|
179
|
+
lookup_table[:categories][old_id] = category.id
|
180
|
+
end
|
181
|
+
|
182
|
+
logger.info { 'Done.' }
|
183
|
+
end
|
184
|
+
|
185
|
+
# Go through the issues, keep a translation table between the old
|
186
|
+
# issue id and the new ones. This is important for importing evidence
|
187
|
+
# Will need to adjust node ID after generating node structure
|
188
|
+
def parse_issues(template)
|
189
|
+
issue = nil
|
190
|
+
|
191
|
+
logger.info { 'Processing Issues...' }
|
192
|
+
|
193
|
+
template.xpath('dradis-template/issues/issue').each do |xml_issue|
|
194
|
+
issue = Issue.new
|
195
|
+
|
196
|
+
return false unless create_issue(issue, xml_issue)
|
197
|
+
|
198
|
+
if issue.text =~ %r{^!(.*)/nodes/(\d+)/attachments/(.+)!$}
|
199
|
+
pending_changes[:attachment_notes] << issue
|
200
|
+
end
|
201
|
+
|
202
|
+
old_id = Integer(xml_issue.at_xpath('id').text.strip)
|
203
|
+
lookup_table[:issues][old_id] = issue.id
|
204
|
+
logger.info{ "New issue detected: #{issue.title}" }
|
205
|
+
end
|
206
|
+
|
207
|
+
logger.info { 'Done.' }
|
208
|
+
|
209
|
+
end
|
210
|
+
|
211
|
+
def parse_methodologies(template)
|
212
|
+
methodology_category = Category.default
|
213
|
+
methodology_library = project.methodology_library
|
214
|
+
|
215
|
+
logger.info { 'Processing Methodologies...' }
|
216
|
+
|
217
|
+
template.xpath('dradis-template/methodologies/methodology').each do |xml_methodology|
|
218
|
+
# FIXME: this is wrong in a few levels, we should be able to save a
|
219
|
+
# Methodology instance calling .save() but the current implementation
|
220
|
+
# of the model would consider this a 'methodology template' and not an
|
221
|
+
# instance.
|
222
|
+
#
|
223
|
+
# Also, methodology notes don't have a valid author, see
|
224
|
+
# MethodologiesController#create action (i.e. 'methodology builder' is
|
225
|
+
# used).
|
226
|
+
Note.create!(
|
227
|
+
author: 'methodology importer',
|
228
|
+
node_id: methodology_library.id,
|
229
|
+
category_id: methodology_category.id,
|
230
|
+
text: xml_methodology.at_xpath('text').text
|
231
|
+
)
|
232
|
+
end
|
233
|
+
|
234
|
+
logger.info { 'Done.' }
|
235
|
+
end
|
236
|
+
|
237
|
+
def parse_node(xml_node)
|
238
|
+
element = xml_node.at_xpath('type-id')
|
239
|
+
type_id = element.text.nil? ? nil : element.text.strip
|
240
|
+
label = xml_node.at_xpath('label').text.strip
|
241
|
+
element = xml_node.at_xpath('parent-id')
|
242
|
+
parent_id = element.text.blank? ? nil : element.text.strip
|
243
|
+
|
244
|
+
# Node positions
|
245
|
+
element = xml_node.at_xpath('position')
|
246
|
+
position = (element && !element.text.nil?) ? element.text.strip : nil
|
247
|
+
|
248
|
+
# Node properties
|
249
|
+
element = xml_node.at_xpath('properties')
|
250
|
+
properties = (element && !element.text.blank?) ? element.text.strip : nil
|
251
|
+
|
252
|
+
created_at = xml_node.at_xpath('created-at')
|
253
|
+
updated_at = xml_node.at_xpath('updated-at')
|
254
|
+
|
255
|
+
logger.info { "New node detected: #{label}, parent_id: #{parent_id}, type_id: #{type_id}" }
|
256
|
+
|
257
|
+
# There are exceptions to the rule, when it does not make sense to have
|
258
|
+
# more than one of this nodes, in any given tree:
|
259
|
+
# - the Configuration.uploadsNode node (detected by its label)
|
260
|
+
# - any nodes with type different from DEFAULT or HOST
|
261
|
+
if label == Configuration.plugin_uploads_node
|
262
|
+
node = project.nodes.create_with(type_id: type_id, parent_id: parent_id)
|
263
|
+
.find_or_create_by!(label: label)
|
264
|
+
elsif Node::Types::USER_TYPES.exclude?(type_id.to_i)
|
265
|
+
node = project.nodes.create_with(label: label)
|
266
|
+
.find_or_create_by!(type_id: type_id)
|
267
|
+
else
|
268
|
+
# We don't want to validate child nodes here yet since they always
|
269
|
+
# have invalid parent id's. They'll eventually be validated in the
|
270
|
+
# finalize_nodes method.
|
271
|
+
has_nil_parent = !parent_id
|
272
|
+
node =
|
273
|
+
project.nodes.new(
|
274
|
+
type_id: type_id,
|
275
|
+
label: label,
|
276
|
+
parent_id: parent_id,
|
277
|
+
position: position
|
278
|
+
)
|
279
|
+
node.save!(validate: has_nil_parent)
|
280
|
+
pending_changes[:orphan_nodes] << node if parent_id
|
281
|
+
end
|
282
|
+
|
283
|
+
if properties
|
284
|
+
node.raw_properties = properties
|
285
|
+
node.save!(validate: has_nil_parent)
|
286
|
+
end
|
287
|
+
|
288
|
+
node.update_attribute(:created_at, created_at.text.strip) if created_at
|
289
|
+
node.update_attribute(:updated_at, updated_at.text.strip) if updated_at
|
290
|
+
|
291
|
+
raise "Couldn't create activities for Node ##{node.id}" unless create_activities(node, xml_node)
|
292
|
+
|
293
|
+
parse_node_notes(node, xml_node)
|
294
|
+
parse_node_evidence(node, xml_node)
|
295
|
+
|
296
|
+
node
|
297
|
+
end
|
298
|
+
|
299
|
+
def parse_nodes(template)
|
300
|
+
logger.info { 'Processing Nodes...' }
|
301
|
+
|
302
|
+
# Re generate the Node tree structure
|
303
|
+
template.xpath('dradis-template/nodes/node').each do |xml_node|
|
304
|
+
|
305
|
+
node = parse_node(xml_node)
|
306
|
+
|
307
|
+
# keep track of reassigned ids
|
308
|
+
# Convert the id to an integer as it has no place being a string, or
|
309
|
+
# directory path. We later use this ID to build a directory structure
|
310
|
+
# to place attachments and without validation opens the potential for
|
311
|
+
# path traversal.
|
312
|
+
node_original_id = Integer(xml_node.at_xpath('id').text.strip)
|
313
|
+
lookup_table[:nodes][node_original_id] = node.id
|
314
|
+
end
|
315
|
+
|
316
|
+
logger.info { 'Done.' }
|
317
|
+
end
|
318
|
+
|
319
|
+
# Create array of evidence from xml input. Cannot store in DB until we
|
320
|
+
# have a new issue id
|
321
|
+
def parse_node_evidence(node, xml_node)
|
322
|
+
xml_node.xpath('evidence/evidence').each do |xml_evidence|
|
323
|
+
if xml_evidence.at_xpath('author') != nil
|
324
|
+
created_at = xml_evidence.at_xpath('created-at')
|
325
|
+
updated_at = xml_evidence.at_xpath('updated-at')
|
326
|
+
|
327
|
+
evidence = Evidence.new(
|
328
|
+
author: xml_evidence.at_xpath('author').text.strip,
|
329
|
+
node_id: node.id,
|
330
|
+
content: xml_evidence.at_xpath('content').text,
|
331
|
+
issue_id: xml_evidence.at_xpath('issue-id').text.strip
|
332
|
+
)
|
333
|
+
|
334
|
+
evidence.update_attribute(:created_at, created_at.text.strip) if created_at
|
335
|
+
evidence.update_attribute(:updated_at, updated_at.text.strip) if updated_at
|
336
|
+
|
337
|
+
pending_changes[:evidence] << evidence
|
338
|
+
pending_changes[:evidence_activity] << xml_evidence.xpath('activities/activity')
|
339
|
+
pending_changes[:evidence_comments] << xml_evidence.xpath('comments/comment')
|
340
|
+
|
341
|
+
logger.info { "\tNew evidence added." }
|
342
|
+
end
|
343
|
+
end
|
344
|
+
end
|
345
|
+
|
346
|
+
def parse_node_notes(node, xml_node)
|
347
|
+
xml_node.xpath('notes/note').each do |xml_note|
|
348
|
+
|
349
|
+
if xml_note.at_xpath('author') != nil
|
350
|
+
old_id = Integer(xml_note.at_xpath('category-id').text.strip)
|
351
|
+
new_id = lookup_table[:categories][old_id]
|
352
|
+
|
353
|
+
created_at = xml_note.at_xpath('created-at')
|
354
|
+
updated_at = xml_note.at_xpath('updated-at')
|
355
|
+
|
356
|
+
logger.info { "Note category rewrite, used to be #{old_id}, now is #{new_id}" }
|
357
|
+
note = Note.create!(
|
358
|
+
author: xml_note.at_xpath('author').text.strip,
|
359
|
+
node_id: node.id,
|
360
|
+
category_id: new_id,
|
361
|
+
text: xml_note.at_xpath('text').text
|
362
|
+
)
|
363
|
+
|
364
|
+
note.update_attribute(:created_at, created_at.text.strip) if created_at
|
365
|
+
note.update_attribute(:updated_at, updated_at.text.strip) if updated_at
|
366
|
+
|
367
|
+
raise "Couldn't save Note" unless validate_and_save(note)
|
368
|
+
|
369
|
+
if note.text =~ %r{^!(.*)/nodes/(\d+)/attachments/(.+)!$}
|
370
|
+
pending_changes[:attachment_notes] << note
|
371
|
+
end
|
372
|
+
|
373
|
+
raise "Couldn't create activities for Note ##{note.id}" unless create_activities(note, xml_note)
|
374
|
+
raise "Couldn't create comments for Note ##{note.id}" unless create_comments(note, xml_note.xpath('comments/comment'))
|
375
|
+
|
376
|
+
logger.info { "\tNew note added." }
|
377
|
+
end
|
378
|
+
end
|
379
|
+
end
|
380
|
+
|
381
|
+
def parse_report_content(template); end
|
382
|
+
|
383
|
+
def parse_tags(template)
|
384
|
+
logger.info { 'Processing Tags...' }
|
385
|
+
|
386
|
+
template.xpath('dradis-template/tags/tag').each do |xml_tag|
|
387
|
+
name = xml_tag.at_xpath('name').text()
|
388
|
+
tag_params = { name: name }
|
389
|
+
tag_params[:project_id] = project.id if Tag.has_attribute?(:project_id)
|
390
|
+
tag = Tag.where(tag_params).first_or_create
|
391
|
+
logger.info { "New tag detected: #{name}" }
|
392
|
+
|
393
|
+
xml_tag.xpath('./taggings/tagging').each do |xml_tagging|
|
394
|
+
old_taggable_id = Integer(xml_tagging.at_xpath('taggable-id').text())
|
395
|
+
taggable_type = xml_tagging.at_xpath('taggable-type').text()
|
396
|
+
|
397
|
+
new_taggable_id = case taggable_type
|
398
|
+
when 'Note'
|
399
|
+
lookup_table[:issues][old_taggable_id]
|
400
|
+
end
|
401
|
+
|
402
|
+
Tagging.create! tag: tag,
|
403
|
+
taggable_id: new_taggable_id, taggable_type: taggable_type
|
404
|
+
end
|
405
|
+
end
|
406
|
+
|
407
|
+
logger.info { 'Done.' }
|
408
|
+
end
|
409
|
+
|
410
|
+
def set_activity_user(activity, email)
|
411
|
+
if Activity.column_names.include?('user')
|
412
|
+
activity.user = email
|
413
|
+
else
|
414
|
+
activity.user_id = user_id_for_email(email)
|
415
|
+
end
|
416
|
+
end
|
417
|
+
|
418
|
+
def update_attachment_references(string)
|
419
|
+
string.gsub(ATTACHMENT_URL) do |attachment|
|
420
|
+
node_id = lookup_table[:nodes][$2.to_i]
|
421
|
+
if node_id
|
422
|
+
"!%s/projects/%d/nodes/%d/attachments/%s!" % [$1, project.id, node_id, $3]
|
423
|
+
else
|
424
|
+
logger.error { "The attachment wasn't included in the package: #{attachment}" }
|
425
|
+
attachment
|
426
|
+
end
|
427
|
+
end
|
428
|
+
end
|
429
|
+
|
430
|
+
def user_id_for_email(email)
|
431
|
+
users[email] || @default_user_id
|
432
|
+
end
|
433
|
+
|
434
|
+
# Cache users to cut down on excess SQL requests
|
435
|
+
def users
|
436
|
+
@users ||= begin
|
437
|
+
User.select([:id, :email]).all.each_with_object({}) do |user, hash|
|
438
|
+
hash[user.email] = user.id
|
439
|
+
end
|
440
|
+
end
|
441
|
+
end
|
442
|
+
|
443
|
+
def validate_and_save(instance)
|
444
|
+
if instance.save
|
445
|
+
return true
|
446
|
+
else
|
447
|
+
@logger.info{ "Malformed #{ instance.class.name } detected: #{ instance.errors.full_messages }" }
|
448
|
+
return false
|
449
|
+
end
|
450
|
+
end
|
451
|
+
|
452
|
+
# Private: Given a XML node contianing assignee information this method
|
453
|
+
# tries to recreate the assignment in the new project.
|
454
|
+
#
|
455
|
+
# * If the user exists in this instance: assign the card to that user
|
456
|
+
# (no matter if the user is not a project author).
|
457
|
+
# * If the user doesn't exist, don't creat an assiment and add a note
|
458
|
+
# inside the card's description.
|
459
|
+
#
|
460
|
+
# card - the Card object we're creating assignments for.
|
461
|
+
# xml_assignee - the Nokogiri::XML::Node that contains node assignment
|
462
|
+
# information.
|
463
|
+
#
|
464
|
+
# Returns nothing, but creates a new Assignee for this card.
|
465
|
+
def create_assignee(card, xml_assignee)
|
466
|
+
email = xml_assignee.text()
|
467
|
+
user_id = user_id_for_email(email)
|
468
|
+
|
469
|
+
if user_id == -1
|
470
|
+
old_assignee_field = card.fields['FormerAssignees'] || ''
|
471
|
+
card.set_field 'FormerAssignees', old_assignee_field << "* #{email}\n"
|
472
|
+
else
|
473
|
+
old_assignee_ids = card.assignee_ids
|
474
|
+
card.assignee_ids = old_assignee_ids + [user_id]
|
475
|
+
end
|
476
|
+
end
|
477
|
+
|
478
|
+
# Private: Reassign cross-references once all the objects in the project
|
479
|
+
# have been recreated.
|
480
|
+
#
|
481
|
+
# No arguments received, but the methods relies on :lookup_table and
|
482
|
+
# :pending_changes provided by dradis-projects.
|
483
|
+
#
|
484
|
+
# Returns nothing.
|
485
|
+
def finalize_cards
|
486
|
+
logger.info { 'Reassigning card positions...' }
|
487
|
+
|
488
|
+
# Fix the :previous_id with the new card IDs
|
489
|
+
pending_changes[:cards].each do |card|
|
490
|
+
card.previous_id = lookup_table[:cards][card.previous_id]
|
491
|
+
raise "Couldn't save card's position" unless validate_and_save(card)
|
492
|
+
end
|
493
|
+
|
494
|
+
logger.info { 'Done.' }
|
495
|
+
end
|
496
|
+
|
497
|
+
# Private: Reassign the List's :previous_id now that we know what are the
|
498
|
+
# new IDs that correspond to all List objects in the import.
|
499
|
+
#
|
500
|
+
# No arguments received, but the method relies on :lookup_table and
|
501
|
+
# :pending_changes provided by dradis-projects.
|
502
|
+
#
|
503
|
+
# Returns nothing.
|
504
|
+
def finalize_lists
|
505
|
+
logger.info { 'Reassigning list positions...' }
|
506
|
+
|
507
|
+
# Fix the :previous_id with the new card IDs
|
508
|
+
pending_changes[:lists].each do |list|
|
509
|
+
list.previous_id = lookup_table[:lists][list.previous_id]
|
510
|
+
raise "Couldn't save list's position" unless validate_and_save(list)
|
511
|
+
end
|
512
|
+
|
513
|
+
logger.info { 'Done.' }
|
514
|
+
end
|
515
|
+
|
516
|
+
# Private: Restore Board, List and Card information from the project
|
517
|
+
# template.
|
518
|
+
def parse_methodologies(template)
|
519
|
+
if template_version == 1
|
520
|
+
# Restore Board from old xml methodology format
|
521
|
+
process_v1_methodologies(template)
|
522
|
+
else
|
523
|
+
process_v2_methodologies(template)
|
524
|
+
end
|
525
|
+
end
|
526
|
+
|
527
|
+
# Private: For each XML card block, we're creating a new Card instance,
|
528
|
+
# restoring the card's Activities and Assignments.
|
529
|
+
#
|
530
|
+
# list - the List instance that will hold this Card.
|
531
|
+
# xml_card - the Nokogiri::XML node containing the card's data.
|
532
|
+
#
|
533
|
+
# Returns nothing, but makes use of the :lookup_table and :pending_changes
|
534
|
+
# variables to store information that will be used during the
|
535
|
+
# :finalize_cards method.
|
536
|
+
def process_card(list, xml_card)
|
537
|
+
due_date = xml_card.at_xpath('due_date').text
|
538
|
+
due_date = Date.iso8601(due_date) unless due_date.empty?
|
539
|
+
|
540
|
+
card = list.cards.create name: xml_card.at_xpath('name').text,
|
541
|
+
description: xml_card.at_xpath('description').text,
|
542
|
+
due_date: due_date,
|
543
|
+
previous_id: xml_card.at_xpath('previous_id').text&.to_i
|
544
|
+
|
545
|
+
xml_card.xpath('activities/activity').each do |xml_activity|
|
546
|
+
raise "Couldn't create activity for Card ##{card.id}" unless create_activity(card, xml_activity)
|
547
|
+
end
|
548
|
+
|
549
|
+
xml_card.xpath('assignees/assignee').each do |xml_assignee|
|
550
|
+
raise "Couldn't create assignment for Card ##{card.id}" unless create_assignee(card, xml_assignee)
|
551
|
+
end
|
552
|
+
|
553
|
+
raise "Couldn't create comments for Card ##{card.id}" unless create_comments(card, xml_card.xpath('comments/comment'))
|
554
|
+
|
555
|
+
xml_id = Integer(xml_card.at_xpath('id').text)
|
556
|
+
lookup_table[:cards][xml_id] = card.id
|
557
|
+
pending_changes[:cards] << card
|
558
|
+
end
|
559
|
+
|
560
|
+
# Private: Initial pass over ./methodologies/ section of the tempalte
|
561
|
+
# document to extract Board, List and Card information. Some of the
|
562
|
+
# objects will contain invalid references (e.g. the former :previous_id
|
563
|
+
# of a card will need to be reassigned) that we will fix at a later stage.
|
564
|
+
#
|
565
|
+
# template - A Nokogiri::XML document containing the project template
|
566
|
+
# data.
|
567
|
+
#
|
568
|
+
# Returns nothing.
|
569
|
+
def process_methodologies(template)
|
570
|
+
logger.info { 'Processing Methodologies...' }
|
571
|
+
|
572
|
+
lookup_table[:cards] = {}
|
573
|
+
lookup_table[:lists] = {}
|
574
|
+
pending_changes[:cards] = []
|
575
|
+
pending_changes[:lists] = []
|
576
|
+
|
577
|
+
template.xpath('dradis-template/methodologies/board').each do |xml_board|
|
578
|
+
xml_node_id = xml_board.at_xpath('node_id').try(:text)
|
579
|
+
node_id =
|
580
|
+
if xml_node_id.present?
|
581
|
+
lookup_table[:nodes][xml_node_id.to_i]
|
582
|
+
else
|
583
|
+
project.methodology_library.id
|
584
|
+
end
|
585
|
+
|
586
|
+
board = content_service.create_board(
|
587
|
+
name: xml_board.at_xpath('name').text,
|
588
|
+
node_id: node_id
|
589
|
+
)
|
590
|
+
|
591
|
+
xml_board.xpath('./list').each do |xml_list|
|
592
|
+
list = board.lists.create name: xml_list.at_xpath('name').text,
|
593
|
+
previous_id: xml_list.at_xpath('previous_id').text&.to_i
|
594
|
+
xml_id = Integer(xml_list.at_xpath('id').text)
|
595
|
+
|
596
|
+
lookup_table[:lists][xml_id] = list.id
|
597
|
+
pending_changes[:lists] << list
|
598
|
+
|
599
|
+
xml_list.xpath('./card').each do |xml_card|
|
600
|
+
process_card(list, xml_card)
|
601
|
+
end
|
602
|
+
end
|
603
|
+
end
|
604
|
+
|
605
|
+
logger.info { 'Done.' }
|
606
|
+
end
|
607
|
+
|
608
|
+
# Private: Pass over old ./methodologies/ sections of the template
|
609
|
+
# document to extract Board, List and Card information.
|
610
|
+
#
|
611
|
+
# template - A Nokogiri::XML document containing the project template
|
612
|
+
# data.
|
613
|
+
#
|
614
|
+
# Returns nothing.
|
615
|
+
def process_v1_methodologies(template)
|
616
|
+
xml_methodologies = template.xpath('dradis-template/methodologies/methodology')
|
617
|
+
return if xml_methodologies.empty?
|
618
|
+
|
619
|
+
logger.info { 'Processing V1 Methodologies...' }
|
620
|
+
|
621
|
+
migration = MethodologyMigrationService.new(project.id)
|
622
|
+
|
623
|
+
xml_methodologies.each do |xml_methodology|
|
624
|
+
migration.migrate(
|
625
|
+
Methodology.new(content: xml_methodology.at_xpath('text').text)
|
626
|
+
)
|
627
|
+
end
|
628
|
+
|
629
|
+
logger.info { 'Done.' }
|
630
|
+
end
|
631
|
+
|
632
|
+
# Private: Pass over new ./methodologies/ sections of the template
|
633
|
+
# document to extract Board, List and Card information.
|
634
|
+
#
|
635
|
+
# template - A Nokogiri::XML document containing the project template
|
636
|
+
# data.
|
637
|
+
#
|
638
|
+
# Returns nothing.
|
639
|
+
def process_v2_methodologies(template)
|
640
|
+
# Restore Board
|
641
|
+
process_methodologies(template)
|
642
|
+
|
643
|
+
# Reassign Card's :previous_id and :assginees
|
644
|
+
finalize_cards()
|
645
|
+
|
646
|
+
# Reassign List's :previous id
|
647
|
+
finalize_lists()
|
648
|
+
end
|
649
|
+
end
|
650
|
+
end
|
651
|
+
end
|