dradis-csv 4.4.0 → 4.5.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.
Files changed (38) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +2 -66
  3. data/CONTRIBUTING.md +1 -1
  4. data/Gemfile +6 -0
  5. data/README.md +3 -18
  6. data/app/assets/javascripts/dradis/plugins/csv/manifests/tylium.js +1 -0
  7. data/app/assets/javascripts/dradis/plugins/csv/upload.js +114 -0
  8. data/app/assets/stylesheets/dradis/plugins/csv/manifests/tylium.scss +1 -0
  9. data/app/assets/stylesheets/dradis/plugins/csv/upload.scss +39 -0
  10. data/app/controllers/dradis/plugins/csv/upload_controller.rb +66 -0
  11. data/app/jobs/dradis/plugins/csv/mapping_import_job.rb +32 -0
  12. data/app/views/dradis/plugins/csv/upload/create.js.erb +4 -0
  13. data/app/views/dradis/plugins/csv/upload/new.html.erb +81 -0
  14. data/config/initializers/inflections.rb +3 -0
  15. data/config/routes.rb +3 -1
  16. data/dradis-csv.gemspec +12 -21
  17. data/lib/dradis/plugins/csv/engine.rb +5 -13
  18. data/lib/dradis/plugins/csv/gem_version.rb +4 -4
  19. data/lib/dradis/plugins/csv/importer.rb +76 -0
  20. data/lib/dradis/plugins/csv.rb +3 -2
  21. data/lib/dradis-csv.rb +1 -3
  22. data/spec/features/upload_spec.rb +267 -0
  23. data/spec/fixtures/files/simple.csv +2 -0
  24. data/spec/fixtures/files/simple_malformed.csv +2 -0
  25. data/spec/jobs/dradis/plugins/csv/mapping_import_job_spec.rb +30 -0
  26. data/spec/lib/dradis/plugins/csv/importer_spec.rb +140 -0
  27. metadata +25 -46
  28. data/.github/issue_template.md +0 -16
  29. data/.github/pull_request_template.md +0 -36
  30. data/.gitignore +0 -8
  31. data/.rspec +0 -2
  32. data/app/controllers/dradis/plugins/csv/base_controller.rb +0 -19
  33. data/app/views/dradis/plugins/csv/export/_index-content.html.erb +0 -10
  34. data/app/views/dradis/plugins/csv/export/_index-tabs.html.erb +0 -3
  35. data/lib/dradis/plugins/csv/exporter.rb +0 -60
  36. data/lib/tasks/thorfile.rb +0 -28
  37. data/spec/csv_export_spec.rb +0 -5
  38. data/spec/spec_helper.rb +0 -4
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a6fefe10783a10cc6954754c3847db4b6fbd47166a468fb2c29504be0c1545a4
4
- data.tar.gz: 151ae7d3bb6bac757ed3dae82034d8274f493d38ccec7e87f5dbc3d44bbeb65c
3
+ metadata.gz: 7d5090a66f9402d6d81382a775932db5a89514940f7d3061a1aa5c595341ba73
4
+ data.tar.gz: 82ac1596529a3975c76932630ef887d9e0f6de522a1e901f1e2e253e810f3635
5
5
  SHA512:
6
- metadata.gz: 07cc1b0c5ddb0b73f0a8ff0bc843bf51c34fa291f09e14213815f2e5fe9927b897765054be1bdae41a833b26827e6ec1fee51941720ad5ceca47acad182ca0a6
7
- data.tar.gz: ac7c0fb2f432c3104c0a42d997ad7d517b533918e3222f7aaf2ccddd730bbd8dc94f934792f27b71803f711bfdcf13c59aa5278d8ed6d89641c8f90c26e53f7b
6
+ metadata.gz: e7881df65ac54e633ee282316a670819b6a73a0ab33102e5233701c098f5129058d7a5321526cf9179a94ae13a154b667eabe70e92e5cc4faadf36a31e3d5504
7
+ data.tar.gz: fa96b2b762366addbe26acf842bd38aed09a2c2b91f46ec70deaf773084237f5f7c42cd4cd35c1f4a6836e4ccff029cbabff56b594a40284092783f6b46e8929
data/CHANGELOG.md CHANGED
@@ -1,66 +1,2 @@
1
- v4.4.0 (June 2022)
2
- - No changes
3
-
4
- v4.3.0 (April 2022)
5
- - No changes
6
-
7
- v4.2.0 (February 2022)
8
- - No changes
9
-
10
- v4.1.0 (November 2021)
11
- - No changes
12
-
13
- v4.0.0 (July 2021)
14
- - No changes
15
-
16
- v3.22.0 (April 2021)
17
- - No changes
18
-
19
- v3.21.0 (February 2021)
20
- - No changes
21
-
22
- v3.20.0 (December 2020)
23
- - Add views for the export view
24
- - Use NamingService to build export filename
25
-
26
- v3.19.0 (September 2020)
27
- - No changes
28
-
29
- v3.18.0 (July 2020)
30
- - No changes
31
-
32
- v3.17.0 (May 2020)
33
- - No changes
34
-
35
- v3.16.0 (February 2020)
36
- - No changes
37
-
38
- v3.15.0 (November 2019)
39
- - No changes
40
-
41
- v3.14.0 (August 2019)
42
- - No changes
43
-
44
- v3.13.0 (June 2019)
45
- - No changes
46
-
47
- v3.12.0 (March 2019)
48
- - No changes
49
-
50
- v3.11.0 (November 2018)
51
- - No changes
52
-
53
- v3.10.0 (August 2018)
54
- - No changes
55
-
56
- v3.9.0 (January 2018)
57
- - No changes
58
-
59
- v3.8.0 (September 2017)
60
- - No changes
61
-
62
- v3.7.0 (July 2017)
63
- - No changes
64
-
65
- v3.6.0 (March 2017)
66
- - No changes
1
+ v4.5.0 (August 2022)
2
+ - Initial implementation
data/CONTRIBUTING.md CHANGED
@@ -1,3 +1,3 @@
1
1
  # Plugin contribution guidelines
2
2
 
3
- See the Dradis Framework's [CONTRIBUTING.md](https://github.com/dradis/dradisframework/blob/master/CONTRIBUTING.md)
3
+ See the Dradis Framework's [CONTRIBUTING.md](https://github.com/dradis/dradisframework/blob/master/CONTRIBUTING.md)
data/Gemfile CHANGED
@@ -15,3 +15,9 @@ gemspec
15
15
 
16
16
  # To use debugger
17
17
  # gem 'debugger'
18
+
19
+ if Dir.exists?('../dradis-plugins')
20
+ gem 'dradis-plugins', path: '../dradis-plugins'
21
+ else
22
+ gem 'dradis-plugins', github: 'dradis/dradis-plugins'
23
+ end
data/README.md CHANGED
@@ -1,23 +1,8 @@
1
- # CSV plugin for Dradis
1
+ # Customizable CSV Importer for the Dradis Framework
2
2
 
3
- [![Build Status](https://secure.travis-ci.org/dradis/dradis-csv.png?branch=master)](http://travis-ci.org/dradis/dradis-csv)
3
+ Upload CSV files into Dradis.
4
4
 
5
- Export Dradis findings into Comma Separated Value (CSV) format.
6
-
7
-
8
- ## Installation
9
-
10
- The plugin requires [Dradis Community Edition](http://dradisframework.org) 3.0 or [Dradis Professional Edition](http://securityroots.com/dradispro/) 1.11 or higher.
11
-
12
- Add the CSV plugin to your `Gemfile.plugins`:
13
-
14
- gem 'dradis-csv'
15
-
16
- And
17
-
18
- bundle install
19
-
20
- And restart your service.
5
+ The add-on requires [Dradis CE](https://dradisframework.org/) > 4.5, or [Dradis Pro](https://dradisframework.com/pro/) > 4.5.
21
6
 
22
7
 
23
8
  ## More information
@@ -0,0 +1 @@
1
+ //= require dradis/plugins/csv/upload
@@ -0,0 +1,114 @@
1
+ window.addEventListener('job-done', function(){
2
+ if ($('body.upload.index').length) {
3
+ var uploader = document.getElementById('uploader');
4
+
5
+ if (uploader.value === 'Dradis::Plugins::CSV') {
6
+ var path = window.location.pathname;
7
+ var project_path = path.split('/').slice(0, -1).join('/');
8
+ var attachment = $('#attachment').val();
9
+
10
+ var redirectPath = project_path + '/addons/csv/upload/new?attachment=' + attachment;
11
+ Turbolinks.visit(redirectPath);
12
+ }
13
+ }
14
+ });
15
+
16
+ document.addEventListener('turbolinks:load', function() {
17
+ if ($('body.upload.new').length) {
18
+ $('[data-behavior=type-select]').on('change', function() {
19
+ var $nodeSelect = $('select option[value="node"]:selected').parent();
20
+
21
+ // Disable Node Label option
22
+ if ($nodeSelect.length) {
23
+ $('[data-behavior=type-select]').not($nodeSelect).find('option[value="node"]').attr('disabled', 'disabled');
24
+ } else {
25
+ $('[data-behavior=type-select]').find('option[value="node"]').removeAttr('disabled');
26
+ }
27
+
28
+ $(this).parents('tr').toggleClass('issue-type', $(this).val() == 'issue');
29
+
30
+ // Update fields column labels
31
+ var $fieldLabel = $(this).closest('tr').find('[data-behavior=field-label]');
32
+
33
+ switch($(this).val()) {
34
+ case 'identifier':
35
+ $fieldLabel.text('plugin_id');
36
+ break;
37
+ case 'node':
38
+ $fieldLabel.text('Label');
39
+ break;
40
+ case 'skip':
41
+ $fieldLabel.text('N/A');
42
+ break;
43
+ default:
44
+ var header = $fieldLabel.data('header');
45
+ $fieldLabel.text(header);
46
+ }
47
+
48
+ _setDradisFieldSelect($(this));
49
+ });
50
+
51
+ $('[data-behavior~=mapping-form]').submit(function() {
52
+ var valid = _validateIdentifierSelected() && _validateNodeSelected();
53
+
54
+ if (!valid) {
55
+ $(this).find('input[type="submit"]').attr('disabled', false).val('Import CSV');
56
+
57
+ $('[data-behavior~=view-content]').animate({
58
+ scrollTop: $('[data-behavior~=validation-messages]').scrollTop()
59
+ });
60
+ }
61
+
62
+ return valid;
63
+ });
64
+
65
+ // Private methods
66
+
67
+ function _setDradisFieldSelect($select) {
68
+ var $row = $select.closest('tr');
69
+
70
+ $row.find('.field-select').attr('disabled', 'disabled').addClass('d-none');
71
+ if ($select.val() == 'issue') {
72
+ $row.find('[data-behavior=issue-field-select]').removeAttr('disabled', 'disabled').removeClass('d-none');
73
+ }
74
+ else if ($select.val() == 'evidence') {
75
+ $row.find('[data-behavior=evidence-field-select]').removeAttr('disabled').removeClass('d-none');
76
+ }
77
+ else {
78
+ $row.find('[data-behavior=empty-field-select]').removeAttr('disabled').removeClass('d-none');
79
+ }
80
+ }
81
+
82
+ function _validateNodeSelected() {
83
+ var $validationMessage = $('[data-behavior~=node-type-validation-message]');
84
+ $validationMessage.addClass('d-none');
85
+
86
+ var selectedEvidenceCount = $('select option[value="evidence"]:selected').length;
87
+ var selectedNodeCount = $('select option[value="node"]:selected').length;
88
+
89
+ var valid = selectedEvidenceCount == 0 ||
90
+ (selectedEvidenceCount > 0 && selectedNodeCount > 0);
91
+
92
+ if (!valid) {
93
+ $validationMessage.removeClass('d-none');
94
+ }
95
+
96
+ return valid;
97
+ }
98
+
99
+ function _validateIdentifierSelected() {
100
+ var $validationMessage = $('[data-behavior~=issue-id-validation-message]');
101
+ $validationMessage.addClass('d-none');
102
+
103
+ var selectedIdentifierCount = $('select option[value="identifier"]:selected').length;
104
+
105
+ var valid = selectedIdentifierCount == 1;
106
+
107
+ if (!valid) {
108
+ $validationMessage.removeClass('d-none');
109
+ }
110
+
111
+ return valid;
112
+ }
113
+ }
114
+ });
@@ -0,0 +1 @@
1
+ //= require dradis/plugins/csv/upload
@@ -0,0 +1,39 @@
1
+ body.upload.new {
2
+ .dataTables_footer_content {
3
+ display: none;
4
+ }
5
+
6
+ .form-actions {
7
+ margin-top: 0;
8
+ }
9
+
10
+ .table {
11
+ margin-bottom: 0 !important;
12
+
13
+ tr {
14
+ &.issue-type:hover {
15
+ .identifier {
16
+ opacity: 1;
17
+ pointer-events: all;
18
+ }
19
+ }
20
+
21
+ td {
22
+ vertical-align: middle;
23
+
24
+ .identifier {
25
+ opacity: 0;
26
+ pointer-events: none;
27
+
28
+ &:checked {
29
+ opacity: 1;
30
+ }
31
+ }
32
+ }
33
+ }
34
+ }
35
+
36
+ .table-wrapper {
37
+ max-height: 100%;
38
+ }
39
+ }
@@ -0,0 +1,66 @@
1
+ module Dradis::Plugins::CSV
2
+ class UploadController < ::AuthenticatedController
3
+ include ProjectScoped
4
+
5
+ before_action :load_attachment, only: [:new, :create]
6
+ before_action :load_rtp_fields, only: [:new]
7
+ before_action :load_csv_headers, only: [:new]
8
+
9
+ def new
10
+ @default_columns = ['Column Header', 'Entity', 'Dradis Field']
11
+
12
+ @log_uid = Log.new.uid
13
+ end
14
+
15
+ def create
16
+ job_logger.write 'Enqueueing job to start in the background.'
17
+
18
+ MappingImportJob.perform_later(
19
+ default_user_id: current_user.id,
20
+ file: @attachment.fullpath.to_s,
21
+ mappings: mappings_params[:field_attributes].to_h,
22
+ project_id: current_project.id,
23
+ uid: params[:log_uid].to_i
24
+ )
25
+ end
26
+
27
+ private
28
+
29
+ def job_logger
30
+ @job_logger ||= Log.new(uid: params[:log_uid].to_i)
31
+ end
32
+
33
+ def load_rtp_fields
34
+ rtp = current_project.report_template_properties
35
+ @rtp_fields =
36
+ unless rtp.nil?
37
+ {
38
+ evidence: rtp.evidence_fields.map(&:name),
39
+ issue: rtp.issue_fields.map(&:name)
40
+ }
41
+ end
42
+ end
43
+
44
+ def load_csv_headers
45
+ begin
46
+ unless File.extname(@attachment.fullpath) == '.csv'
47
+ raise Dradis::Plugins::CSV::FileExtensionError
48
+ end
49
+
50
+ @headers = ::CSV.open(@attachment.fullpath, &:readline)
51
+ rescue CSV::MalformedCSVError => e
52
+ return redirect_to main_app.project_upload_manager_path, alert: "The uploaded file is not a valid CSV file: #{e.message}"
53
+ rescue Dradis::Plugins::CSV::FileExtensionError
54
+ return redirect_to main_app.project_upload_manager_path, alert: "The uploaded file is not a CSV file."
55
+ end
56
+ end
57
+
58
+ def load_attachment
59
+ @attachment = Attachment.find(params[:attachment], conditions: { node_id: current_project.plugin_uploads_node.id })
60
+ end
61
+
62
+ def mappings_params
63
+ params.require(:mappings).permit(field_attributes: [:field, :type])
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,32 @@
1
+ module Dradis::Plugins::CSV
2
+ class MappingImportJob < ApplicationJob
3
+ queue_as :dradis_project
4
+
5
+ # mappings hash:
6
+ # The key is the column index, while the value is a hash containing the type of resource (evidence/identifier/issue/node).
7
+ # It's used to map a CSV header to a field in Dradis (only for evidence and issues).
8
+ #
9
+ # e.g.
10
+ # {
11
+ # '0' => { 'type' => 'node' },
12
+ # '1' => { 'type' => 'issue', 'field' => 'Title' },
13
+ # '2' => { 'type' => 'identifier' },
14
+ # '3' => { 'type' => 'evidence', 'field' => 'Port' }
15
+ # }
16
+ def perform(default_user_id:, file:, mappings:, project_id:, uid:)
17
+ logger = Log.new(uid: uid)
18
+ logger.write { "Job id is #{job_id}." }
19
+
20
+ importer = Importer.new(
21
+ default_user_id: default_user_id,
22
+ logger: logger,
23
+ plugin: self.class.module_parent,
24
+ project_id: project_id
25
+ )
26
+
27
+ importer.import_csv(file: file, mappings: mappings)
28
+
29
+ logger.write { 'Worker process completed.' }
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,4 @@
1
+ $('#result').show();
2
+ ConsoleUpdater.jobId = <%= @job_logger.uid %>;
3
+ ConsoleUpdater.parsing = true;
4
+ setTimeout(ConsoleUpdater.updateConsole, 1000);
@@ -0,0 +1,81 @@
1
+ <% content_for :title, 'CSV Upload Mapping' %>
2
+
3
+ <% content_for :breadcrumbs do %>
4
+ <nav>
5
+ <ol class="breadcrumb">
6
+ <li class="breadcrumb-item">
7
+ <%= link_to 'Upload Manager', main_app.project_upload_manager_path(current_project) %>
8
+ </li>
9
+ <li class="breadcrumb-item active">CSV Upload Mapping</li>
10
+ </ol>
11
+ </nav>
12
+ <% end %>
13
+
14
+ <div class="content-container">
15
+ <div data-behavior="validation-messages">
16
+ <div class="alert alert-danger d-none" data-behavior="issue-id-validation-message">
17
+ <p>An Issue ID must be selected.</p>
18
+ </div>
19
+
20
+ <div class="alert alert-danger d-none" data-behavior="node-type-validation-message">
21
+ <p>A Node Label must be selected to import evidence records.</p>
22
+ </div>
23
+ </div>
24
+
25
+ <%= form_with url: project_upload_index_path(current_project, format: :js), method: :post, data: { behavior: 'mapping-form' } do |f| %>
26
+ <%= hidden_field_tag 'log_uid', @log_uid %>
27
+ <%= hidden_field_tag 'job_id', params[:job_id] %>
28
+ <%= hidden_field_tag 'attachment', params[:attachment] %>
29
+
30
+ <table class="table table-striped mb-0">
31
+ <thead>
32
+ <tr>
33
+ <th>Column Header</th>
34
+ <th class="no-sort">Entity</th>
35
+ <th>Dradis Field</th>
36
+ </tr>
37
+ </thead>
38
+ <tbody>
39
+ <% @headers.each_with_index do |header, index| %>
40
+ <tr class="issue-type">
41
+ <td><%= header %></td>
42
+ <td>
43
+ <div class="form-group m-0">
44
+ <%= f.select "mappings[field_attributes][#{index}][type]", [['Issue Field', 'issue'], ['Issue ID', 'identifier'], ['Evidence Field', 'evidence'], ['Node', 'node'], ['&#9472;'.html_safe, 'divider'], ['Do Not Import','skip']], { disabled: 'divider' }, class: 'form-control custom-select w-75', data: { behavior: 'type-select' } %>
45
+ </div>
46
+ </td>
47
+ <td>
48
+ <% if @rtp_fields %>
49
+ <div class="form-group m-0">
50
+ <% issue_options = @rtp_fields[:issue].any? ? options_for_select(@rtp_fields[:issue]) : options_for_select([[header, header]], disabled: header, selected: header) %>
51
+ <%= f.select "mappings[field_attributes][#{index}][field]", issue_options, {}, class: 'form-control custom-select w-75 field-select', data: { behavior: 'issue-field-select', header: header } %>
52
+
53
+ <% evidence_options = @rtp_fields[:evidence].any? ? options_for_select(@rtp_fields[:evidence]) : options_for_select([[header, header]], disabled: header, selected: header) %>
54
+ <%= f.select "mappings[field_attributes][#{index}][field]", evidence_options, {}, disabled: true, class: 'form-control custom-select w-75 field-select d-none', data: { behavior: 'evidence-field-select', header: header } %>
55
+
56
+ <%= f.select "mappings[field_attributes][#{index}][field]", [['N/A', '']], {}, disabled: true, class: 'form-control custom-select w-75 field-select d-none', data: { behavior: 'empty-field-select', header: header } %>
57
+ </div>
58
+ <% else %>
59
+ <span data-behavior="field-label" data-header="<%= header.delete(" \t\r\n") %>" ><%= header.delete(" \t\r\n") %></span>
60
+ <% end %>
61
+ </td>
62
+ </tr>
63
+ <% end %>
64
+ </tbody>
65
+ </table>
66
+ <div class="form-actions">
67
+ <%= f.submit 'Import CSV', class: 'btn btn-primary mr-1', data: { disable_with: false } %> or
68
+ <%= link_to 'Cancel', main_app.project_upload_manager_path(current_project) %>
69
+ </div>
70
+ <% end %>
71
+ </div>
72
+
73
+ <div class="col-12 p-0 order-3 order-xxl-4">
74
+ <div class="content-container mt-0">
75
+ <h4 class="header-underline">Output console</h4>
76
+ <div id="status"></div>
77
+ <%= content_tag :div, id: 'result', style: 'display:none', data: { url: main_app.status_console_index_path } do %>
78
+ <div id="console" class="mx-0 mb-0"></div>
79
+ <% end %>
80
+ </div>
81
+ </div>
@@ -0,0 +1,3 @@
1
+ ActiveSupport::Inflector.inflections(:en) do |inflect|
2
+ inflect.acronym 'CSV'
3
+ end
data/config/routes.rb CHANGED
@@ -1,3 +1,5 @@
1
1
  Dradis::Plugins::CSV::Engine.routes.draw do
2
- root to: 'base#index'
2
+ resources :projects, only: [] do
3
+ resources :upload, only: [:new, :create], path: '/addons/csv/upload'
4
+ end
3
5
  end
data/dradis-csv.gemspec CHANGED
@@ -2,33 +2,24 @@ $:.push File.expand_path('../lib', __FILE__)
2
2
  require 'dradis/plugins/csv/version'
3
3
  version = Dradis::Plugins::CSV::VERSION::STRING
4
4
 
5
-
6
5
  # Describe your gem and declare its dependencies:
7
6
  Gem::Specification.new do |spec|
8
- spec.platform = Gem::Platform::RUBY
9
- spec.name = 'dradis-csv'
10
- spec.version = version
11
- spec.summary = 'CSV export plugin for the Dradis Framework.'
12
- spec.description = 'This plugin allows you to export your Dradis results in CSV format.'
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.'
13
12
 
14
- spec.license = 'GPL-2'
13
+ spec.license = 'GPL-2'
15
14
 
16
- spec.authors = ['Daniel Martin']
17
- spec.email = ['etd@nomejortu.com']
18
- spec.homepage = 'http://dradisframework.org'
15
+ spec.authors = ['Daniel Martin']
16
+ spec.email = ['etd@nomejortu.com']
17
+ spec.homepage = 'http://dradisframework.org'
19
18
 
20
- spec.files = `git ls-files`.split($\)
19
+ spec.files = `git ls-files`.split($\)
21
20
  spec.executables = spec.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
22
- spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
21
+ spec.test_files = spec.files.grep(%r{^(spec|features)/})
23
22
 
24
- # By not including Rails as a dependency, we can use the gem with different
25
- # versions of Rails (a sure recipe for disaster, I'm sure), which is needed
26
- # until we bump Dradis Pro to 4.1.
27
- # s.add_dependency 'rails', '~> 4.1.1'
28
23
  spec.add_dependency 'dradis-plugins', '~> 4.0'
29
-
30
- spec.add_development_dependency 'bundler', '~> 1.6'
31
- spec.add_development_dependency 'rake', '~> 10.0'
32
-
33
- spec.add_development_dependency 'rspec-rails'#, '~> 3.0.0'
24
+ spec.add_development_dependency 'bundler'
34
25
  end
@@ -2,22 +2,14 @@ module Dradis::Plugins::CSV
2
2
  class Engine < ::Rails::Engine
3
3
  isolate_namespace Dradis::Plugins::CSV
4
4
 
5
- include Dradis::Plugins::Base
6
- provides :export
7
- description 'Export results in CSV format'
5
+ include ::Dradis::Plugins::Base
6
+ description 'Processes CSV output'
7
+ provides :addon, :upload
8
8
 
9
-
10
- initializer "dradis-csv.inflections" do |app|
11
- ActiveSupport::Inflector.inflections do |inflect|
12
- inflect.acronym('CSV')
13
- end
14
- end
15
-
16
- initializer 'dradis-csv.mount_engine' do
9
+ initializer 'csv.mount_engine' do
17
10
  Rails.application.routes.append do
18
- mount Dradis::Plugins::CSV::Engine => '/export/csv'
11
+ mount Dradis::Plugins::CSV::Engine => '/', as: :csv
19
12
  end
20
13
  end
21
-
22
14
  end
23
15
  end
@@ -1,16 +1,16 @@
1
1
  module Dradis
2
2
  module Plugins
3
3
  module CSV
4
- # Returns the version of the currently loaded CSV as a <tt>Gem::Version</tt>
4
+ # Returns the version of the currently loaded Dradis as a <tt>Gem::Version</tt>
5
5
  def self.gem_version
6
6
  Gem::Version.new VERSION::STRING
7
7
  end
8
8
 
9
9
  module VERSION
10
10
  MAJOR = 4
11
- MINOR = 4
12
- TINY = 0
13
- PRE = nil
11
+ MINOR = 5
12
+ TINY = 0
13
+ PRE = nil
14
14
 
15
15
  STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
16
16
  end
@@ -0,0 +1,76 @@
1
+ module Dradis::Plugins::CSV
2
+ class Importer < Dradis::Plugins::Upload::Importer
3
+ def self.templates
4
+ {}
5
+ end
6
+
7
+ def import(params={})
8
+ logger.info { 'Uploading CSV file...' }
9
+
10
+ logger.info { 'Done' }
11
+ end
12
+
13
+ def import_csv(params)
14
+ logger.info { 'Worker process starting background task.' }
15
+
16
+ mappings_groups = params[:mappings].group_by { |index, mapping| mapping['type'] }
17
+
18
+ filename = File.basename(params[:file])
19
+ id_index = Integer(mappings_groups['identifier']&.first&.first, exception: false)
20
+ @evidence_mappings = mappings_groups['evidence'] || []
21
+ @issue_lookup = {}
22
+ @issue_mappings = mappings_groups['issue'] || []
23
+ @node_index = Integer(mappings_groups['node']&.first&.first, exception: false)
24
+
25
+
26
+ CSV.foreach(params[:file], headers: true).with_index do |row, index|
27
+ csv_id = row[id_index] || "#{filename}-#{index}"
28
+ process_issue(csv_id: csv_id, row: row)
29
+ process_node(csv_id: csv_id, row: row)
30
+ end
31
+
32
+ true
33
+ end
34
+
35
+ private
36
+
37
+ attr_accessor :evidence_mappings, :issue_lookup, :issue_mappings, :node_index
38
+
39
+ def build_text(mappings:, row:)
40
+ mappings.map do |index, mapping|
41
+ next if project.report_template_properties && mapping['field'].blank?
42
+
43
+ field_name = project.report_template_properties ? mapping['field'] : row.headers[index.to_i].delete(" \t\r\n")
44
+ field_value = row[index.to_i]
45
+ "#[#{field_name}]#\n#{field_value}"
46
+ end.compact.join("\n\n")
47
+ end
48
+
49
+ def process_evidence(csv_id:, node:, row:)
50
+ logger.info{ "\t\t => Creating evidence: (node: #{node.label}, plugin_id: #{csv_id})" }
51
+
52
+ issue = issue_lookup[csv_id]
53
+ evidence_content = build_text(mappings: @evidence_mappings, row: row)
54
+ content_service.create_evidence(issue: issue, node: node, content: evidence_content)
55
+ end
56
+
57
+ def process_issue(csv_id:, row:)
58
+ logger.info { "\t => Creating new issue (plugin_id: #{csv_id})" }
59
+ issue_text = build_text(mappings: issue_mappings, row: row)
60
+ issue = content_service.create_issue(text: issue_text, id: csv_id)
61
+
62
+ issue_lookup[csv_id] = issue
63
+ end
64
+
65
+ def process_node(csv_id:, row:)
66
+ node_label = row[node_index]
67
+
68
+ if node_label.present?
69
+ logger.info { "\t\t => Processing node: #{node_label}" }
70
+ node = content_service.create_node(label: node_label, type: :host)
71
+
72
+ process_evidence(csv_id: csv_id, node: node, row: row)
73
+ end
74
+ end
75
+ end
76
+ end
@@ -1,10 +1,11 @@
1
1
  module Dradis
2
2
  module Plugins
3
- module CVS
3
+ module CSV
4
+ class FileExtensionError < StandardError; end
4
5
  end
5
6
  end
6
7
  end
7
8
 
8
9
  require 'dradis/plugins/csv/engine'
9
- require 'dradis/plugins/csv/exporter'
10
+ require 'dradis/plugins/csv/importer'
10
11
  require 'dradis/plugins/csv/version'