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.
- 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'
|