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