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,36 @@
|
|
1
|
+
module Dradis::Plugins::ContentService
|
2
|
+
module Evidence
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
def create_evidence(args={})
|
6
|
+
content = args.fetch(:content, default_evidence_content)
|
7
|
+
node = args.fetch(:node, default_node_parent)
|
8
|
+
issue = args[:issue] || default_evidence_issue
|
9
|
+
|
10
|
+
evidence = node.evidence.new(issue_id: issue.id, content: content)
|
11
|
+
|
12
|
+
if evidence.valid?
|
13
|
+
evidence.save
|
14
|
+
else
|
15
|
+
try_rescue_from_length_validation(
|
16
|
+
model: evidence,
|
17
|
+
field: :content,
|
18
|
+
text: content,
|
19
|
+
msg: 'Error in create_evidence()'
|
20
|
+
)
|
21
|
+
end
|
22
|
+
|
23
|
+
evidence
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def default_evidence_content
|
29
|
+
"create_evidence() invoked by #{plugin} without a :content parameter"
|
30
|
+
end
|
31
|
+
|
32
|
+
def default_evidence_issue
|
33
|
+
create_issue(text: "#[Title]#\nAuto-generated issue.\n\n#[Description]#\ncreate_evidence() invoked by #{plugin} without an :issue parameter")
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
module Dradis::Plugins::ContentService
|
2
|
+
module Issues
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
def all_issues
|
6
|
+
project.issues.where(category_id: default_issue_category.id)
|
7
|
+
end
|
8
|
+
|
9
|
+
def create_issue(args={})
|
10
|
+
text = args.fetch(:text, default_issue_text)
|
11
|
+
# NOTE that ID is the unique issue identifier assigned by the plugin,
|
12
|
+
# and is not to be confused with the Issue#id primary key
|
13
|
+
id = args.fetch(:id, default_issue_id)
|
14
|
+
|
15
|
+
# Bail if we already have this issue in the cache
|
16
|
+
uuid = [plugin::Engine::plugin_name, id]
|
17
|
+
cache_key = uuid.join('-')
|
18
|
+
|
19
|
+
return issue_cache[cache_key] if issue_cache.key?(cache_key)
|
20
|
+
|
21
|
+
# we inject the source Plugin and unique ID into the issue's text
|
22
|
+
plugin_details =
|
23
|
+
"\n\n#[plugin]#\n#{uuid[0]}\n" \
|
24
|
+
"\n\n#[plugin_id]#\n#{uuid[1]}\n"
|
25
|
+
text << plugin_details
|
26
|
+
|
27
|
+
issue = Issue.new(text: text) do |i|
|
28
|
+
i.author = default_author
|
29
|
+
i.node = project.issue_library
|
30
|
+
i.category = default_issue_category
|
31
|
+
end
|
32
|
+
|
33
|
+
if issue.valid?
|
34
|
+
issue.save
|
35
|
+
else
|
36
|
+
try_rescue_from_length_validation(
|
37
|
+
model: issue,
|
38
|
+
field: :text,
|
39
|
+
text: text,
|
40
|
+
msg: 'Error in create_issue()',
|
41
|
+
tail: plugin_details
|
42
|
+
)
|
43
|
+
end
|
44
|
+
|
45
|
+
issue_cache.store(cache_key, issue)
|
46
|
+
end
|
47
|
+
|
48
|
+
# Create a hash with all issues where the keys correspond to the field passed
|
49
|
+
# as an argument.
|
50
|
+
#
|
51
|
+
# This is use by the plugins to check whether a given issue is already in
|
52
|
+
# the project.
|
53
|
+
# def all_issues_by_field(field)
|
54
|
+
# # we don't memoize it because we want it to reflect recently added Issues
|
55
|
+
# klass = class_for(:issue)
|
56
|
+
#
|
57
|
+
# issues_map = klass.where(category_id: default_issue_category.id).map do |issue|
|
58
|
+
# [issue.fields[field], issue]
|
59
|
+
# end
|
60
|
+
# Hash[issues_map]
|
61
|
+
# end
|
62
|
+
|
63
|
+
# Accesing the library by primary sorting key. Raise an exception unless
|
64
|
+
# the issue library cache has been initialized.
|
65
|
+
def issue_cache
|
66
|
+
@issue_cache ||= begin
|
67
|
+
issues_map = all_issues.map do |issue|
|
68
|
+
cache_key = [
|
69
|
+
issue.fields['plugin'],
|
70
|
+
issue.fields['plugin_id']
|
71
|
+
].join('-')
|
72
|
+
|
73
|
+
[cache_key, issue]
|
74
|
+
end
|
75
|
+
Hash[issues_map]
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
|
80
|
+
private
|
81
|
+
|
82
|
+
def default_issue_category
|
83
|
+
@default_issue_category ||= Category.issue
|
84
|
+
end
|
85
|
+
|
86
|
+
def default_issue_id
|
87
|
+
"create_issue() invoked by #{plugin} without an :id parameter"
|
88
|
+
end
|
89
|
+
|
90
|
+
def default_issue_text
|
91
|
+
"create_issue() invoked by #{plugin} without a :text parameter"
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
module Dradis::Plugins::ContentService
|
2
|
+
module Nodes
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
def reporting_nodes
|
6
|
+
nodes = []
|
7
|
+
|
8
|
+
nodes |= nodes_from_evidence
|
9
|
+
nodes |= nodes_from_properties
|
10
|
+
|
11
|
+
# Note that the below sorting would the non-IP nodes first, then the IP
|
12
|
+
# nodes, and will sort them by each octet.
|
13
|
+
#
|
14
|
+
# See:
|
15
|
+
# http://stackoverflow.com/questions/13996033/sorting-an-array-in-ruby-special-case
|
16
|
+
# http://tech.maynurd.com/archives/124
|
17
|
+
nodes.sort_by! { |node| node.label.split('.').map(&:to_i) }
|
18
|
+
end
|
19
|
+
|
20
|
+
def create_node(args={})
|
21
|
+
label = args[:label] || default_node_label
|
22
|
+
parent = args[:parent] || default_node_parent
|
23
|
+
|
24
|
+
type_id = begin
|
25
|
+
if args[:type]
|
26
|
+
tmp_type = args[:type].to_s.upcase
|
27
|
+
if Node::Types::const_defined?(tmp_type)
|
28
|
+
"Node::Types::#{tmp_type}".constantize
|
29
|
+
else
|
30
|
+
default_node_type
|
31
|
+
end
|
32
|
+
else
|
33
|
+
default_node_type
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
new_node = parent.children.find_or_initialize_by(
|
38
|
+
label: label,
|
39
|
+
type_id: type_id
|
40
|
+
)
|
41
|
+
# `Node#project_id` method does not exist in CE. We set the project for
|
42
|
+
# `new_node` once it is initialized using `Node#project=`
|
43
|
+
new_node.project = parent.project
|
44
|
+
new_node.save
|
45
|
+
new_node
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
def default_node_label
|
51
|
+
"create_node() invoked by #{plugin} without a :label parameter"
|
52
|
+
end
|
53
|
+
|
54
|
+
def default_node_parent
|
55
|
+
@default_parent_node ||= project.plugin_parent_node
|
56
|
+
end
|
57
|
+
|
58
|
+
def default_node_type
|
59
|
+
@default_node_type ||= Node::Types::DEFAULT
|
60
|
+
end
|
61
|
+
|
62
|
+
|
63
|
+
# Private: this method returns a list of nodes associated with Evidence
|
64
|
+
# instances. When a node is affected by multiple issues, or multiple pieces
|
65
|
+
# of evidence, we just want a single reference to it.
|
66
|
+
#
|
67
|
+
# Returns and Array with a unique collection of Nodes.
|
68
|
+
def nodes_from_evidence
|
69
|
+
all_issues.
|
70
|
+
includes(:evidence, evidence: :node).
|
71
|
+
collect(&:evidence).
|
72
|
+
# Each Issue can have 0, 1 or more Evidence
|
73
|
+
map { |evidence_collection| evidence_collection.collect(&:node) }.
|
74
|
+
flatten.
|
75
|
+
uniq
|
76
|
+
end
|
77
|
+
|
78
|
+
# Private: this method returns a list of nodes in the project that have some
|
79
|
+
# properties associated with them. Typically properties are used for HOST
|
80
|
+
# type nodes, and even if they have no issues / evidence associated, we want
|
81
|
+
# to include them in the report.
|
82
|
+
#
|
83
|
+
# Returns and Array with a unique collection of Nodes.
|
84
|
+
def nodes_from_properties
|
85
|
+
project.nodes.user_nodes.where('properties IS NOT NULL AND properties != \'{}\'')
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module Dradis::Plugins::ContentService
|
2
|
+
module Notes
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
def all_notes
|
6
|
+
project.notes.where(category: Category.report)
|
7
|
+
end
|
8
|
+
|
9
|
+
def create_note(args={})
|
10
|
+
cat = args.fetch(:category, default_note_category)
|
11
|
+
node = args.fetch(:node, default_node_parent)
|
12
|
+
text = args.fetch(:text, default_note_text)
|
13
|
+
|
14
|
+
note = node.notes.new(
|
15
|
+
text: text,
|
16
|
+
category: cat,
|
17
|
+
author: default_author
|
18
|
+
)
|
19
|
+
|
20
|
+
if note.valid?
|
21
|
+
note.save
|
22
|
+
else
|
23
|
+
try_rescue_from_length_validation(
|
24
|
+
model: note,
|
25
|
+
field: :text,
|
26
|
+
text: text,
|
27
|
+
msg: 'Error in create_note()'
|
28
|
+
)
|
29
|
+
end
|
30
|
+
|
31
|
+
note
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
def default_note_category
|
36
|
+
@default_note_category ||= Category.default
|
37
|
+
end
|
38
|
+
|
39
|
+
def default_note_text
|
40
|
+
"create_note() invoked by #{plugin} without a :text parameter"
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module Dradis
|
2
|
+
module Plugins
|
3
|
+
class Engine < ::Rails::Engine
|
4
|
+
isolate_namespace Dradis::Plugins
|
5
|
+
|
6
|
+
config.dradis = ActiveSupport::OrderedOptions.new
|
7
|
+
|
8
|
+
initializer "dradis-plugins.set_configs" do |app|
|
9
|
+
options = app.config.dradis
|
10
|
+
options.base_export_controller_class_name ||= 'AuthenticatedController'
|
11
|
+
options.thor_helper_module ||= Dradis::Plugins::ThorHelper
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
require 'dradis/plugins/export/base'
|
@@ -0,0 +1,57 @@
|
|
1
|
+
# This module contains basic Export plugin functions.
|
2
|
+
#
|
3
|
+
|
4
|
+
module Dradis
|
5
|
+
module Plugins
|
6
|
+
module Export
|
7
|
+
class Base
|
8
|
+
attr_accessor :content_service, :logger, :options, :plugin, :project
|
9
|
+
|
10
|
+
def initialize(args={})
|
11
|
+
# Save everything just in case the implementing class needs any of it.
|
12
|
+
@options = args
|
13
|
+
|
14
|
+
# Can't use :fetch for :plugin or :default_plugin gets evaluated
|
15
|
+
@logger = args.fetch(:logger, Rails.logger)
|
16
|
+
@plugin = args[:plugin] || default_plugin
|
17
|
+
@project = args.key?(:project_id) ? Project.find(args[:project_id]) : nil
|
18
|
+
|
19
|
+
@content_service = args.fetch(:content_service, default_content_service)
|
20
|
+
|
21
|
+
post_initialize(args)
|
22
|
+
end
|
23
|
+
|
24
|
+
def export(args={})
|
25
|
+
raise "The export() method is not implemented in this plugin [#{self.class.name}]."
|
26
|
+
end
|
27
|
+
|
28
|
+
# This method can be overwriten by plugins to do initialization tasks.
|
29
|
+
def post_initialize(args={})
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
def default_content_service
|
34
|
+
@content ||= Dradis::Plugins::ContentService::Base.new(
|
35
|
+
logger: logger,
|
36
|
+
plugin: plugin,
|
37
|
+
project: project
|
38
|
+
)
|
39
|
+
end
|
40
|
+
|
41
|
+
# This assumes the plugin's Exporter class is directly nexted into the
|
42
|
+
# plugin's namespace (e.g. Dradis::Plugins::HtmlExport::Exporter)
|
43
|
+
def default_plugin
|
44
|
+
plugin_module = self.class.name.deconstantize
|
45
|
+
plugin_constant = plugin_module.constantize
|
46
|
+
plugin_engine = plugin_constant::Engine
|
47
|
+
if Dradis::Plugins.registered?(plugin_engine)
|
48
|
+
plugin_constant
|
49
|
+
else
|
50
|
+
raise "You need to pass a :plugin value to your Exporter or define it under your plugin's root namespace."
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
end # Base
|
55
|
+
end # Export
|
56
|
+
end # Plugins
|
57
|
+
end # Core
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Dradis
|
2
|
+
module Plugins
|
3
|
+
# Returns the version of the currently loaded Frontend as a <tt>Gem::Version</tt>
|
4
|
+
def self.gem_version
|
5
|
+
Gem::Version.new VERSION::STRING
|
6
|
+
end
|
7
|
+
|
8
|
+
module VERSION
|
9
|
+
MAJOR = 3
|
10
|
+
MINOR = 18
|
11
|
+
TINY = 0
|
12
|
+
PRE = nil
|
13
|
+
|
14
|
+
STRING = [MAJOR, MINOR, TINY, PRE].compact.join('.')
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
module Dradis
|
2
|
+
module Plugins
|
3
|
+
module Import
|
4
|
+
|
5
|
+
module Filters
|
6
|
+
class << self
|
7
|
+
# -- Class Methods --------------------------------------------------------
|
8
|
+
# One Import plugin can define several filters (e.g. to query different
|
9
|
+
# endpoints of a remote API).
|
10
|
+
#
|
11
|
+
# Use this method in your Importer to register different filters, pass a
|
12
|
+
# block or a class.
|
13
|
+
#
|
14
|
+
# Examples:
|
15
|
+
#
|
16
|
+
# register_filter :by_osvdb_id do
|
17
|
+
# def c
|
18
|
+
# end
|
19
|
+
def add(plugin, label, filter, &block)
|
20
|
+
filter ||= Class.new(Dradis::Plugins::Import::Filters::Base)
|
21
|
+
filter.class_eval(&block) if block_given?
|
22
|
+
|
23
|
+
unless filter.method_defined?(:query)
|
24
|
+
raise NoMethodError, "query() is not declared in the #{label.inspect} filter"
|
25
|
+
end
|
26
|
+
|
27
|
+
base = Dradis::Plugins::Import::Filters::Base
|
28
|
+
unless filter.ancestors.include?(base)
|
29
|
+
raise "#{label.inspect} is not a #{base}"
|
30
|
+
end
|
31
|
+
|
32
|
+
_filters[plugin] = {} unless _filters.key?(plugin)
|
33
|
+
_filters[plugin][label] = filter
|
34
|
+
end
|
35
|
+
|
36
|
+
# Provides access to filters by plugin
|
37
|
+
# :api: public
|
38
|
+
def [](plugin)
|
39
|
+
_filters[plugin]
|
40
|
+
end
|
41
|
+
|
42
|
+
# :api: private
|
43
|
+
def _filters
|
44
|
+
@filters ||= {}
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|