dradis-plugins 4.10.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/.github/pull_request_template.md +45 -0
- data/CHANGELOG.md +9 -0
- data/README.md +3 -4
- data/dradis-plugins.gemspec +2 -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 +15 -11
- 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
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
Please review [CONTRIBUTING.md](https://github.com/dradis/dradis-ce/blob/develop/CONTRIBUTING.md) and remove this line.
|
|
2
|
+
|
|
3
|
+
### Summary
|
|
4
|
+
|
|
5
|
+
Provide a general description of the code changes in your pull
|
|
6
|
+
request... were there any bugs you had fixed? If so, mention them. If
|
|
7
|
+
these bugs have open GitHub issues, be sure to tag them here as well,
|
|
8
|
+
to keep the conversation linked together.
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Testing Steps
|
|
12
|
+
|
|
13
|
+
Provide steps to test functionality, described in detail for someone not familiar with this part of the application / code base
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
### Other Information
|
|
17
|
+
|
|
18
|
+
If there's anything else that's important and relevant to your pull
|
|
19
|
+
request, mention that information here. This could include
|
|
20
|
+
benchmarks, or other information.
|
|
21
|
+
|
|
22
|
+
Thanks for contributing to Dradis!
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
### Copyright assignment
|
|
26
|
+
|
|
27
|
+
Collaboration is difficult with commercial closed source but we want
|
|
28
|
+
to keep as much of the OSS ethos as possible available to users
|
|
29
|
+
who want to fix it themselves.
|
|
30
|
+
|
|
31
|
+
In order to unambiguously own and sell Dradis Framework commercial
|
|
32
|
+
products, we must have the copyright associated with the entire
|
|
33
|
+
codebase. Any code you create which is merged must be owned by us.
|
|
34
|
+
That's not us trying to be a jerks, that's just the way it works.
|
|
35
|
+
|
|
36
|
+
You can delete this section, but the following sentence needs to
|
|
37
|
+
remain in the PR's description:
|
|
38
|
+
|
|
39
|
+
> I assign all rights, including copyright, to any future Dradis
|
|
40
|
+
> work by myself to Security Roots.
|
|
41
|
+
|
|
42
|
+
### Check List
|
|
43
|
+
|
|
44
|
+
- [ ] Added a CHANGELOG entry
|
|
45
|
+
- [ ] Added specs
|
data/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,12 @@
|
|
|
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
|
+
|
|
7
|
+
v4.11.0 (January 2024)
|
|
8
|
+
- No changes
|
|
9
|
+
|
|
1
10
|
v4.10.0 (September 2023)
|
|
2
11
|
- Add validations to the Export::BaseController
|
|
3
12
|
- Update gemspec links
|
data/README.md
CHANGED
|
@@ -8,17 +8,16 @@ 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
|
|
|
16
|
-
See the Dradis Framework's [README.md](https://github.com/dradis/
|
|
15
|
+
See the Dradis Framework's [README.md](https://github.com/dradis/dradis-ce/blob/develop/README.md)
|
|
17
16
|
|
|
18
17
|
|
|
19
18
|
## Contributing
|
|
20
19
|
|
|
21
|
-
See the Dradis Framework's [CONTRIBUTING.md](https://github.com/dradis/
|
|
20
|
+
See the Dradis Framework's [CONTRIBUTING.md](https://github.com/dradis/dradis-ce/blob/develop/CONTRIBUTING.md)
|
|
22
21
|
|
|
23
22
|
|
|
24
23
|
## License
|
data/dradis-plugins.gemspec
CHANGED
|
@@ -13,10 +13,10 @@ Gem::Specification.new do |spec|
|
|
|
13
13
|
spec.license = 'GPL-2'
|
|
14
14
|
|
|
15
15
|
spec.authors = ['Daniel Martin']
|
|
16
|
-
spec.homepage = 'http://dradis.com'
|
|
16
|
+
spec.homepage = 'http://dradis.com/ce/'
|
|
17
17
|
|
|
18
18
|
spec.files = `git ls-files`.split($\)
|
|
19
|
-
spec.executables = spec.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
|
19
|
+
spec.executables = spec.files.grep(%r{^bin/}).map { |f| File.basename(f) }
|
|
20
20
|
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
|
21
21
|
|
|
22
22
|
spec.add_development_dependency 'bundler'
|
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
|
-
autorequire:
|
|
8
|
+
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date:
|
|
11
|
+
date: 2024-05-07 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: bundler
|
|
@@ -53,11 +53,12 @@ dependencies:
|
|
|
53
53
|
- !ruby/object:Gem::Version
|
|
54
54
|
version: '0'
|
|
55
55
|
description: Required dependency for Dradis Framework.
|
|
56
|
-
email:
|
|
56
|
+
email:
|
|
57
57
|
executables: []
|
|
58
58
|
extensions: []
|
|
59
59
|
extra_rdoc_files: []
|
|
60
60
|
files:
|
|
61
|
+
- ".github/pull_request_template.md"
|
|
61
62
|
- ".gitignore"
|
|
62
63
|
- ".rspec"
|
|
63
64
|
- CHANGELOG.md
|
|
@@ -92,11 +93,14 @@ files:
|
|
|
92
93
|
- lib/dradis/plugins/import/filters.rb
|
|
93
94
|
- lib/dradis/plugins/import/filters/base.rb
|
|
94
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
|
|
95
99
|
- lib/dradis/plugins/settings.rb
|
|
96
100
|
- lib/dradis/plugins/settings/adapters/db.rb
|
|
97
101
|
- lib/dradis/plugins/settings/adapters/encrypted_configuration.rb
|
|
98
|
-
- lib/dradis/plugins/
|
|
99
|
-
- lib/dradis/plugins/templates.rb
|
|
102
|
+
- lib/dradis/plugins/templates/migrate_templates.rb
|
|
103
|
+
- lib/dradis/plugins/templates/samples.rb
|
|
100
104
|
- lib/dradis/plugins/thor.rb
|
|
101
105
|
- lib/dradis/plugins/thor_helper.rb
|
|
102
106
|
- lib/dradis/plugins/upload.rb
|
|
@@ -109,15 +113,15 @@ files:
|
|
|
109
113
|
- spec/lib/dradis/plugins/content_service/boards_spec.rb
|
|
110
114
|
- spec/lib/dradis/plugins/content_service/content_blocks_spec.rb
|
|
111
115
|
- spec/lib/dradis/plugins/content_service/issues_spec.rb
|
|
116
|
+
- spec/lib/dradis/plugins/mapping_service_spec.rb
|
|
112
117
|
- spec/lib/dradis/plugins/settings/adapters/encrypted_configuration_spec.rb
|
|
113
|
-
- spec/lib/dradis/plugins/template_service_spec.rb
|
|
114
118
|
- spec/settings_spec.rb
|
|
115
119
|
- spec/spec_helper.rb
|
|
116
|
-
homepage: http://dradis.com
|
|
120
|
+
homepage: http://dradis.com/ce/
|
|
117
121
|
licenses:
|
|
118
122
|
- GPL-2
|
|
119
123
|
metadata: {}
|
|
120
|
-
post_install_message:
|
|
124
|
+
post_install_message:
|
|
121
125
|
rdoc_options: []
|
|
122
126
|
require_paths:
|
|
123
127
|
- lib
|
|
@@ -133,7 +137,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
133
137
|
version: '0'
|
|
134
138
|
requirements: []
|
|
135
139
|
rubygems_version: 3.1.4
|
|
136
|
-
signing_key:
|
|
140
|
+
signing_key:
|
|
137
141
|
specification_version: 4
|
|
138
142
|
summary: Plugin manager for the Dradis Framework project.
|
|
139
143
|
test_files:
|
|
@@ -142,7 +146,7 @@ test_files:
|
|
|
142
146
|
- spec/lib/dradis/plugins/content_service/boards_spec.rb
|
|
143
147
|
- spec/lib/dradis/plugins/content_service/content_blocks_spec.rb
|
|
144
148
|
- spec/lib/dradis/plugins/content_service/issues_spec.rb
|
|
149
|
+
- spec/lib/dradis/plugins/mapping_service_spec.rb
|
|
145
150
|
- spec/lib/dradis/plugins/settings/adapters/encrypted_configuration_spec.rb
|
|
146
|
-
- spec/lib/dradis/plugins/template_service_spec.rb
|
|
147
151
|
- spec/settings_spec.rb
|
|
148
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
|