dradis-plugins 3.18.0

Sign up to get free protection for your applications and to get access to all the features.
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