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.
- checksums.yaml +7 -0
- data/.gitignore +11 -0
- data/.rspec +2 -0
- data/CHANGELOG.md +77 -0
- data/CONTRIBUTING.md +3 -0
- data/Gemfile +10 -0
- data/LICENSE +339 -0
- data/README.md +31 -0
- data/Rakefile +2 -0
- data/app/controllers/concerns/dradis/plugins/persistent_permissions.rb +43 -0
- data/app/controllers/dradis/plugins/export/base_controller.rb +18 -0
- data/dradis-plugins.gemspec +31 -0
- data/lib/dradis-plugins.rb +1 -0
- data/lib/dradis/plugins.rb +80 -0
- data/lib/dradis/plugins/base.rb +45 -0
- data/lib/dradis/plugins/configurable.rb +26 -0
- data/lib/dradis/plugins/content_service/base.rb +26 -0
- data/lib/dradis/plugins/content_service/boards.rb +32 -0
- data/lib/dradis/plugins/content_service/categories.rb +9 -0
- data/lib/dradis/plugins/content_service/content_blocks.rb +44 -0
- data/lib/dradis/plugins/content_service/core.rb +53 -0
- data/lib/dradis/plugins/content_service/evidence.rb +36 -0
- data/lib/dradis/plugins/content_service/issues.rb +94 -0
- data/lib/dradis/plugins/content_service/nodes.rb +88 -0
- data/lib/dradis/plugins/content_service/notes.rb +43 -0
- data/lib/dradis/plugins/content_service/properties.rb +9 -0
- data/lib/dradis/plugins/engine.rb +15 -0
- data/lib/dradis/plugins/export.rb +1 -0
- data/lib/dradis/plugins/export/base.rb +57 -0
- data/lib/dradis/plugins/gem_version.rb +17 -0
- data/lib/dradis/plugins/import.rb +3 -0
- data/lib/dradis/plugins/import/filters.rb +51 -0
- data/lib/dradis/plugins/import/filters/base.rb +16 -0
- data/lib/dradis/plugins/import/result.rb +12 -0
- data/lib/dradis/plugins/settings.rb +91 -0
- data/lib/dradis/plugins/template_service.rb +104 -0
- data/lib/dradis/plugins/templates.rb +57 -0
- data/lib/dradis/plugins/thor.rb +30 -0
- data/lib/dradis/plugins/thor_helper.rb +29 -0
- data/lib/dradis/plugins/upload.rb +10 -0
- data/lib/dradis/plugins/upload/base.rb +57 -0
- data/lib/dradis/plugins/upload/field_processor.rb +35 -0
- data/lib/dradis/plugins/upload/importer.rb +78 -0
- data/lib/dradis/plugins/version.rb +11 -0
- data/spec/internal/log/test.log +0 -0
- data/spec/lib/dradis/plugins/content_service/boards_spec.rb +45 -0
- data/spec/lib/dradis/plugins/content_service/issues_spec.rb +64 -0
- data/spec/settings_spec.rb +88 -0
- data/spec/spec_helper.rb +2 -0
- 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,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
|