dradis-csv 4.4.0 → 4.5.0

Sign up to get free protection for your applications and to get access to all the features.
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'