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,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
|