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