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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b275df2487d66dae267758c2e2d95b41fd7364769804fff82cc9221a916c031d
4
- data.tar.gz: 806cd8a53d29ae2d43ec053c7ae2304185d3ca7ae7b419e7668144263814e978
3
+ metadata.gz: 303a79ec7a8bf67064fe8a6dbb7e7c86cb9bc9ff4de1df69f7ab94ba9f55f805
4
+ data.tar.gz: 4fd4d6780ba9737bd45551f8c1b40e9f9654989f8925fae1bc38261e2288f95c
5
5
  SHA512:
6
- metadata.gz: dfb8b301121edb09990744c2cb43bf17d82d00055140219188f6f75123f73db112a74236be2b4302124be96b6105961f19e2d00a02497ff0f63c056039c9b690
7
- data.tar.gz: fd04af3f6aa47766507c71d1d30331f000ec26ee65e17ac14f1e9259f822763c58238f241c02c00df7914e85f78e1fb047de098111c440c866e4ebe3aa9376b6
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://dradisframework.org/) > 3.0, or [Dradis Pro](https://dradisframework.com/pro/).
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
 
@@ -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.fetch(:scope, :published)
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
@@ -7,7 +7,7 @@ module Dradis
7
7
 
8
8
  module VERSION
9
9
  MAJOR = 4
10
- MINOR = 11
10
+ MINOR = 12
11
11
  TINY = 0
12
12
  PRE = nil
13
13
 
@@ -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,8 @@
1
+ module Dradis
2
+ module Plugins
3
+ module Mappings
4
+ end
5
+ end
6
+ end
7
+
8
+ require 'dradis/plugins/mappings/base'
@@ -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 = args.fetch(:content_service, default_content_service)
33
- @template_service = args.fetch(:template_service, default_template_service)
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
- def default_template_service
76
- @template ||= Dradis::Plugins::TemplateService.new(
77
- logger: logger,
78
- plugin: plugin
78
+ Dradis::Plugins::MappingService.new(
79
+ destination: destination,
80
+ integration: plugin
79
81
  )
80
82
  end
81
83
  end # Importer
@@ -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/template_service'
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.11.0
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-01-17 00:00:00.000000000 Z
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/template_service.rb
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.3.7
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