dradis-csv 5.3.0 → 5.4.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9fc992eccc61f6f4f2b7a0d2696dbda946add4cb50a38cb92cc435dea0dc1038
4
- data.tar.gz: 993fdee3bc19179b7f42801f6e0a078fa6e26d0e8eec09ae60a4e89006627e09
3
+ metadata.gz: f66806e2230745ce4ab608c4930c575d92e35f577c7a86ff9f888327c313cda9
4
+ data.tar.gz: 2510f5a2d96208375f1a339976dc16c03490cdbabb297728095e178c635f2a20
5
5
  SHA512:
6
- metadata.gz: 7f3322fbe5e012b8bbe5eaab98450f71c3273461398f5e1d5e814c626152037216288fafe53614305789dd8a415c5ac5071bc6ed1b0ae01ee8268bd0f71964c1
7
- data.tar.gz: be698324c4a89f500d9da324016e149b8d9d09842f5af2ae26a922130eb2fa21aab85d9b4baa6d76f3b3254d7ba28acb343d56a576d07dac3dc73492a87acf45
6
+ metadata.gz: 5a6e489369e1d0a13bb3c60a02b02f1352680a0fe85c39448828891aac46b85a18227d97f62bc7486385dba3345bbb3027edf5fec786485bb54f2956ec5c720e
7
+ data.tar.gz: 90f1df72e003a07b155c3f445905332c63b902d49d753b0b167a33ad6158cfdc5a0408e22565ff36f42868a52f290d97aded1336d4354854d074fd294e45e645
data/CHANGELOG.md CHANGED
@@ -1,3 +1,6 @@
1
+ v5.4.0 (September 2026)
2
+ - No changes
3
+
1
4
  v5.3.0 (August 2026)
2
5
  - Add support for managing Mappings through the Mappings Manager
3
6
  - Add support for saving column assignments from the upload mapper
@@ -8,7 +8,7 @@ module Dradis
8
8
 
9
9
  module VERSION
10
10
  MAJOR = 5
11
- MINOR = 3
11
+ MINOR = 4
12
12
  TINY = 0
13
13
  PRE = nil
14
14
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dradis-csv
3
3
  version: !ruby/object:Gem::Version
4
- version: 5.3.0
4
+ version: 5.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Daniel Martin
@@ -56,12 +56,7 @@ executables: []
56
56
  extensions: []
57
57
  extra_rdoc_files: []
58
58
  files:
59
- - ".github/pull_request_template.md"
60
- - ".gitignore"
61
59
  - CHANGELOG.md
62
- - CHANGELOG.template
63
- - CONTRIBUTING.md
64
- - Gemfile
65
60
  - LICENSE
66
61
  - README.md
67
62
  - Rakefile
@@ -75,7 +70,6 @@ files:
75
70
  - app/views/dradis/plugins/csv/upload/new.html.erb
76
71
  - config/initializers/inflections.rb
77
72
  - config/routes.rb
78
- - dradis-csv.gemspec
79
73
  - lib/dradis-csv.rb
80
74
  - lib/dradis/plugins/csv.rb
81
75
  - lib/dradis/plugins/csv/engine.rb
@@ -85,13 +79,6 @@ files:
85
79
  - lib/dradis/plugins/csv/mapping.rb
86
80
  - lib/dradis/plugins/csv/mapping_form.rb
87
81
  - lib/dradis/plugins/csv/version.rb
88
- - spec/features/upload_spec.rb
89
- - spec/fixtures/files/simple (copy).csv
90
- - spec/fixtures/files/simple.csv
91
- - spec/fixtures/files/simple_malformed.csv
92
- - spec/jobs/dradis/plugins/csv/mapping_import_job_spec.rb
93
- - spec/lib/dradis/plugins/csv/importer_spec.rb
94
- - spec/lib/dradis/plugins/csv/mapping_form_spec.rb
95
82
  homepage: http://dradis.com
96
83
  licenses:
97
84
  - GPL-2
@@ -113,11 +100,4 @@ requirements: []
113
100
  rubygems_version: 3.6.9
114
101
  specification_version: 4
115
102
  summary: CSV add-on for the Dradis Framework.
116
- test_files:
117
- - spec/features/upload_spec.rb
118
- - spec/fixtures/files/simple (copy).csv
119
- - spec/fixtures/files/simple.csv
120
- - spec/fixtures/files/simple_malformed.csv
121
- - spec/jobs/dradis/plugins/csv/mapping_import_job_spec.rb
122
- - spec/lib/dradis/plugins/csv/importer_spec.rb
123
- - spec/lib/dradis/plugins/csv/mapping_form_spec.rb
103
+ test_files: []
@@ -1,45 +0,0 @@
1
- Please review [CONTRIBUTING.md](https://github.com/dradis/dradis-ce/blob/develop/CONTRIBUTING.md) and remove this line.
2
-
3
- ### Summary
4
-
5
- Provide a general description of the code changes in your pull
6
- request... were there any bugs you had fixed? If so, mention them. If
7
- these bugs have open GitHub issues, be sure to tag them here as well,
8
- to keep the conversation linked together.
9
-
10
-
11
- ### Testing Steps
12
-
13
- Provide steps to test functionality, described in detail for someone not familiar with this part of the application / code base
14
-
15
-
16
- ### Other Information
17
-
18
- If there's anything else that's important and relevant to your pull
19
- request, mention that information here. This could include
20
- benchmarks, or other information.
21
-
22
- Thanks for contributing to Dradis!
23
-
24
-
25
- ### Copyright assignment
26
-
27
- Collaboration is difficult with commercial closed source but we want
28
- to keep as much of the OSS ethos as possible available to users
29
- who want to fix it themselves.
30
-
31
- In order to unambiguously own and sell Dradis Framework commercial
32
- products, we must have the copyright associated with the entire
33
- codebase. Any code you create which is merged must be owned by us.
34
- That's not us trying to be a jerks, that's just the way it works.
35
-
36
- You can delete this section, but the following sentence needs to
37
- remain in the PR's description:
38
-
39
- > I assign all rights, including copyright, to any future Dradis
40
- > work by myself to Security Roots.
41
-
42
- ### Check List
43
-
44
- - [ ] Added a CHANGELOG entry
45
- - [ ] Added specs
data/.gitignore DELETED
@@ -1,5 +0,0 @@
1
- .DS_Store
2
-
3
- pkg/
4
-
5
- Gemfile.lock
data/CHANGELOG.template DELETED
@@ -1,12 +0,0 @@
1
- [v#.#.#] ([month] [YYYY])
2
- - [future tense verb] [feature]
3
- - Upgraded gems:
4
- - [gem]
5
- - Bugs fixes:
6
- - [future tense verb] [bug fix]
7
- - Bug tracker items:
8
- - [item]
9
- - Security Fixes:
10
- - High: (Authenticated|Unauthenticated) (admin|author|contributor) [vulnerability description]
11
- - Medium: (Authenticated|Unauthenticated) (admin|author|contributor) [vulnerability description]
12
- - Low: (Authenticated|Unauthenticated) (admin|author|contributor) [vulnerability description]
data/CONTRIBUTING.md DELETED
@@ -1,3 +0,0 @@
1
- # Plugin contribution guidelines
2
-
3
- See the Dradis Framework's [CONTRIBUTING.md](https://github.com/dradis/dradisframework/blob/master/CONTRIBUTING.md)
data/Gemfile DELETED
@@ -1,23 +0,0 @@
1
- source 'https://rubygems.org'
2
-
3
- # Declare your gem's dependencies in dradispro-duoweb.gemspec.
4
- # Bundler will treat runtime dependencies like base dependencies, and
5
- # development dependencies will be added by default to the :development group.
6
- gemspec
7
-
8
- # jquery-rails is used by the dummy application
9
- # gem "jquery-rails"
10
-
11
- # Declare any dependencies that are still in development here instead of in
12
- # your gemspec. These might include edge Rails or gems from your path or
13
- # Git. Remember to move these dependencies to your gemspec before releasing
14
- # your gem to rubygems.org.
15
-
16
- # To use debugger
17
- # gem 'debugger'
18
-
19
- if Dir.exist?('../dradis-plugins')
20
- gem 'dradis-plugins', path: '../dradis-plugins'
21
- else
22
- gem 'dradis-plugins', github: 'dradis/dradis-plugins'
23
- end
data/dradis-csv.gemspec DELETED
@@ -1,25 +0,0 @@
1
- $:.push File.expand_path('../lib', __FILE__)
2
- require 'dradis/plugins/csv/version'
3
- version = Dradis::Plugins::CSV::VERSION::STRING
4
-
5
- # Describe your gem and declare its dependencies:
6
- Gem::Specification.new do |spec|
7
- spec.platform = Gem::Platform::RUBY
8
- spec.name = 'dradis-csv'
9
- spec.version = version
10
- spec.summary = 'CSV add-on for the Dradis Framework.'
11
- spec.description = 'This add-on allows you to upload and parse CSV output into Dradis.'
12
-
13
- spec.license = 'GPL-2'
14
-
15
- spec.authors = ['Daniel Martin']
16
- spec.homepage = 'http://dradis.com'
17
-
18
- spec.files = `git ls-files`.split("\n")
19
- spec.executables = spec.files.grep(%r{^bin/}).map { |f| File.basename(f) }
20
- spec.test_files = spec.files.grep(%r{^(spec|features)/})
21
-
22
- spec.add_dependency 'dradis-plugins', '>= 4.0'
23
- spec.add_development_dependency 'bundler', '~> 2.0'
24
- spec.add_development_dependency 'rake'
25
- end
@@ -1,381 +0,0 @@
1
- require 'rails_helper'
2
-
3
- # To run, execute from Dradis main app folder:
4
- # bin/rspec [dradis-plugins path]/spec/features/upload_spec.rb
5
-
6
- describe 'upload feature', js: true do
7
- before do
8
- login_to_project_as_user
9
- visit project_upload_path(@project)
10
- end
11
-
12
- context 'uploading a CSV file' do
13
- let(:file_path) { File.expand_path('../fixtures/files/simple.csv', __dir__) }
14
- before do
15
- @headers = CSV.open(file_path, &:readline)
16
-
17
- find('#state + .combobox').click
18
- find('#state ~ .combobox-menu .combobox-option', text: 'Published').click
19
-
20
- find('#uploader + .combobox').click
21
- find('#uploader ~ .combobox-menu .combobox-option', text: 'Dradis::Plugins::CSV').click
22
-
23
- attach_file 'file', file_path, visible: false, disabled: false
24
-
25
- expect(page).to have_text('CSV Upload Mapping', wait: 30)
26
- end
27
-
28
- it 'redirects to the mapping page' do
29
- expect(current_path).to eq(csv.new_project_upload_path(@project))
30
- end
31
-
32
- it 'lists the fields in the table' do
33
- within('tbody') do
34
- @headers.each do |header|
35
- expect(page).to have_selector('td', text: header)
36
- end
37
- end
38
- end
39
-
40
- context 'mapping CSV columns' do
41
- context 'when identifier not selected' do
42
- it 'shows a validation message on the page' do
43
- within all('tbody tr')[3] do
44
- select 'Evidence Field'
45
- end
46
-
47
- click_button 'Import CSV'
48
- expect(page).to have_text('An Issue ID must be selected.')
49
- end
50
- end
51
-
52
- context 'when there are evidence type but no node type selected' do
53
- it 'shows a validation message on the page' do
54
- within all('tbody tr')[2] do
55
- select 'Issue ID'
56
- end
57
-
58
- within all('tbody tr')[3] do
59
- select 'Evidence Field'
60
- end
61
-
62
- click_button 'Import CSV'
63
- expect(page).to have_text('A Node Label must be selected to import evidence records.')
64
- end
65
- end
66
-
67
- context 'valid states' do
68
- it 'imports the issues based on the selected state' do
69
- select 'Issue ID', from: 'mappings[field_attributes][0][type]'
70
- select 'Node', from: 'mappings[field_attributes][3][type]'
71
- select 'Evidence Field', from: 'mappings[field_attributes][4][type]'
72
- select 'Evidence Field', from: 'mappings[field_attributes][5][type]'
73
-
74
- perform_enqueued_jobs do
75
- click_button 'Import CSV'
76
-
77
- find('#console .log', wait: 30, match: :first)
78
-
79
- expect(page).to have_text('Worker process completed.')
80
-
81
- expect(Issue.published.count).to eq(1)
82
- end
83
- end
84
- end
85
-
86
- context 'invalid states' do
87
- it 'imports the issues as draft' do
88
- select 'Issue ID', from: 'mappings[field_attributes][0][type]'
89
- select 'Node', from: 'mappings[field_attributes][3][type]'
90
- select 'Evidence Field', from: 'mappings[field_attributes][4][type]'
91
- select 'Evidence Field', from: 'mappings[field_attributes][5][type]'
92
-
93
- page.execute_script(<<~JS)
94
- const select = document.querySelector('#state');
95
- select.value = 'tampered_value';
96
- JS
97
-
98
- perform_enqueued_jobs do
99
- click_button 'Import CSV'
100
-
101
- find('#console .log', wait: 30, match: :first)
102
-
103
- expect(page).to have_text('Worker process completed.')
104
-
105
- expect(Issue.published.count).to eq(0)
106
- end
107
- end
108
- end
109
-
110
- context 'when project does not have RTP' do
111
- it 'imports all columns as fields' do
112
- select 'Issue ID', from: 'mappings[field_attributes][0][type]'
113
- select 'Node', from: 'mappings[field_attributes][3][type]'
114
- select 'Evidence Field', from: 'mappings[field_attributes][4][type]'
115
- select 'Evidence Field', from: 'mappings[field_attributes][5][type]'
116
-
117
- perform_enqueued_jobs do
118
- click_button 'Import CSV'
119
-
120
- find('#console .log', wait: 30, match: :first)
121
-
122
- expect(page).to have_text('Worker process completed.')
123
-
124
- issue = Issue.last
125
- expect(issue.fields).to eq({ 'Description' => 'Test CSV', 'Title' => 'SQL Injection', 'VulnerabilityCategory' =>'High', 'plugin' => 'csv', 'plugin_id' => '1' })
126
-
127
- node = issue.affected.first
128
- expect(node.label).to eq('10.0.0.1')
129
-
130
- evidence = node.evidence.first
131
- expect(evidence.fields).to eq({ 'Label' => '10.0.0.1', 'Title' => 'SQL Injection', 'Location' => '10.0.0.1', 'Port' => '443' })
132
-
133
- expect(Mapping.where(component: 'csv')).to be_empty
134
- end
135
- end
136
- end
137
-
138
- context 'when project have RTP' do
139
- before do
140
- rtp = create(:report_template_properties, evidence_fields: evidence_fields, issue_fields: issue_fields)
141
- @project.update(report_template_properties: rtp)
142
-
143
- page.refresh
144
- end
145
-
146
- context 'without fields' do
147
- let (:evidence_fields) { [] }
148
- let (:issue_fields) { [] }
149
-
150
- it 'creates records with fields from the headers' do
151
- select 'Issue ID', from: 'mappings[field_attributes][0][type]'
152
- select 'Node', from: 'mappings[field_attributes][3][type]'
153
- select 'Evidence Field', from: 'mappings[field_attributes][4][type]'
154
- select 'Evidence Field', from: 'mappings[field_attributes][5][type]'
155
-
156
- perform_enqueued_jobs do
157
- click_button 'Import CSV'
158
-
159
- find('#console .log', wait: 30, match: :first)
160
-
161
- expect(page).to have_text('Worker process completed.')
162
-
163
- issue = Issue.last
164
- expect(issue.fields).to eq({ 'Description' => 'Test CSV', 'Title' => 'SQL Injection', 'Vulnerability Category' =>'High', 'plugin' => 'csv', 'plugin_id' => '1' })
165
-
166
- node = issue.affected.first
167
- expect(node.label).to eq('10.0.0.1')
168
-
169
- evidence = node.evidence.first
170
- expect(evidence.fields).to eq({ 'Label' => '10.0.0.1', 'Location' => '10.0.0.1', 'Title' => 'SQL Injection', 'Port' => '443' })
171
- end
172
- end
173
- end
174
-
175
- context 'with fields' do
176
- let (:evidence_fields) {
177
- [
178
- { name: 'Location', type: :string, default: true },
179
- { name: 'Port', type: :string, default: true}
180
- ]
181
- }
182
-
183
- let (:issue_fields) {
184
- [
185
- { name: 'Title', type: :string, default: true },
186
- { name: 'Description', type: :string, default: true},
187
- { name: 'Severity', type: :string, default: true}
188
- ]
189
- }
190
-
191
- it 'shows the available fields for the selected type' do
192
- select 'Issue Field', from: 'mappings[field_attributes][1][type]'
193
-
194
- issue_fields.each do |field|
195
- expect(page).to have_selector('option', text: field[:name])
196
- end
197
-
198
- select 'Evidence Field', from: 'mappings[field_attributes][4][type]'
199
-
200
- evidence_fields.each do |field|
201
- expect(page).to have_selector('option', text: field[:name])
202
- end
203
- end
204
-
205
- it 'can select which columns to import' do
206
- select 'Issue ID', from: 'mappings[field_attributes][0][type]'
207
-
208
- select 'Issue Field', from: 'mappings[field_attributes][1][type]'
209
- select 'Title', from: 'mappings[field_attributes][1][field]'
210
-
211
- select 'Issue Field', from: 'mappings[field_attributes][2][type]'
212
- select 'Description', from: 'mappings[field_attributes][2][field]'
213
-
214
- select 'Node', from: 'mappings[field_attributes][3][type]'
215
-
216
- select 'Evidence Field', from: 'mappings[field_attributes][4][type]'
217
- select 'Location', from: 'mappings[field_attributes][4][field]'
218
-
219
- select 'Evidence Field', from: 'mappings[field_attributes][5][type]'
220
- select 'Port', from: 'mappings[field_attributes][5][field]'
221
-
222
- select 'Issue Field', from: 'mappings[field_attributes][6][type]'
223
- select 'Severity', from: 'mappings[field_attributes][6][field]'
224
-
225
- perform_enqueued_jobs do
226
- click_button 'Import CSV'
227
-
228
- find('#console .log', wait: 30, match: :first)
229
-
230
- expect(page).to have_text('Worker process completed.')
231
-
232
- issue = Issue.last
233
- expect(issue.fields).to eq({ 'Description' => 'Test CSV', 'Title' => 'SQL Injection', 'Severity' => 'High', 'plugin' => 'csv', 'plugin_id' => '1' })
234
-
235
- node = issue.affected.first
236
- expect(node.label).to eq('10.0.0.1')
237
-
238
- evidence = node.evidence.first
239
- expect(evidence.fields).to eq({ 'Label' => '10.0.0.1', 'Location' => '10.0.0.1', 'Title' => 'SQL Injection', 'Port' => '443' })
240
-
241
- headers = CSV.open(file_path, &:readline)
242
- issue_source = Dradis::Plugins::CSV.mapping_source(headers: headers, entity: :issue)
243
- evidence_source = Dradis::Plugins::CSV.mapping_source(headers: headers, entity: :evidence)
244
- expect(Mapping.where(component: 'csv').pluck(:source)).to match_array([issue_source, evidence_source])
245
- end
246
- end
247
- end
248
-
249
- context 'when no evidence fields' do
250
- let (:evidence_fields) { [] }
251
- let (:issue_fields) { [] }
252
-
253
- it 'still creates evidence record' do
254
- within all('tbody tr')[0] do
255
- select 'Issue ID'
256
- end
257
-
258
- within all('tbody tr')[1] do
259
- select 'Issue Field'
260
- end
261
-
262
- within all('tbody tr')[3] do
263
- select 'Node'
264
- end
265
-
266
- within all('tbody tr')[5] do
267
- select 'Issue Field'
268
- end
269
-
270
- perform_enqueued_jobs do
271
- click_button 'Import CSV'
272
-
273
- find('#console .log', wait: 30, match: :first)
274
-
275
- expect(page).to have_text('Worker process completed.')
276
-
277
- issue = Issue.last
278
- expect(issue.fields).to include({ 'Title' => 'SQL Injection', 'plugin' => 'csv', 'plugin_id' => '1' })
279
-
280
- node = issue.affected.first
281
- expect(node.label).to eq('10.0.0.1')
282
-
283
- evidence = node.evidence.first
284
- expect(evidence.content).to eq('')
285
- end
286
- end
287
- end
288
- end
289
- end
290
- end
291
-
292
- context 'uploading a CSV file with a saved mapping' do
293
- let(:file_path) { File.expand_path('../fixtures/files/simple.csv', __dir__) }
294
-
295
- before do
296
- rtp = create(
297
- :report_template_properties,
298
- evidence_fields: [{ name: 'Location', type: :string, default: true }],
299
- issue_fields: [{ name: 'Title', type: :string, default: true }]
300
- )
301
- @project.update(report_template_properties: rtp)
302
-
303
- headers = CSV.open(file_path, &:readline)
304
- issue_mapping = Mapping.create!(
305
- component: 'csv',
306
- source: Dradis::Plugins::CSV.mapping_source(headers: headers, entity: :issue),
307
- destination: rtp.as_mapping_destination
308
- )
309
- issue_mapping.mapping_fields.create!(
310
- source_field: 'Id', destination_field: 'plugin_id', content: '{{ csv[Id] }}'
311
- )
312
- issue_mapping.mapping_fields.create!(
313
- source_field: 'Title', destination_field: 'Title', content: '{{ csv[Title] }}'
314
- )
315
-
316
- evidence_mapping = Mapping.create!(
317
- component: 'csv',
318
- source: Dradis::Plugins::CSV.mapping_source(headers: headers, entity: :evidence),
319
- destination: rtp.as_mapping_destination
320
- )
321
- evidence_mapping.mapping_fields.create!(
322
- source_field: 'Host', destination_field: 'node_label', content: '{{ csv[Host] }}'
323
- )
324
- evidence_mapping.mapping_fields.create!(
325
- source_field: 'Location', destination_field: 'Location', content: '{{ csv[Location] }}'
326
- )
327
-
328
- page.refresh
329
-
330
- find('#uploader + .combobox').click
331
- find('#uploader ~ .combobox-menu .combobox-option', text: 'Dradis::Plugins::CSV').click
332
- end
333
-
334
- it 'imports immediately using the saved mapping, without showing the mapper' do
335
- attach_file 'file', file_path, visible: false, disabled: false
336
-
337
- expect(page).to have_text('CSV imported using its saved mapping', wait: 30)
338
- expect(current_path).to eq(main_app.project_upload_manager_path(@project))
339
-
340
- issue = Issue.last
341
- expect(issue.fields).to include('Title' => 'SQL Injection', 'plugin' => 'csv', 'plugin_id' => '1')
342
-
343
- node = issue.affected.first
344
- expect(node.label).to eq('10.0.0.1')
345
-
346
- evidence = node.evidence.first
347
- expect(evidence.fields).to include('Location' => '10.0.0.1')
348
- end
349
- end
350
-
351
- describe 'CSV file samples' do
352
- before do
353
- find('#uploader + .combobox').click
354
- find('#uploader ~ .combobox-menu .combobox-option', text: 'Dradis::Plugins::CSV').click
355
-
356
- attach_file 'file', file_path, visible: false, disabled: false
357
- end
358
-
359
- context 'uploading a malformed CSV file' do
360
- let(:file_path) { File.expand_path('../fixtures/files/simple_malformed.csv', __dir__) }
361
-
362
- it 'redirects to upload manager with error' do
363
- find('.alert.alert-danger', wait: 30)
364
-
365
- expect(page).to have_text('The uploaded file is not a valid CSV file')
366
- expect(current_path).to eq(main_app.project_upload_manager_path(@project))
367
- end
368
- end
369
-
370
- context 'uploading any file other than CSV' do
371
- let(:file_path) { Rails.root.join('spec/fixtures/files/rails.png') }
372
-
373
- it 'redirects to upload manager with error' do
374
- find('.alert.alert-danger', wait: 30)
375
-
376
- expect(page).to have_text('The uploaded file is not a CSV file.')
377
- expect(current_path).to eq(main_app.project_upload_manager_path(@project))
378
- end
379
- end
380
- end
381
- end
@@ -1,2 +0,0 @@
1
- "Id","Title","Description","Host","Location","Port","Vulnerability Category"
2
- "1","SQL Injection","Test CSV","10.0.0.1","10.0.0.1","443","High"
@@ -1,2 +0,0 @@
1
- "Id","Title","Description","Host","Location","Port","Vulnerability Category"
2
- "1","SQL Injection","Test CSV","10.0.0.1","10.0.0.1","443","High"
@@ -1,2 +0,0 @@
1
- "Id";"Title";"Description";"Host";"Location";"Port"
2
- "1";"SQL Injection";"Test CSV";"10.0.0.1";"10.0.0.1";"443"
@@ -1,32 +0,0 @@
1
- require 'rails_helper'
2
-
3
- RSpec.describe Dradis::Plugins::CSV::MappingImportJob do
4
- let(:file) { File.expand_path('../../../.../../../fixtures/files/simple.csv', __dir__) }
5
-
6
- let(:perform_job) do
7
- described_class.new.perform(
8
- default_user_id: create(:user).id,
9
- file: file,
10
- headers: CSV.open(file, &:readline),
11
- mappings: {},
12
- project_id: create(:project).id,
13
- state: 'draft',
14
- uid: 1
15
- )
16
- end
17
-
18
- describe '#perform' do
19
- it 'calls Importer#import_rows' do
20
- dbl = double('Importer')
21
- allow(Dradis::Plugins::CSV::Importer).to receive(:new).and_return(dbl)
22
- expect(dbl).to receive(:import_rows).and_return(true)
23
-
24
- perform_job
25
- end
26
-
27
- it 'writes a known final line in the log' do
28
- perform_job
29
- expect(Log.last.text).to eq 'Worker process completed.'
30
- end
31
- end
32
- end
@@ -1,223 +0,0 @@
1
- require 'rails_helper'
2
-
3
- RSpec.describe Dradis::Plugins::CSV::Importer do
4
- let(:file) { File.expand_path('../../../.../../../fixtures/files/simple.csv', __dir__) }
5
- let(:headers) { CSV.open(file, &:readline) }
6
- let(:project) { create(:project) }
7
-
8
- let(:instance) do
9
- described_class.new(
10
- default_user_id: create(:user).id,
11
- logger: Log.new(uid: 1),
12
- plugin: Dradis::Plugins::CSV,
13
- project_id: project.id
14
- )
15
- end
16
-
17
- let(:import_rows) do
18
- instance.import_rows(file: file, headers: headers, mappings: mappings)
19
- end
20
-
21
- describe '#import_rows' do
22
- context 'when project has RTP' do
23
- let(:mappings) do
24
- {
25
- '0' => { 'type' => 'identifier' },
26
- '1' => { 'type' => 'issue', 'field' => 'MyTitle' },
27
- '3' => { 'type' => 'node', 'field' => '' },
28
- '4' => { 'type' => 'evidence', 'field' => 'MyLocation' },
29
- '5' => { 'type' => 'evidence', 'field' => '' }
30
- }
31
- end
32
-
33
- before do
34
- project.update(report_template_properties: create(:report_template_properties))
35
- end
36
-
37
- it 'uses the field as Dradis Field' do
38
- import_rows
39
-
40
- issue = Issue.first
41
- expect(issue.fields).to eq({ 'MyTitle' => 'SQL Injection', 'plugin' => 'csv', 'plugin_id' => '1' })
42
-
43
- node = issue.affected.first
44
- expect(node.label).to eq('10.0.0.1')
45
-
46
- evidence = node.evidence.first
47
- expect(evidence.fields).to eq({ 'Label' => '10.0.0.1', 'Title' => '(No #[Title]# field)', 'MyLocation' => '10.0.0.1' })
48
- end
49
- end
50
-
51
- context 'when project does not have RTP' do
52
- let(:mappings) do
53
- {
54
- '0' => { 'type' => 'identifier' },
55
- '1' => { 'type' => 'issue', 'field' => 'MyTitle' },
56
- '3' => { 'type' => 'node', 'field' => '' },
57
- '4' => { 'type' => 'evidence', 'field' => 'MyLocation' },
58
- '5' => { 'type' => 'evidence', 'field' => '' },
59
- '6' => { 'type' => 'issue', 'field' => '' }
60
- }
61
- end
62
-
63
- it 'uses the column name as Dradis Field' do
64
- import_rows
65
-
66
- issue = Issue.first
67
- expect(issue.fields).to eq({ 'Title' => 'SQL Injection', 'VulnerabilityCategory' => 'High', 'plugin' => 'csv', 'plugin_id' => '1' })
68
-
69
- node = issue.affected.first
70
- expect(node.label).to eq('10.0.0.1')
71
-
72
- evidence = node.evidence.first
73
- expect(evidence.fields).to eq({ 'Label' => '10.0.0.1', 'Location' => '10.0.0.1', 'Port' => '443', 'Title' => 'SQL Injection' })
74
- end
75
-
76
- it 'strips out whitespace from column header' do
77
- import_rows
78
-
79
- issue = Issue.first
80
- expect(issue.fields.keys).to include('VulnerabilityCategory')
81
- end
82
- end
83
-
84
- context 'when mapping does not have a node type' do
85
- let(:mappings) do
86
- {
87
- '0' => { 'type' => 'identifier' },
88
- '1' => { 'type' => 'issue' },
89
- '4' => { 'type' => 'evidence' }
90
- }
91
- end
92
-
93
- it 'does not create node and evidence' do
94
- import_rows
95
-
96
- issue = Issue.last
97
- expect(issue.affected.length).to eq(0)
98
- expect(issue.evidence.length).to eq(0)
99
- end
100
- end
101
-
102
- context 'when no identifier is passed in' do
103
- let(:mappings) do
104
- {
105
- '1' => { 'type' => 'issue' },
106
- '4' => { 'type' => 'evidence' }
107
- }
108
- end
109
-
110
- it 'uses filename and row index as csv_id' do
111
- import_rows
112
-
113
- issue = Issue.last
114
- expect(issue.fields).to eq({ 'Title' => 'SQL Injection', 'plugin' => 'csv', 'plugin_id' => 'simple.csv-0' })
115
- end
116
- end
117
-
118
- context 'when no evidence fields' do
119
- let(:mappings) do
120
- {
121
- '0' => { 'type' => 'identifier' },
122
- '1' => { 'type' => 'issue', 'field' => 'MyTitle' },
123
- '3' => { 'type' => 'node', 'field' => '' }
124
- }
125
- end
126
-
127
- it 'still creates evidence record' do
128
- import_rows
129
-
130
- issue = Issue.first
131
- expect(issue.fields).to eq({ 'Title' => 'SQL Injection', 'plugin' => 'csv', 'plugin_id' => '1' })
132
-
133
- node = issue.affected.first
134
- expect(node.label).to eq('10.0.0.1')
135
-
136
- evidence = node.evidence.first
137
- expect(evidence.content).to eq('')
138
- end
139
- end
140
- end
141
-
142
- describe '#import' do
143
- context 'when the project has no RTP' do
144
- it 'does not import anything' do
145
- expect(instance.import(file: file)).to eq(false)
146
- expect(Issue.count).to eq(0)
147
- end
148
- end
149
-
150
- context 'when the project has RTP but no saved mapping' do
151
- before do
152
- project.update(report_template_properties: create(:report_template_properties))
153
- end
154
-
155
- it 'does not import anything' do
156
- expect(instance.import(file: file)).to eq(false)
157
- expect(Issue.count).to eq(0)
158
- end
159
- end
160
-
161
- context 'when a saved mapping exists for these headers' do
162
- let(:rtp) { create(:report_template_properties) }
163
-
164
- before do
165
- project.update(report_template_properties: rtp)
166
-
167
- issue_mapping = Mapping.create!(
168
- component: 'csv',
169
- source: Dradis::Plugins::CSV.mapping_source(headers: headers, entity: :issue),
170
- destination: rtp.as_mapping_destination
171
- )
172
- issue_mapping.mapping_fields.create!(
173
- source_field: 'Id', destination_field: 'plugin_id', content: '{{ csv[Id] }}'
174
- )
175
- issue_mapping.mapping_fields.create!(
176
- source_field: 'Title', destination_field: 'Title', content: '{{ csv[Title] }}'
177
- )
178
-
179
- evidence_mapping = Mapping.create!(
180
- component: 'csv',
181
- source: Dradis::Plugins::CSV.mapping_source(headers: headers, entity: :evidence),
182
- destination: rtp.as_mapping_destination
183
- )
184
- evidence_mapping.mapping_fields.create!(
185
- source_field: 'Host', destination_field: 'node_label', content: '{{ csv[Host] }}'
186
- )
187
- evidence_mapping.mapping_fields.create!(
188
- source_field: 'Location', destination_field: 'Location', content: '{{ csv[Location] }}'
189
- )
190
- end
191
-
192
- it 'imports using the saved mapping' do
193
- expect(instance.import(file: file)).to eq(true)
194
-
195
- issue = Issue.last
196
- expect(issue.fields).to include('Title' => 'SQL Injection', 'plugin_id' => '1')
197
-
198
- node = issue.affected.first
199
- expect(node.label).to eq('10.0.0.1')
200
-
201
- evidence = node.evidence.first
202
- expect(evidence.fields).to include('Location' => '10.0.0.1')
203
- end
204
- end
205
- end
206
-
207
- describe '.templates' do
208
- let(:rtp) { create(:report_template_properties) }
209
- let(:issue_source) { Dradis::Plugins::CSV.mapping_source(headers: headers, entity: :issue) }
210
- let(:evidence_source) { Dradis::Plugins::CSV.mapping_source(headers: headers, entity: :evidence) }
211
-
212
- before do
213
- Mapping.create!(component: 'csv', source: issue_source, destination: rtp.as_mapping_destination)
214
- Mapping.create!(component: 'csv', source: evidence_source, destination: rtp.as_mapping_destination)
215
- end
216
-
217
- it 'groups the currently known sources by entity' do
218
- expect(described_class.templates).to eq(
219
- issue: [issue_source], evidence: [evidence_source]
220
- )
221
- end
222
- end
223
- end
@@ -1,139 +0,0 @@
1
- require 'rails_helper'
2
-
3
- RSpec.describe Dradis::Plugins::CSV::MappingForm do
4
- let(:headers) { ['Id', 'Title', 'Host', 'Vulnerability Category'] }
5
- let(:destination) { 'rtp_1' }
6
- let(:rtp_fields) { { issue: ['Title'], evidence: ['Rating'] } }
7
- let(:column_mappings) do
8
- {
9
- '0' => { 'type' => 'identifier' },
10
- '1' => { 'type' => 'issue', 'field' => 'Title' },
11
- '2' => { 'type' => 'node', 'field' => '' },
12
- '3' => { 'type' => 'evidence', 'field' => 'Rating' }
13
- }
14
- end
15
-
16
- let(:issue_source) do
17
- Dradis::Plugins::CSV.mapping_source(headers: headers, entity: :issue)
18
- end
19
- let(:evidence_source) do
20
- Dradis::Plugins::CSV.mapping_source(headers: headers, entity: :evidence)
21
- end
22
-
23
- subject(:form) do
24
- described_class.new(destination: destination, headers: headers, rtp_fields: rtp_fields)
25
- end
26
-
27
- let(:save) { form.save(column_mappings: column_mappings) }
28
-
29
- describe '#save' do
30
- it 'creates one mapping per entity with normalized source fields' do
31
- expect { save }.to change { Mapping.count }.by(2)
32
-
33
- issue_mapping = Mapping.find_by(
34
- component: 'csv', source: issue_source, destination: destination
35
- )
36
- expect(issue_mapping.mapping_fields.pluck(:destination_field, :source_field, :content)).to match_array([
37
- ['plugin_id', 'Id', '{{ csv[Id] }}'],
38
- ['Title', 'Title', '{{ csv[Title] }}']
39
- ])
40
-
41
- evidence_mapping = Mapping.find_by(
42
- component: 'csv', source: evidence_source, destination: destination
43
- )
44
- expect(evidence_mapping.mapping_fields.pluck(:destination_field, :source_field, :content)).to match_array([
45
- ['node_label', 'Host', '{{ csv[Host] }}'],
46
- ['Rating', 'VulnerabilityCategory', '{{ csv[VulnerabilityCategory] }}']
47
- ])
48
- end
49
-
50
- context 'without a destination' do
51
- let(:destination) { nil }
52
-
53
- it 'does not create any mappings' do
54
- expect { save }.not_to change { Mapping.count }
55
- end
56
- end
57
-
58
- context 'when only skipped or blank columns are assigned to an entity' do
59
- let(:column_mappings) do
60
- {
61
- '0' => { 'type' => 'identifier' },
62
- '1' => { 'type' => 'issue', 'field' => '' },
63
- '3' => { 'type' => 'skip' }
64
- }
65
- end
66
-
67
- it 'only stores the fields with a destination' do
68
- expect { save }.to change { Mapping.count }.by(1)
69
-
70
- issue_mapping = Mapping.find_by(
71
- component: 'csv', source: issue_source, destination: destination
72
- )
73
- expect(issue_mapping.mapping_fields.pluck(:destination_field)).to eq(['plugin_id'])
74
- end
75
- end
76
-
77
- context 'when the RTP has no fields defined for an entity' do
78
- let(:rtp_fields) { { issue: ['Title'], evidence: [] } }
79
-
80
- it 'does not create a mapping for that entity, even for its identifier/node column' do
81
- expect { save }.to change { Mapping.count }.by(1)
82
-
83
- expect(Mapping.find_by(source: evidence_source)).to be_nil
84
-
85
- issue_mapping = Mapping.find_by(
86
- component: 'csv', source: issue_source, destination: destination
87
- )
88
- expect(issue_mapping.mapping_fields.pluck(:destination_field)).to match_array(['plugin_id', 'Title'])
89
- end
90
- end
91
-
92
- context 'when the RTP has no fields defined for either entity' do
93
- let(:rtp_fields) { { issue: [], evidence: [] } }
94
-
95
- it 'does not create any mappings' do
96
- expect { save }.not_to change { Mapping.count }
97
- end
98
- end
99
-
100
- context 'when a column is assigned to a custom field' do
101
- let(:column_mappings) do
102
- {
103
- '0' => { 'type' => 'identifier' },
104
- '1' => { 'type' => 'issue', 'field' => 'Custom Field', 'custom_field' => 'CVSS Score' },
105
- '2' => { 'type' => 'node', 'field' => '' },
106
- '3' => { 'type' => 'evidence', 'field' => 'Rating' }
107
- }
108
- end
109
-
110
- it 'stores the typed-in name as the destination field' do
111
- save
112
-
113
- issue_mapping = Mapping.find_by(
114
- component: 'csv', source: issue_source, destination: destination
115
- )
116
- expect(issue_mapping.mapping_fields.pluck(:destination_field)).to match_array(['plugin_id', 'CVSS Score'])
117
- end
118
-
119
- context 'when no custom field name is provided' do
120
- let(:column_mappings) do
121
- {
122
- '0' => { 'type' => 'identifier' },
123
- '1' => { 'type' => 'issue', 'field' => 'Custom Field', 'custom_field' => '' },
124
- '3' => { 'type' => 'skip' }
125
- }
126
- end
127
-
128
- it 'does not store a field for that column' do
129
- save
130
-
131
- issue_mapping = Mapping.find_by(
132
- component: 'csv', source: issue_source, destination: destination
133
- )
134
- expect(issue_mapping.mapping_fields.pluck(:destination_field)).to eq(['plugin_id'])
135
- end
136
- end
137
- end
138
- end
139
- end