dradis-plugins 4.11.0 → 4.12.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 +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
|