dradis-plugins 3.18.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.
Files changed (50) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +11 -0
  3. data/.rspec +2 -0
  4. data/CHANGELOG.md +77 -0
  5. data/CONTRIBUTING.md +3 -0
  6. data/Gemfile +10 -0
  7. data/LICENSE +339 -0
  8. data/README.md +31 -0
  9. data/Rakefile +2 -0
  10. data/app/controllers/concerns/dradis/plugins/persistent_permissions.rb +43 -0
  11. data/app/controllers/dradis/plugins/export/base_controller.rb +18 -0
  12. data/dradis-plugins.gemspec +31 -0
  13. data/lib/dradis-plugins.rb +1 -0
  14. data/lib/dradis/plugins.rb +80 -0
  15. data/lib/dradis/plugins/base.rb +45 -0
  16. data/lib/dradis/plugins/configurable.rb +26 -0
  17. data/lib/dradis/plugins/content_service/base.rb +26 -0
  18. data/lib/dradis/plugins/content_service/boards.rb +32 -0
  19. data/lib/dradis/plugins/content_service/categories.rb +9 -0
  20. data/lib/dradis/plugins/content_service/content_blocks.rb +44 -0
  21. data/lib/dradis/plugins/content_service/core.rb +53 -0
  22. data/lib/dradis/plugins/content_service/evidence.rb +36 -0
  23. data/lib/dradis/plugins/content_service/issues.rb +94 -0
  24. data/lib/dradis/plugins/content_service/nodes.rb +88 -0
  25. data/lib/dradis/plugins/content_service/notes.rb +43 -0
  26. data/lib/dradis/plugins/content_service/properties.rb +9 -0
  27. data/lib/dradis/plugins/engine.rb +15 -0
  28. data/lib/dradis/plugins/export.rb +1 -0
  29. data/lib/dradis/plugins/export/base.rb +57 -0
  30. data/lib/dradis/plugins/gem_version.rb +17 -0
  31. data/lib/dradis/plugins/import.rb +3 -0
  32. data/lib/dradis/plugins/import/filters.rb +51 -0
  33. data/lib/dradis/plugins/import/filters/base.rb +16 -0
  34. data/lib/dradis/plugins/import/result.rb +12 -0
  35. data/lib/dradis/plugins/settings.rb +91 -0
  36. data/lib/dradis/plugins/template_service.rb +104 -0
  37. data/lib/dradis/plugins/templates.rb +57 -0
  38. data/lib/dradis/plugins/thor.rb +30 -0
  39. data/lib/dradis/plugins/thor_helper.rb +29 -0
  40. data/lib/dradis/plugins/upload.rb +10 -0
  41. data/lib/dradis/plugins/upload/base.rb +57 -0
  42. data/lib/dradis/plugins/upload/field_processor.rb +35 -0
  43. data/lib/dradis/plugins/upload/importer.rb +78 -0
  44. data/lib/dradis/plugins/version.rb +11 -0
  45. data/spec/internal/log/test.log +0 -0
  46. data/spec/lib/dradis/plugins/content_service/boards_spec.rb +45 -0
  47. data/spec/lib/dradis/plugins/content_service/issues_spec.rb +64 -0
  48. data/spec/settings_spec.rb +88 -0
  49. data/spec/spec_helper.rb +2 -0
  50. metadata +138 -0
@@ -0,0 +1,12 @@
1
+ module Dradis::Plugins::Import
2
+ class Result
3
+ attr_accessor :description, :id, :tags, :title
4
+
5
+ def initialize(args={})
6
+ @description = args[:description] || "The Import plugin didn't provide a :description for this result."
7
+ @id = args[:id] || "The Import plugin didn't provide an :id for this result."
8
+ @tags = args[:tags] || []
9
+ @title = args[:title] || "The Import plugin didn't provide a :title for this result."
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,91 @@
1
+ module Dradis::Plugins
2
+ class Settings
3
+ attr_reader :namespace
4
+
5
+ def initialize(namespace)
6
+ @namespace = namespace
7
+ @dirty_options ||= {}
8
+ @default_options ||= HashWithIndifferentAccess.new
9
+ end
10
+
11
+ def respond_to?(name)
12
+ super || @dirty_options.key?(name.to_sym)
13
+ end
14
+
15
+ def all
16
+ @default_options.map do |key, value|
17
+ {
18
+ name: key.to_sym,
19
+ value: value = dirty_or_db_setting_or_default(key.to_sym),
20
+ default: is_default?(key, value)
21
+ }
22
+ end.sort_by{ |o| o[:name] }
23
+ end
24
+
25
+ def save
26
+ @dirty_options.reject{ |k, v| v.present? && v == db_setting(k) }.each{ |k, v| write_to_db(k, v) }
27
+ end
28
+
29
+ def update_settings(opts = {})
30
+ opts.select{ |k, v| @default_options.key?(k) }.each do |k, v|
31
+ @dirty_options[k.to_sym] = v
32
+ end
33
+ save
34
+ end
35
+
36
+ def reset_defaults!
37
+ @dirty_options = {}
38
+ @default_options.each do |key, value|
39
+ Configuration.where(name: namespaced_key(key)).each(&:destroy)
40
+ end
41
+ end
42
+
43
+ def is_default?(key, value)
44
+ value.to_s == @default_options[key.to_sym].to_s
45
+ end
46
+
47
+ private
48
+
49
+ # ---------------------------------------------------- Method missing magic
50
+ def method_missing(name, *args, &blk)
51
+ if name.to_s =~ /^default_(.*)=$/
52
+ @default_options[$1.to_sym] = args.first
53
+ elsif name.to_s =~ /=$/
54
+ @dirty_options[$`.to_sym] = args.first
55
+ elsif @default_options.key?(name)
56
+ dirty_or_db_setting_or_default(name)
57
+ else
58
+ super
59
+ end
60
+ end
61
+ # --------------------------------------------------- /Method missing magic
62
+
63
+ def write_to_db(key, value)
64
+ db_setting = Configuration.find_or_create_by(name: namespaced_key(key))
65
+ db_setting.update_attribute(:value, value)
66
+ end
67
+
68
+
69
+ def db_setting(key)
70
+ Configuration.where(name: namespaced_key(key)).first.value rescue nil
71
+ end
72
+
73
+ # This method looks up in the configuration repository DB to see if the
74
+ # user has provided a value for the given setting. If not, the default
75
+ # value is returned.
76
+ def dirty_or_db_setting_or_default(key)
77
+ if @dirty_options.key?(key)
78
+ @dirty_options[key]
79
+ elsif Configuration.exists?(name: namespaced_key(key))
80
+ db_setting(key)
81
+ else
82
+ @default_options[key]
83
+ end
84
+ end
85
+
86
+ # Builds namespaced key
87
+ def namespaced_key(key)
88
+ [self.namespace.to_s, key.to_s.underscore].join(":")
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,104 @@
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
+
12
+ # For a given entry, return a text blob resulting from applying the
13
+ # chosen template to the supplied entry.
14
+ def process_template(args={})
15
+ self.template = args[:template]
16
+ data = args[:data]
17
+
18
+ processor = @plugin::FieldProcessor.new(data: data)
19
+
20
+ template_source.gsub( /%(.*?)%/ ) do |field|
21
+ name = field[1..-2]
22
+ if fields.include?(name)
23
+ processor.value(field: name)
24
+ else
25
+ "Field [#{field}] not recognized by the plugin"
26
+ end
27
+ end
28
+ end
29
+
30
+
31
+ # ---------------------------------------------- Plugin Manager interface
32
+
33
+ # This lists the fields defined by this plugin that can be used in the
34
+ # template
35
+ def fields
36
+ @fields ||= {}
37
+ @fields[template] ||= begin
38
+ fields_file = File.join(templates_dir, "#{template}.fields")
39
+ File.readlines(fields_file).map(&:chomp)
40
+ end
41
+ end
42
+
43
+ # This returns a sample of valid entry for the Plugin Manager
44
+ def sample
45
+ @sample ||= {}
46
+ @sample[template] ||= begin
47
+ sample_file = File.join(templates_dir, "#{template}.sample")
48
+ File.read(sample_file)
49
+ end
50
+ end
51
+
52
+ # Set the plugin's item template. This is used by the Plugins Manager
53
+ # to force the plugin to use the new_template (provided by the user)
54
+ def set_template(args={})
55
+ template = args[:template]
56
+ content = args[:content]
57
+
58
+ @sources ||= {}
59
+ @sources[template] ||= {
60
+ content: nil,
61
+ mtime: DateTime.now
62
+ }
63
+ @sources[template][:content] = content
64
+ end
65
+
66
+ # This method returns the current template's source. It caches the
67
+ # template based on the file's last-modified time and refreshes the
68
+ # cached copy when it detects changes.
69
+ def template_source
70
+ @sources ||= {}
71
+
72
+ # The template can change from one time to the next (via the Plugin Manager)
73
+ template_file = File.join(templates_dir, "#{template}.template")
74
+ template_mtime = File.mtime(template_file)
75
+
76
+ if @sources.key?(template)
77
+ # refresh cached version if modified since last read
78
+ if template_mtime > @sources[template][:mtime]
79
+ @template[template][:mtime] = template_mtime
80
+ @template[template][:content] = File.read( template_file )
81
+ end
82
+ else
83
+ @sources[template] = {
84
+ mtime: template_mtime,
85
+ content: File.read(template_file)
86
+ }
87
+ end
88
+
89
+ @sources[template][:content]
90
+ end
91
+ # --------------------------------------------- /Plugin Manager interface
92
+
93
+ private
94
+
95
+ # This method returns the default location in which plugins should look
96
+ # for their templates.
97
+ def default_templates_dir
98
+ @default_templates_dir ||= begin
99
+ File.join(Configuration.paths_templates_plugins, @plugin::meta[:name].to_s)
100
+ end
101
+ end
102
+ end
103
+ end
104
+ end
@@ -0,0 +1,57 @@
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
@@ -0,0 +1,30 @@
1
+ module Dradis
2
+ module Plugins
3
+ module Thor
4
+ def self.included(base)
5
+ base.extend(ClassMethods)
6
+
7
+ base.class_eval do
8
+ # Keep track of any templates the plugin defines
9
+ paths['dradis/thorfiles'] = 'lib/tasks'
10
+ end
11
+ end
12
+
13
+ module ClassMethods
14
+ def load_thor_tasks
15
+ plugin_thorfiles.each do |thorfile|
16
+ require thorfile
17
+ end
18
+ end
19
+
20
+ def plugin_thorfiles(args={})
21
+ if paths['dradis/thorfiles'].existent.any?
22
+ Dir["%s/thorfile.rb" % paths['dradis/thorfiles'].existent]
23
+ else
24
+ []
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,29 @@
1
+ module Dradis
2
+ module Plugins
3
+ # Helper methods for plugin Thor tasks
4
+ module ThorHelper
5
+ attr_accessor :task_options, :logger
6
+
7
+ def detect_and_set_project_scope
8
+ task_options[:project_id] = Project.new.id
9
+ end
10
+
11
+ def task_options
12
+ @task_options ||= { logger: logger }
13
+ end
14
+
15
+ def logger
16
+ @logger ||= default_logger
17
+ end
18
+
19
+
20
+ private
21
+ def default_logger
22
+ STDOUT.sync = true
23
+ logger = Logger.new(STDOUT)
24
+ logger.level = Logger::DEBUG
25
+ logger
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,10 @@
1
+ module Dradis
2
+ module Plugins
3
+ module Upload
4
+ end
5
+ end
6
+ end
7
+
8
+ require 'dradis/plugins/upload/base'
9
+ require 'dradis/plugins/upload/importer'
10
+ require 'dradis/plugins/upload/field_processor'
@@ -0,0 +1,57 @@
1
+
2
+ # When you call provides :upload in your Engine, this module gets included. It
3
+ # provides two features:
4
+ #
5
+ # a) an .uploaders() class method that by default responds with a single array
6
+ # item pointing to the engine's parent. This will be used by the framework to
7
+ # locate the ::Importer object that does the upload heavy lifting.
8
+ #
9
+ # If your plugin implements more than one uploader, each one would be contained
10
+ # in its own namespace, and you should overwrite the .uploaders() method to
11
+ # return an array of all these namespaces. See method definition for an
12
+ # example.
13
+ #
14
+ # b) it adds a .meta() method to the engine's parent module, containing the
15
+ # name, description and version of the add-on.
16
+ #
17
+ # Again, if you implement more than one uploader, make sure you create a
18
+ # .meta() class-level method in each of your namespaces.
19
+ #
20
+
21
+ module Dradis::Plugins::Upload::Base
22
+ extend ActiveSupport::Concern
23
+
24
+ included do
25
+ parent.extend NamespaceClassMethods
26
+ end
27
+
28
+ module ClassMethods
29
+ # Return the list of modules that provide upload functionality. This is
30
+ # useful if one plugin provides uploading functionality for more than one
31
+ # file type (e.g. the Projects plugin allows you to upload a Package or a
32
+ # Template).
33
+ #
34
+ # The default implementation just returns this plugin's namespace (e.g.
35
+ # Dradis::Plugins::Nessus). If a plugin provides multiple uploaders, they
36
+ # can override this method:
37
+ # def self.uploders
38
+ # [
39
+ # Dradis::Plugins::Projects::Package,
40
+ # Dradis::Plugins::Projects::Template
41
+ # ]
42
+ # end
43
+ def uploaders()
44
+ [parent]
45
+ end
46
+ end
47
+
48
+ module NamespaceClassMethods
49
+ def meta
50
+ {
51
+ name: self::Engine::plugin_name,
52
+ description: self::Engine::plugin_description,
53
+ version: self::VERSION::STRING
54
+ }
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,35 @@
1
+ # The plugin's FieldProcessor is in charge of understanding the incoming data
2
+ # from the uploaded file and extracting the fields to populate the template.
3
+ # Plugins are expected to overwrite the value() method.
4
+ #
5
+ module Dradis
6
+ module Plugins
7
+ module Upload
8
+
9
+ class FieldProcessor
10
+ attr_reader :data
11
+
12
+ def initialize(args={})
13
+ @data = args[:data]
14
+ post_initialize(args)
15
+ end
16
+
17
+ # Inspect the data object currently stored in this processor instance
18
+ # and extract the value of the requested field.
19
+ #
20
+ # Subclasses will overwrite this method.
21
+ def value(args={})
22
+ field = args[:field]
23
+ "Sorry, this plugin doesn't define a FieldProcessor (called for [#{field}])"
24
+ end
25
+
26
+ protected
27
+ # This can be overriden by subclasses
28
+ def post_initialize(args={})
29
+ # nop
30
+ end
31
+ end
32
+
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,78 @@
1
+ # This module contains basic Upload plugin functions to control template
2
+ # sample and field management for the Plugin Manager.
3
+ #
4
+ module Dradis
5
+ module Plugins
6
+ module Upload
7
+ class Importer
8
+ attr_accessor(
9
+ :content_service,
10
+ :default_user_id,
11
+ :logger,
12
+ :options,
13
+ :plugin,
14
+ :project,
15
+ :template_service
16
+ )
17
+
18
+ def initialize(args={})
19
+ @options = args
20
+
21
+ @logger = args.fetch(:logger, Rails.logger)
22
+ @plugin = args[:plugin] || default_plugin
23
+ @project = args.key?(:project_id) ? Project.find(args[:project_id]) : nil
24
+ @default_user_id = args[:default_user_id] || -1
25
+
26
+ @content_service = args.fetch(:content_service, default_content_service)
27
+ @template_service = args.fetch(:template_service, default_template_service)
28
+
29
+ post_initialize(args)
30
+ end
31
+
32
+ def import(args={})
33
+ raise "The import() method is not implemented in this plugin [#{self.class.name}]."
34
+ end
35
+
36
+ # This method can be overwriten by plugins to do initialization tasks.
37
+ def post_initialize(args={})
38
+ end
39
+
40
+ private
41
+ def default_content_service
42
+ @content ||= Dradis::Plugins::ContentService::Base.new(
43
+ logger: logger,
44
+ plugin: plugin,
45
+ project: project
46
+ )
47
+ end
48
+
49
+ # This assumes the plugin's Importer class is directly nexted into the
50
+ # plugin's namespace (e.g. Dradis::Plugins::Nessus::Importer)
51
+ def default_plugin
52
+ plugin_module = self.class.name.deconstantize
53
+ plugin_constant = plugin_module.constantize
54
+
55
+ if defined?(plugin_constant::Engine)
56
+ plugin_engine = plugin_constant::Engine
57
+ if Dradis::Plugins.registered?(plugin_engine)
58
+ plugin_constant
59
+ else
60
+ raise "Your plugin isn't registered with the framework."
61
+ end
62
+ else
63
+ raise "You need to pass a :plugin value to your Importer or define it under your plugin's root namespace."
64
+ end
65
+ end
66
+
67
+
68
+ def default_template_service
69
+ @template ||= Dradis::Plugins::TemplateService.new(
70
+ logger: logger,
71
+ plugin: plugin
72
+ )
73
+ end
74
+ end # Importer
75
+
76
+ end # Upload
77
+ end # Plugins
78
+ end # Core