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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +2 -66
- data/CONTRIBUTING.md +1 -1
- data/Gemfile +6 -0
- data/README.md +3 -18
- data/app/assets/javascripts/dradis/plugins/csv/manifests/tylium.js +1 -0
- data/app/assets/javascripts/dradis/plugins/csv/upload.js +114 -0
- data/app/assets/stylesheets/dradis/plugins/csv/manifests/tylium.scss +1 -0
- data/app/assets/stylesheets/dradis/plugins/csv/upload.scss +39 -0
- data/app/controllers/dradis/plugins/csv/upload_controller.rb +66 -0
- data/app/jobs/dradis/plugins/csv/mapping_import_job.rb +32 -0
- data/app/views/dradis/plugins/csv/upload/create.js.erb +4 -0
- data/app/views/dradis/plugins/csv/upload/new.html.erb +81 -0
- data/config/initializers/inflections.rb +3 -0
- data/config/routes.rb +3 -1
- data/dradis-csv.gemspec +12 -21
- data/lib/dradis/plugins/csv/engine.rb +5 -13
- data/lib/dradis/plugins/csv/gem_version.rb +4 -4
- data/lib/dradis/plugins/csv/importer.rb +76 -0
- data/lib/dradis/plugins/csv.rb +3 -2
- data/lib/dradis-csv.rb +1 -3
- data/spec/features/upload_spec.rb +267 -0
- data/spec/fixtures/files/simple.csv +2 -0
- data/spec/fixtures/files/simple_malformed.csv +2 -0
- data/spec/jobs/dradis/plugins/csv/mapping_import_job_spec.rb +30 -0
- data/spec/lib/dradis/plugins/csv/importer_spec.rb +140 -0
- metadata +25 -46
- data/.github/issue_template.md +0 -16
- data/.github/pull_request_template.md +0 -36
- data/.gitignore +0 -8
- data/.rspec +0 -2
- data/app/controllers/dradis/plugins/csv/base_controller.rb +0 -19
- data/app/views/dradis/plugins/csv/export/_index-content.html.erb +0 -10
- data/app/views/dradis/plugins/csv/export/_index-tabs.html.erb +0 -3
- data/lib/dradis/plugins/csv/exporter.rb +0 -60
- data/lib/tasks/thorfile.rb +0 -28
- data/spec/csv_export_spec.rb +0 -5
- data/spec/spec_helper.rb +0 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7d5090a66f9402d6d81382a775932db5a89514940f7d3061a1aa5c595341ba73
|
4
|
+
data.tar.gz: 82ac1596529a3975c76932630ef887d9e0f6de522a1e901f1e2e253e810f3635
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e7881df65ac54e633ee282316a670819b6a73a0ab33102e5233701c098f5129058d7a5321526cf9179a94ae13a154b667eabe70e92e5cc4faadf36a31e3d5504
|
7
|
+
data.tar.gz: fa96b2b762366addbe26acf842bd38aed09a2c2b91f46ec70deaf773084237f5f7c42cd4cd35c1f4a6836e4ccff029cbabff56b594a40284092783f6b46e8929
|
data/CHANGELOG.md
CHANGED
@@ -1,66 +1,2 @@
|
|
1
|
-
v4.
|
2
|
-
-
|
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
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -1,23 +1,8 @@
|
|
1
|
-
# CSV
|
1
|
+
# Customizable CSV Importer for the Dradis Framework
|
2
2
|
|
3
|
-
|
3
|
+
Upload CSV files into Dradis.
|
4
4
|
|
5
|
-
|
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,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'], ['─'.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>
|
data/config/routes.rb
CHANGED
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
|
9
|
-
spec.name
|
10
|
-
spec.version
|
11
|
-
spec.summary
|
12
|
-
spec.description = 'This
|
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
|
13
|
+
spec.license = 'GPL-2'
|
15
14
|
|
16
|
-
spec.authors
|
17
|
-
spec.email
|
18
|
-
spec.homepage
|
15
|
+
spec.authors = ['Daniel Martin']
|
16
|
+
spec.email = ['etd@nomejortu.com']
|
17
|
+
spec.homepage = 'http://dradisframework.org'
|
19
18
|
|
20
|
-
spec.files
|
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
|
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
|
-
|
7
|
-
|
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 => '/
|
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
|
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 =
|
12
|
-
TINY
|
13
|
-
PRE
|
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
|
data/lib/dradis/plugins/csv.rb
CHANGED
@@ -1,10 +1,11 @@
|
|
1
1
|
module Dradis
|
2
2
|
module Plugins
|
3
|
-
module
|
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/
|
10
|
+
require 'dradis/plugins/csv/importer'
|
10
11
|
require 'dradis/plugins/csv/version'
|