dradis-plugins 4.11.0 → 4.12.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 +6 -0
- data/README.md +1 -2
- data/lib/dradis/plugins/base.rb +3 -1
- data/lib/dradis/plugins/content_service/core.rb +9 -1
- data/lib/dradis/plugins/gem_version.rb +1 -1
- data/lib/dradis/plugins/mapping_service.rb +77 -0
- data/lib/dradis/plugins/mappings/base.rb +82 -0
- data/lib/dradis/plugins/mappings.rb +8 -0
- data/lib/dradis/plugins/templates/migrate_templates.rb +143 -0
- data/lib/dradis/plugins/templates/samples.rb +45 -0
- data/lib/dradis/plugins/upload/importer.rb +9 -7
- data/lib/dradis/plugins.rb +13 -2
- data/spec/lib/dradis/plugins/mapping_service_spec.rb +54 -0
- metadata +10 -7
- data/lib/dradis/plugins/template_service.rb +0 -102
- data/lib/dradis/plugins/templates.rb +0 -57
- data/spec/lib/dradis/plugins/template_service_spec.rb +0 -28
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 303a79ec7a8bf67064fe8a6dbb7e7c86cb9bc9ff4de1df69f7ab94ba9f55f805
|
4
|
+
data.tar.gz: 4fd4d6780ba9737bd45551f8c1b40e9f9654989f8925fae1bc38261e2288f95c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f5070d0ac07b7b045d140013aa5a26448bd6be87cbab58a8b7b19c298da1f6162bd867c66e2d2a0497ba8bcea32417ad4b22449863c322a760fdd3e32596bf9b
|
7
|
+
data.tar.gz: 7f73a9201af4be0334536254024b70e7dc2f317f8bdd0d1e2a8fae785c8abf815060cd71be3e9799b65dc15ad9252da0a0d1c1fb9c23b7920be0c34ce431d62d
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,9 @@
|
|
1
|
+
v4.12.0 (May 2024)
|
2
|
+
- Update Dradis links in README
|
3
|
+
- Fix the TypeError around the plugins template caching
|
4
|
+
- Remove template_service and add mapping_service to apply mappings on tool upload
|
5
|
+
- Include mapping module when integrations provide 'upload'
|
6
|
+
|
1
7
|
v4.11.0 (January 2024)
|
2
8
|
- No changes
|
3
9
|
|
data/README.md
CHANGED
@@ -8,8 +8,7 @@ The Dradis 3 gemified plugin Engines need to include Dradis::Plugins::Base which
|
|
8
8
|
|
9
9
|
Warning, we may end up merging this gem with Dradis::Core!!
|
10
10
|
|
11
|
-
The add-on requires [Dradis CE](https://
|
12
|
-
|
11
|
+
The add-on requires [Dradis CE](https://dradis.com/ce/) > 3.0, or [Dradis Pro](https://dradis.com/).
|
13
12
|
|
14
13
|
## More information
|
15
14
|
|
data/lib/dradis/plugins/base.rb
CHANGED
@@ -12,7 +12,8 @@ module Dradis
|
|
12
12
|
|
13
13
|
# Extend the engine with other functionality
|
14
14
|
include Dradis::Plugins::Configurable
|
15
|
-
include Dradis::Plugins::Templates
|
15
|
+
include Dradis::Plugins::Templates::MigrateTemplates
|
16
|
+
include Dradis::Plugins::Templates::Samples
|
16
17
|
include Dradis::Plugins::Thor
|
17
18
|
end
|
18
19
|
|
@@ -33,6 +34,7 @@ module Dradis
|
|
33
34
|
@features = list
|
34
35
|
if list.include?(:upload)
|
35
36
|
include Dradis::Plugins::Upload::Base
|
37
|
+
include Dradis::Plugins::Mappings::Base
|
36
38
|
end
|
37
39
|
end
|
38
40
|
|
@@ -15,7 +15,7 @@ module Dradis::Plugins::ContentService
|
|
15
15
|
@logger = args.fetch(:logger, Rails.logger)
|
16
16
|
@plugin = args.fetch(:plugin)
|
17
17
|
@project = args[:project]
|
18
|
-
@scope = args
|
18
|
+
@scope = validate_scope(args[:scope]).to_sym
|
19
19
|
@state = args[:state]
|
20
20
|
end
|
21
21
|
|
@@ -51,5 +51,13 @@ module Dradis::Plugins::ContentService
|
|
51
51
|
end
|
52
52
|
end
|
53
53
|
|
54
|
+
def validate_scope(scope)
|
55
|
+
valid_scopes = Dradis::Plugins::ContentService::Base::VALID_SCOPES
|
56
|
+
if scope && valid_scopes.include?(scope.to_s)
|
57
|
+
scope
|
58
|
+
else
|
59
|
+
:published
|
60
|
+
end
|
61
|
+
end
|
54
62
|
end
|
55
63
|
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
module Dradis
|
2
|
+
module Plugins
|
3
|
+
class MappingService
|
4
|
+
attr_accessor :component, :destination, :integration, :source
|
5
|
+
|
6
|
+
def initialize(destination: nil, integration:)
|
7
|
+
@destination = destination
|
8
|
+
@integration = integration
|
9
|
+
@component = @integration.meta[:name].to_s
|
10
|
+
@sample_dir = default_sample_dir
|
11
|
+
end
|
12
|
+
|
13
|
+
def apply_mapping(data:, source:, mapping_fields: nil)
|
14
|
+
@source = source
|
15
|
+
return unless valid_source?
|
16
|
+
|
17
|
+
field_processor = integration::FieldProcessor.new(data: data)
|
18
|
+
mapping_fields = mapping_fields || get_mapping_fields
|
19
|
+
|
20
|
+
mapping_fields.map do |field|
|
21
|
+
field_name = field.destination_field
|
22
|
+
field_content = process_content(
|
23
|
+
field.content,
|
24
|
+
field_processor
|
25
|
+
)
|
26
|
+
|
27
|
+
"#[#{field_name}]#\n#{field_content}"
|
28
|
+
end&.join("\n\n")
|
29
|
+
end
|
30
|
+
|
31
|
+
# This returns a sample of valid entry for the Mappings Manager
|
32
|
+
def sample
|
33
|
+
@sample ||= {}
|
34
|
+
if valid_source?
|
35
|
+
@sample[source] ||= begin
|
36
|
+
sample_file = File.join(@sample_dir, "#{source}.sample")
|
37
|
+
File.read(sample_file)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
# This method returns the default location in which integrations store their sample files
|
45
|
+
def default_sample_dir
|
46
|
+
@default_sample_dir ||= begin
|
47
|
+
File.join(Configuration.paths_templates_plugins, component)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def get_mapping_fields
|
52
|
+
# returns the mapping fields for the found mapping,
|
53
|
+
# or the default mapping_fields
|
54
|
+
integration.mapping_fields(
|
55
|
+
source: source,
|
56
|
+
destination: destination
|
57
|
+
)
|
58
|
+
end
|
59
|
+
|
60
|
+
def process_content(content, field_processor)
|
61
|
+
content.gsub(/{{\s?#{component}\[(\S*?)\]\s?}}/) do |field|
|
62
|
+
name = field.split(/\[|\]/)[1]
|
63
|
+
|
64
|
+
if integration.source_fields(source).include?(name)
|
65
|
+
field_processor.value(field: name)
|
66
|
+
else
|
67
|
+
"Field [#{field}] not recognized by the integration"
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def valid_source?
|
73
|
+
@source = source if integration.mapping_sources.include?(source.to_sym)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
|
2
|
+
# When you call provides :upload in your Engine, this module gets included.
|
3
|
+
module Dradis::Plugins::Mappings::Base
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
included do
|
7
|
+
module_parent.extend ClassMethods
|
8
|
+
end
|
9
|
+
|
10
|
+
module ClassMethods
|
11
|
+
|
12
|
+
def default_mapping_fields(source)
|
13
|
+
default_mapping(source).map do |destination_field, content|
|
14
|
+
MappingField.new(destination_field: destination_field, content: content)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def component
|
19
|
+
meta[:name].to_s
|
20
|
+
end
|
21
|
+
|
22
|
+
def field_names(source:, destination: nil, field_type: 'destination')
|
23
|
+
mappings = mappings(source: source, destination: destination)
|
24
|
+
|
25
|
+
mapping_fields = if mappings.any?
|
26
|
+
mappings.map(&:mapping_fields).flatten
|
27
|
+
end
|
28
|
+
|
29
|
+
if mapping_fields && mapping_fields.any?
|
30
|
+
mapping_fields.pluck("#{field_type}_field").uniq
|
31
|
+
else
|
32
|
+
default_mapping(source).keys
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def default_mapping(source)
|
37
|
+
if mapping_sources.include?(source.to_sym)
|
38
|
+
self::Mapping::DEFAULT_MAPPING[source.to_sym]
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
# given the params returns all matching mappings
|
43
|
+
# will accept source and/or destination or no args
|
44
|
+
def mappings(source: nil, destination: nil)
|
45
|
+
mappings = Mapping.includes(:mapping_fields).where(
|
46
|
+
component: component
|
47
|
+
)
|
48
|
+
mappings = mappings.where(source: source) if source
|
49
|
+
mappings = mappings.where(destination: destination) if destination
|
50
|
+
mappings
|
51
|
+
end
|
52
|
+
|
53
|
+
# returns single matching mapping given source & destination or default
|
54
|
+
def get_mapping(source:, destination:)
|
55
|
+
mapping = Mapping.includes(:mapping_fields).find_by(
|
56
|
+
component: component,
|
57
|
+
source: source,
|
58
|
+
destination: destination
|
59
|
+
)
|
60
|
+
end
|
61
|
+
|
62
|
+
def mapping_fields(source:, destination:)
|
63
|
+
mapping = get_mapping(source: source, destination: destination)
|
64
|
+
|
65
|
+
if mapping
|
66
|
+
mapping.mapping_fields
|
67
|
+
else
|
68
|
+
default_mapping_fields(source)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def mapping_sources
|
73
|
+
self::Mapping::SOURCE_FIELDS.keys
|
74
|
+
end
|
75
|
+
|
76
|
+
def source_fields(source)
|
77
|
+
if mapping_sources.include?(source.to_sym)
|
78
|
+
self::Mapping::SOURCE_FIELDS[source.to_sym]
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,143 @@
|
|
1
|
+
module Dradis
|
2
|
+
module Plugins
|
3
|
+
module Templates
|
4
|
+
module MigrateTemplates
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
# Apr 2024 migration to move from .template files
|
8
|
+
# to db-backed mappings. Can be removed in Apr 2026
|
9
|
+
|
10
|
+
LEGACY_FIELDS_REGEX = /%(\S+?)%/.freeze
|
11
|
+
LEGACY_MAPPING_REFERENCE = {
|
12
|
+
'burp' => {
|
13
|
+
'html_evidence' => 'html_evidence',
|
14
|
+
'html_issue' => 'issue',
|
15
|
+
'xml_evidence' => 'evidence',
|
16
|
+
'xml_issue' => 'issue'
|
17
|
+
},
|
18
|
+
'qualys' => {
|
19
|
+
'asset_evidence' => 'asset-evidence',
|
20
|
+
'asset_issue' => 'asset-issue',
|
21
|
+
'vuln_evidence' => 'evidence',
|
22
|
+
'vuln_element' => 'element',
|
23
|
+
'was_evidence' => 'was-evidence',
|
24
|
+
'was_issue' => 'was-issue'
|
25
|
+
}
|
26
|
+
}.freeze
|
27
|
+
|
28
|
+
module ClassMethods
|
29
|
+
def migrate_templates_to_mappings(args = {})
|
30
|
+
# return if the integration doesn't provide any templates ex. projects, cve
|
31
|
+
return unless paths['dradis/templates'].existent.any?
|
32
|
+
@integration_name = plugin_name.to_s
|
33
|
+
# return if templates have already been migrated (mappings exist for the integration)
|
34
|
+
return if ::Mapping.where(component: @integration_name).any?
|
35
|
+
|
36
|
+
templates_dir = args.fetch(:from)
|
37
|
+
integration_templates_dir = File.join(templates_dir, @integration_name)
|
38
|
+
|
39
|
+
if uploaders.count > 1
|
40
|
+
migrate_multiple_uploaders(@integration_name, integration_templates_dir)
|
41
|
+
else
|
42
|
+
template_files = Dir["#{integration_templates_dir}/*.template"]
|
43
|
+
return unless template_files.any?
|
44
|
+
|
45
|
+
template_files.each do |template_file|
|
46
|
+
next unless File.exist?(template_file)
|
47
|
+
source = File.basename(template_file, '.template')
|
48
|
+
# create a mapping & mapping_fields for each field in the file
|
49
|
+
migrate(template_file, source)
|
50
|
+
rename_file(template_file)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
private
|
56
|
+
|
57
|
+
def create_mapping(mapping_source, destination = nil)
|
58
|
+
::Mapping.find_or_create_by!(
|
59
|
+
component: @integration_name,
|
60
|
+
source: mapping_source,
|
61
|
+
destination: destination
|
62
|
+
)
|
63
|
+
end
|
64
|
+
|
65
|
+
def create_mapping_fields(mapping, template_file)
|
66
|
+
template_fields = parse_template_fields(template_file)
|
67
|
+
return unless template_fields
|
68
|
+
|
69
|
+
# create a mapping_field for each field in the .template file
|
70
|
+
template_fields.each do |field_title, field_content|
|
71
|
+
# set source_field by taking the first match to the existing %% syntax
|
72
|
+
source_field = field_content.match(LEGACY_FIELDS_REGEX)
|
73
|
+
source_field = source_field ? source_field[1] : 'Custom Text'
|
74
|
+
|
75
|
+
updated_content = update_syntax(field_content)
|
76
|
+
|
77
|
+
mapping.mapping_fields.find_or_create_by!(
|
78
|
+
source_field: source_field,
|
79
|
+
destination_field: field_title,
|
80
|
+
content: updated_content
|
81
|
+
)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def migrate(template_file, source)
|
86
|
+
destinations = if defined?(Dradis::Pro)
|
87
|
+
ReportTemplateProperties.with_fields_defined.map(&:as_mapping_destination)
|
88
|
+
else
|
89
|
+
[nil]
|
90
|
+
end
|
91
|
+
|
92
|
+
destinations.each do |destination|
|
93
|
+
ActiveRecord::Base.transaction do
|
94
|
+
mapping = create_mapping(source, destination)
|
95
|
+
create_mapping_fields(mapping, template_file)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
# previously our integrations with multiple uploaders (Burp, Qualys) had inconsistent
|
101
|
+
# template names (some included the uploader, some didn't ex. burp issue vs html_evidence)
|
102
|
+
# they have been renamed to follow a consistent 'uploader_entity' structure, but
|
103
|
+
# in order to migrate the old templates to the db with the new names as the source
|
104
|
+
# we need to reference an object in the integration that maps the new name to the old one
|
105
|
+
def migrate_multiple_uploaders(integration, templates_dir)
|
106
|
+
return unless LEGACY_MAPPING_REFERENCE[integration]
|
107
|
+
|
108
|
+
LEGACY_MAPPING_REFERENCE[integration].each do |source_field, legacy_template_name|
|
109
|
+
template_file = Dir["#{templates_dir}/#{legacy_template_name}.template"]
|
110
|
+
if template_file.any? { |file| File.exist?(file) }
|
111
|
+
migrate(template_file[0], source_field)
|
112
|
+
# burp issue.template is used by both uploaders so don't rename it the first time around
|
113
|
+
rename_file(template_file[0]) unless source_field == 'html_issue'
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
def parse_template_fields(template_file)
|
119
|
+
if File.file?(template_file)
|
120
|
+
template_content = File.read(template_file)
|
121
|
+
FieldParser.source_to_fields(template_content)
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
def rename_file(template_file)
|
126
|
+
# Don't rename if it's already been renamed.
|
127
|
+
if !template_file.include?('.legacy') && File.file?(template_file)
|
128
|
+
File.rename template_file, "#{template_file}.legacy"
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
def update_syntax(field_content)
|
133
|
+
# turn the %% syntax into the new
|
134
|
+
# '{{ <integration>[was-issue.title] }}' format
|
135
|
+
field_content.gsub(LEGACY_FIELDS_REGEX) do |content|
|
136
|
+
"{{ #{@integration_name}[#{content[1..-2]}] }}"
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module Dradis
|
2
|
+
module Plugins
|
3
|
+
module Templates
|
4
|
+
module Samples
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
included do
|
8
|
+
# Keep track of any templates the plugin defines
|
9
|
+
paths['dradis/templates'] = 'templates'
|
10
|
+
end
|
11
|
+
|
12
|
+
module ClassMethods
|
13
|
+
def copy_samples(args = {})
|
14
|
+
destination = args.fetch(:to)
|
15
|
+
|
16
|
+
destination_dir = File.join(destination, plugin_name.to_s)
|
17
|
+
FileUtils.mkdir_p(destination_dir) if integration_samples.any?
|
18
|
+
|
19
|
+
integration_samples.each do |template|
|
20
|
+
destination_file = File.join(destination_dir, File.basename(template))
|
21
|
+
|
22
|
+
Rails.logger.info do
|
23
|
+
"Updating templates for #{plugin_name} plugin. "\
|
24
|
+
"Destination: #{destination}"
|
25
|
+
end
|
26
|
+
FileUtils.cp(template, destination_file)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def integration_samples(args = {})
|
33
|
+
@templates ||= begin
|
34
|
+
if paths['dradis/templates'].existent.any?
|
35
|
+
Dir["#{paths['dradis/templates'].existent.first}/*.sample"]
|
36
|
+
else
|
37
|
+
[]
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -9,11 +9,11 @@ module Dradis
|
|
9
9
|
:content_service,
|
10
10
|
:default_user_id,
|
11
11
|
:logger,
|
12
|
+
:mapping_service,
|
12
13
|
:options,
|
13
14
|
:plugin,
|
14
15
|
:project,
|
15
16
|
:state,
|
16
|
-
:template_service
|
17
17
|
)
|
18
18
|
|
19
19
|
def self.templates
|
@@ -29,8 +29,8 @@ module Dradis
|
|
29
29
|
@project = args.key?(:project_id) ? Project.find(args[:project_id]) : nil
|
30
30
|
@state = args.fetch(:state, :published)
|
31
31
|
|
32
|
-
@content_service
|
33
|
-
@
|
32
|
+
@content_service = args.fetch(:content_service, default_content_service)
|
33
|
+
@mapping_service = default_mapping_service
|
34
34
|
|
35
35
|
post_initialize(args)
|
36
36
|
end
|
@@ -71,11 +71,13 @@ module Dradis
|
|
71
71
|
end
|
72
72
|
end
|
73
73
|
|
74
|
+
def default_mapping_service
|
75
|
+
rtp = project.report_template_properties if project
|
76
|
+
destination = rtp ? rtp.as_mapping_destination : nil
|
74
77
|
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
plugin: plugin
|
78
|
+
Dradis::Plugins::MappingService.new(
|
79
|
+
destination: destination,
|
80
|
+
integration: plugin
|
79
81
|
)
|
80
82
|
end
|
81
83
|
end # Importer
|
data/lib/dradis/plugins.rb
CHANGED
@@ -57,6 +57,15 @@ module Dradis
|
|
57
57
|
@@extensions.include?(const)
|
58
58
|
end
|
59
59
|
|
60
|
+
def upload_integration_names_and_modules
|
61
|
+
with_feature(:upload).each_with_object({}) do |integration, integrations_hash|
|
62
|
+
integration_name = integration.plugin_name.to_s
|
63
|
+
integration_module = integration.module_parent
|
64
|
+
|
65
|
+
integrations_hash[integration_name] = integration_module
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
60
69
|
private
|
61
70
|
|
62
71
|
# Use this to ensure the Extension conforms with some expected interface
|
@@ -74,11 +83,12 @@ require 'dradis/plugins/engine'
|
|
74
83
|
require 'dradis/plugins/version'
|
75
84
|
|
76
85
|
require 'dradis/plugins/content_service/base'
|
77
|
-
require 'dradis/plugins/
|
86
|
+
require 'dradis/plugins/mapping_service'
|
78
87
|
|
79
88
|
require 'dradis/plugins/base'
|
80
89
|
require 'dradis/plugins/export'
|
81
90
|
require 'dradis/plugins/import'
|
91
|
+
require 'dradis/plugins/mappings'
|
82
92
|
require 'dradis/plugins/upload'
|
83
93
|
|
84
94
|
# Common functionality
|
@@ -86,6 +96,7 @@ require 'dradis/plugins/configurable'
|
|
86
96
|
require 'dradis/plugins/settings'
|
87
97
|
require 'dradis/plugins/settings/adapters/db'
|
88
98
|
require 'dradis/plugins/settings/adapters/encrypted_configuration'
|
89
|
-
require 'dradis/plugins/templates'
|
99
|
+
require 'dradis/plugins/templates/samples'
|
100
|
+
require 'dradis/plugins/templates/migrate_templates'
|
90
101
|
require 'dradis/plugins/thor'
|
91
102
|
require 'dradis/plugins/thor_helper'
|
@@ -0,0 +1,54 @@
|
|
1
|
+
require 'rails_helper'
|
2
|
+
|
3
|
+
# To run, execute from Dradis main app folder:
|
4
|
+
# bin/rspec [dradis-plugins path]/spec/lib/dradis/plugins/mapping_service_spec.rb
|
5
|
+
describe Dradis::Plugins::MappingService do
|
6
|
+
describe '#apply_mapping' do
|
7
|
+
let(:data) { double }
|
8
|
+
let(:liquid_mapping_field) {
|
9
|
+
create(:mapping_field, content: "{% if issue.evidence %}\n{% endif %} }}")
|
10
|
+
}
|
11
|
+
let(:integration) { Dradis::Plugins::Qualys }
|
12
|
+
let(:project) { create(:project, :with_report_template_properties) }
|
13
|
+
let(:qualys_mapping_field) {
|
14
|
+
create(:mapping_field, destination_field: 'Test Field', content: 'test content')
|
15
|
+
}
|
16
|
+
let(:mapping_service) do
|
17
|
+
Dradis::Plugins::MappingService.new(
|
18
|
+
integration: integration,
|
19
|
+
destination: "rtp_#{project.report_template_properties_id}"
|
20
|
+
)
|
21
|
+
end
|
22
|
+
let(:mapping_processed) do
|
23
|
+
mapping_service.apply_mapping(source: 'was_issue', data: data)
|
24
|
+
end
|
25
|
+
|
26
|
+
before do
|
27
|
+
allow(data).to receive(:name).and_return('VULNERABILITY')
|
28
|
+
end
|
29
|
+
|
30
|
+
context 'with default mappings' do
|
31
|
+
it 'applies default mappings when no mapping exists' do
|
32
|
+
expect(mapping_processed).to include("#[Title]#\n")
|
33
|
+
expect(mapping_processed).not_to include("#[Test Field]#\n")
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
context 'with custom mappings' do
|
38
|
+
it 'applies mappings when a mapping matching the uploader & source exists' do
|
39
|
+
allow(mapping_service).to receive(:get_mapping_fields).and_return([qualys_mapping_field])
|
40
|
+
|
41
|
+
expect(mapping_processed).to include("#[Test Field]#\ntest content")
|
42
|
+
expect(mapping_processed).not_to include("#[Title]#\n")
|
43
|
+
end
|
44
|
+
|
45
|
+
context 'with liquid content' do
|
46
|
+
it 'does not parse the liquid data as fields' do
|
47
|
+
allow(mapping_service).to receive(:get_mapping_fields).and_return([liquid_mapping_field])
|
48
|
+
|
49
|
+
expect(mapping_processed).to include("{% if issue.evidence %}\n{% endif %} }}")
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: dradis-plugins
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 4.
|
4
|
+
version: 4.12.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Daniel Martin
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-
|
11
|
+
date: 2024-05-07 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -93,11 +93,14 @@ files:
|
|
93
93
|
- lib/dradis/plugins/import/filters.rb
|
94
94
|
- lib/dradis/plugins/import/filters/base.rb
|
95
95
|
- lib/dradis/plugins/import/result.rb
|
96
|
+
- lib/dradis/plugins/mapping_service.rb
|
97
|
+
- lib/dradis/plugins/mappings.rb
|
98
|
+
- lib/dradis/plugins/mappings/base.rb
|
96
99
|
- lib/dradis/plugins/settings.rb
|
97
100
|
- lib/dradis/plugins/settings/adapters/db.rb
|
98
101
|
- lib/dradis/plugins/settings/adapters/encrypted_configuration.rb
|
99
|
-
- lib/dradis/plugins/
|
100
|
-
- lib/dradis/plugins/templates.rb
|
102
|
+
- lib/dradis/plugins/templates/migrate_templates.rb
|
103
|
+
- lib/dradis/plugins/templates/samples.rb
|
101
104
|
- lib/dradis/plugins/thor.rb
|
102
105
|
- lib/dradis/plugins/thor_helper.rb
|
103
106
|
- lib/dradis/plugins/upload.rb
|
@@ -110,8 +113,8 @@ files:
|
|
110
113
|
- spec/lib/dradis/plugins/content_service/boards_spec.rb
|
111
114
|
- spec/lib/dradis/plugins/content_service/content_blocks_spec.rb
|
112
115
|
- spec/lib/dradis/plugins/content_service/issues_spec.rb
|
116
|
+
- spec/lib/dradis/plugins/mapping_service_spec.rb
|
113
117
|
- spec/lib/dradis/plugins/settings/adapters/encrypted_configuration_spec.rb
|
114
|
-
- spec/lib/dradis/plugins/template_service_spec.rb
|
115
118
|
- spec/settings_spec.rb
|
116
119
|
- spec/spec_helper.rb
|
117
120
|
homepage: http://dradis.com/ce/
|
@@ -133,7 +136,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
133
136
|
- !ruby/object:Gem::Version
|
134
137
|
version: '0'
|
135
138
|
requirements: []
|
136
|
-
rubygems_version: 3.
|
139
|
+
rubygems_version: 3.1.4
|
137
140
|
signing_key:
|
138
141
|
specification_version: 4
|
139
142
|
summary: Plugin manager for the Dradis Framework project.
|
@@ -143,7 +146,7 @@ test_files:
|
|
143
146
|
- spec/lib/dradis/plugins/content_service/boards_spec.rb
|
144
147
|
- spec/lib/dradis/plugins/content_service/content_blocks_spec.rb
|
145
148
|
- spec/lib/dradis/plugins/content_service/issues_spec.rb
|
149
|
+
- spec/lib/dradis/plugins/mapping_service_spec.rb
|
146
150
|
- spec/lib/dradis/plugins/settings/adapters/encrypted_configuration_spec.rb
|
147
|
-
- spec/lib/dradis/plugins/template_service_spec.rb
|
148
151
|
- spec/settings_spec.rb
|
149
152
|
- spec/spec_helper.rb
|
@@ -1,102 +0,0 @@
|
|
1
|
-
module Dradis
|
2
|
-
module Plugins
|
3
|
-
class TemplateService
|
4
|
-
attr_accessor :logger, :template, :templates_dir
|
5
|
-
|
6
|
-
def initialize(args = {})
|
7
|
-
@plugin = args.fetch(:plugin)
|
8
|
-
@templates_dir = args[:templates_dir] || default_templates_dir
|
9
|
-
end
|
10
|
-
|
11
|
-
# For a given entry, return a text blob resulting from applying the
|
12
|
-
# chosen template to the supplied entry.
|
13
|
-
def process_template(args = {})
|
14
|
-
self.template = args[:template]
|
15
|
-
data = args[:data]
|
16
|
-
|
17
|
-
processor = @plugin::FieldProcessor.new(data: data)
|
18
|
-
|
19
|
-
template_source.gsub(/%(\S*?)%/) do |field|
|
20
|
-
name = field[1..-2]
|
21
|
-
if fields.include?(name)
|
22
|
-
processor.value(field: name)
|
23
|
-
else
|
24
|
-
"Field [#{field}] not recognized by the plugin"
|
25
|
-
end
|
26
|
-
end
|
27
|
-
end
|
28
|
-
|
29
|
-
# ---------------------------------------------- Plugin Manager interface
|
30
|
-
|
31
|
-
# This lists the fields defined by this plugin that can be used in the
|
32
|
-
# template
|
33
|
-
def fields
|
34
|
-
@fields ||= {}
|
35
|
-
@fields[template] ||= begin
|
36
|
-
fields_file = File.join(templates_dir, "#{template}.fields")
|
37
|
-
File.readlines(fields_file).map(&:chomp)
|
38
|
-
end
|
39
|
-
end
|
40
|
-
|
41
|
-
# This returns a sample of valid entry for the Plugin Manager
|
42
|
-
def sample
|
43
|
-
@sample ||= {}
|
44
|
-
@sample[template] ||= begin
|
45
|
-
sample_file = File.join(templates_dir, "#{template}.sample")
|
46
|
-
File.read(sample_file)
|
47
|
-
end
|
48
|
-
end
|
49
|
-
|
50
|
-
# Set the plugin's item template. This is used by the Plugins Manager
|
51
|
-
# to force the plugin to use the new_template (provided by the user)
|
52
|
-
def set_template(args = {})
|
53
|
-
template = args[:template]
|
54
|
-
content = args[:content]
|
55
|
-
|
56
|
-
@sources ||= {}
|
57
|
-
@sources[template] ||= {
|
58
|
-
content: nil,
|
59
|
-
mtime: DateTime.now
|
60
|
-
}
|
61
|
-
@sources[template][:content] = content
|
62
|
-
end
|
63
|
-
|
64
|
-
# This method returns the current template's source. It caches the
|
65
|
-
# template based on the file's last-modified time and refreshes the
|
66
|
-
# cached copy when it detects changes.
|
67
|
-
def template_source
|
68
|
-
@sources ||= {}
|
69
|
-
|
70
|
-
# The template can change from one time to the next (via the Plugin Manager)
|
71
|
-
template_file = File.join(templates_dir, "#{template}.template")
|
72
|
-
template_mtime = File.mtime(template_file)
|
73
|
-
|
74
|
-
if @sources.key?(template)
|
75
|
-
# refresh cached version if modified since last read
|
76
|
-
if template_mtime > @sources[template][:mtime]
|
77
|
-
@template[template][:mtime] = template_mtime
|
78
|
-
@template[template][:content] = File.read(template_file)
|
79
|
-
end
|
80
|
-
else
|
81
|
-
@sources[template] = {
|
82
|
-
mtime: template_mtime,
|
83
|
-
content: File.read(template_file)
|
84
|
-
}
|
85
|
-
end
|
86
|
-
|
87
|
-
@sources[template][:content]
|
88
|
-
end
|
89
|
-
# --------------------------------------------- /Plugin Manager interface
|
90
|
-
|
91
|
-
private
|
92
|
-
|
93
|
-
# This method returns the default location in which plugins should look
|
94
|
-
# for their templates.
|
95
|
-
def default_templates_dir
|
96
|
-
@default_templates_dir ||= begin
|
97
|
-
File.join(Configuration.paths_templates_plugins, @plugin::meta[:name].to_s)
|
98
|
-
end
|
99
|
-
end
|
100
|
-
end
|
101
|
-
end
|
102
|
-
end
|
@@ -1,57 +0,0 @@
|
|
1
|
-
module Dradis
|
2
|
-
module Plugins
|
3
|
-
module Templates
|
4
|
-
extend ActiveSupport::Concern
|
5
|
-
|
6
|
-
included do
|
7
|
-
# Keep track of any templates the plugin defines
|
8
|
-
paths['dradis/templates'] = 'templates'
|
9
|
-
end
|
10
|
-
|
11
|
-
module ClassMethods
|
12
|
-
def copy_templates(args={})
|
13
|
-
destination = args.fetch(:to)
|
14
|
-
|
15
|
-
destination_dir = File.join(destination, plugin_name.to_s)
|
16
|
-
FileUtils.mkdir_p(destination_dir) if plugin_templates.any?
|
17
|
-
|
18
|
-
plugin_templates.each do |template|
|
19
|
-
destination_file = File.join(destination_dir, File.basename(template))
|
20
|
-
|
21
|
-
next if skip?(destination_file)
|
22
|
-
|
23
|
-
Rails.logger.info do
|
24
|
-
"Updating templates for #{plugin_name} plugin. "\
|
25
|
-
"Destination: #{destination}"
|
26
|
-
end
|
27
|
-
FileUtils.cp(template, destination_file)
|
28
|
-
end
|
29
|
-
end
|
30
|
-
|
31
|
-
def plugin_templates(args={})
|
32
|
-
@templates ||= begin
|
33
|
-
if paths['dradis/templates'].existent.any?
|
34
|
-
Dir["#{paths['dradis/templates'].existent.first}/*"]
|
35
|
-
else
|
36
|
-
[]
|
37
|
-
end
|
38
|
-
end
|
39
|
-
end
|
40
|
-
|
41
|
-
private
|
42
|
-
|
43
|
-
# Normally we want to copy all templates so that the user always has
|
44
|
-
# the latest version.
|
45
|
-
#
|
46
|
-
# However, if it's a '.template' file, the user might have edited their
|
47
|
-
# local copy, and we don't want to override their changes. So only
|
48
|
-
# copy .template files over if the user has no copy at all (i.e. if
|
49
|
-
# this is the first time they've started Dradis since this template was
|
50
|
-
# added.)
|
51
|
-
def skip?(file_path)
|
52
|
-
File.exist?(file_path) && File.extname(file_path) == ".template"
|
53
|
-
end
|
54
|
-
end
|
55
|
-
end
|
56
|
-
end
|
57
|
-
end
|
@@ -1,28 +0,0 @@
|
|
1
|
-
require 'rails_helper'
|
2
|
-
|
3
|
-
# To run, execute from Dradis main app folder:
|
4
|
-
# bin/rspec [dradis-plugins path]/spec/lib/dradis/plugins/template_service_spec.rb
|
5
|
-
describe Dradis::Plugins::TemplateService do
|
6
|
-
describe '#process_template' do
|
7
|
-
let(:data) { double }
|
8
|
-
let(:plugin) { Dradis::Plugins::Nessus }
|
9
|
-
let(:template_service) do
|
10
|
-
Dradis::Plugins::TemplateService.new(plugin: plugin)
|
11
|
-
end
|
12
|
-
|
13
|
-
context 'liquid' do
|
14
|
-
before do
|
15
|
-
allow(data).to receive(:name).and_return('ReportHost')
|
16
|
-
allow(template_service).to receive(:template_source).and_return(
|
17
|
-
"{% if issue.evidence %}\n{% end if %}"
|
18
|
-
)
|
19
|
-
end
|
20
|
-
|
21
|
-
it 'does not parse the liquid data as fields' do
|
22
|
-
expect(template_service).to_not receive(:fields)
|
23
|
-
|
24
|
-
template_service.process_template(data: data)
|
25
|
-
end
|
26
|
-
end
|
27
|
-
end
|
28
|
-
end
|