controls 1.7.0.beta.1 → 1.7.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.yardopts +1 -0
- data/lib/controls.rb +8 -2
- data/lib/controls/client.rb +4 -0
- data/lib/controls/client/coverage.rb +8 -0
- data/lib/controls/client/findings.rb +2 -1
- data/lib/controls/client/security_controls.rb +1 -1
- data/lib/controls/ext/dish/plate.rb +20 -20
- data/lib/controls/objects/assessment.rb +2 -0
- data/lib/controls/objects/asset.rb +29 -1
- data/lib/controls/objects/configuration.rb +4 -0
- data/lib/controls/objects/configuration_finding.rb +6 -0
- data/lib/controls/objects/coverage_information.rb +3 -3
- data/lib/controls/objects/error.rb +12 -0
- data/lib/controls/objects/event.rb +13 -0
- data/lib/controls/objects/product_change_event_payload.rb +2 -2
- data/lib/controls/objects/security_control_change_event_payload.rb +2 -2
- data/lib/controls/objects/site_change_event_payload.rb +2 -2
- data/lib/controls/response.rb +10 -1
- data/lib/controls/version.rb +1 -1
- data/spec/controls/client/assessments_spec.rb +3 -1
- data/spec/matchers.rb +3 -28
- metadata +5 -6
- data/spec/controls/client/events_spec.rb +0 -54
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5c68ebb70f4ca7eb870f510d30c18ab95cad9c44
|
4
|
+
data.tar.gz: 72f79ad7b6814653b8e1b2ac9e1fa75cdaa2bfdb
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7189f9747d18096cbd4eacff03f2b0ddbfaf8ce2ead46364b8406fda8778315b74a6bbe51ca779d99bdc25022382788bc1b548093ee9dded5e599a4c6f5f54da
|
7
|
+
data.tar.gz: df937e63a59fc5bed775f39ea0c58f1b369ec3218778248c893a470a844e47cc8374e69369a3ea960e857945dba50a9df8f24e9d27d65320a0c7014a419e42db
|
data/.yardopts
CHANGED
data/lib/controls.rb
CHANGED
@@ -3,8 +3,6 @@ require 'controls/default'
|
|
3
3
|
|
4
4
|
# A Ruby client for the **controls**insight API
|
5
5
|
module Controls
|
6
|
-
Error = Class.new(StandardError)
|
7
|
-
|
8
6
|
class << self
|
9
7
|
include Controls::Configurable
|
10
8
|
|
@@ -20,11 +18,19 @@ module Controls
|
|
20
18
|
@client
|
21
19
|
end
|
22
20
|
|
21
|
+
# Yields the global client to configure in a block
|
22
|
+
#
|
23
23
|
# @yield [client]
|
24
24
|
def configure
|
25
25
|
yield client
|
26
26
|
end
|
27
27
|
|
28
|
+
# Overrides the respond_to_missing method to act as a proxy for
|
29
|
+
# {Controls::Client}
|
30
|
+
#
|
31
|
+
# @param [Symbol,String] method_name the method name to check for
|
32
|
+
# @param [Boolean] include_private to include private methods when checking for method response
|
33
|
+
# @return [Boolean] whether {Controls} responds to the method
|
28
34
|
def respond_to_missing?(method_name, include_private = false)
|
29
35
|
client.respond_to?(method_name, include_private)
|
30
36
|
end
|
data/lib/controls/client.rb
CHANGED
@@ -216,6 +216,10 @@ module Controls
|
|
216
216
|
end
|
217
217
|
end
|
218
218
|
|
219
|
+
# Creates an error from the last request
|
220
|
+
#
|
221
|
+
# @param [String] message the message to prepend to the response code/status
|
222
|
+
# @return [Controls::Error] the generated error message
|
219
223
|
def exception(message = "HTTP Error")
|
220
224
|
last_request = _last_request
|
221
225
|
if last_request
|
@@ -1,6 +1,14 @@
|
|
1
1
|
module Controls
|
2
2
|
class Client
|
3
|
+
# A module to encapsulate API methods related to coverage
|
4
|
+
# @since API v1.0
|
5
|
+
# [todo] - this version is obviously off
|
6
|
+
# @version v1.0.0
|
3
7
|
module Coverage
|
8
|
+
# Either returns coverage for all security controls or one by name
|
9
|
+
#
|
10
|
+
# @param [String] security_control_name the security control to return coverage for
|
11
|
+
# @return [Array<Controls::SecurityControlCoverage>,Controls::SecurityControlCoverage]
|
4
12
|
def security_control_coverage(security_control_name = nil)
|
5
13
|
if security_control_name
|
6
14
|
get "/coverage/security_controls/#{security_control_name}"
|
@@ -2,7 +2,8 @@ module Controls
|
|
2
2
|
class Client
|
3
3
|
# A module to encapsulate API methods related to assets
|
4
4
|
# @since API v1.0
|
5
|
-
#
|
5
|
+
# [todo] - this version is obviously off
|
6
|
+
# @version v1.7.0
|
6
7
|
module Findings
|
7
8
|
# @!group Security Control/Configuration Findings Methods
|
8
9
|
|
@@ -33,7 +33,7 @@ module Controls
|
|
33
33
|
|
34
34
|
# Updates the specified security control(s)
|
35
35
|
#
|
36
|
-
# @param [Array[Hash{String=>String,Boolean}] controls a list of controls to update
|
36
|
+
# @param [Array[Hash{String=>String,Boolean}]] controls a list of controls to update
|
37
37
|
# @return [void]
|
38
38
|
def update_security_controls(controls)
|
39
39
|
# [review] - this style is a discouraged for Arrays, but we want to treat controls as an Array of Hashes
|
@@ -1,25 +1,5 @@
|
|
1
1
|
module Dish
|
2
2
|
class Plate
|
3
|
-
def method_missing(method, *args, &block)
|
4
|
-
method = method.to_s
|
5
|
-
camel_case_key = method.split('_').map(&:capitalize).join
|
6
|
-
camel_case_key[0] = camel_case_key[0].downcase
|
7
|
-
|
8
|
-
if method.end_with? '?'
|
9
|
-
key = camel_case_key[0..-2]
|
10
|
-
_check_for_presence(key)
|
11
|
-
elsif method.end_with? '='
|
12
|
-
key = camel_case_key[0..-2]
|
13
|
-
_set_value(key, args.first)
|
14
|
-
else
|
15
|
-
if @_original_hash.key?(camel_case_key)
|
16
|
-
_get_value(camel_case_key)
|
17
|
-
else
|
18
|
-
super(method.to_sym, *args, &block)
|
19
|
-
end
|
20
|
-
end
|
21
|
-
end
|
22
|
-
|
23
3
|
def methods(all = true)
|
24
4
|
valid_keys = as_hash.keys.map do |key|
|
25
5
|
key.to_s.gsub(/([^A-Z])([A-Z]+)/, '\1_\2').downcase.to_sym
|
@@ -53,5 +33,25 @@ module Dish
|
|
53
33
|
value = _convert_value(value, self.class.coercions[key])
|
54
34
|
@_original_hash[key] = value
|
55
35
|
end
|
36
|
+
|
37
|
+
def method_missing(method, *args, &block)
|
38
|
+
method = method.to_s
|
39
|
+
camel_case_key = method.split('_').map(&:capitalize).join
|
40
|
+
camel_case_key[0] = camel_case_key[0].downcase
|
41
|
+
|
42
|
+
if method.end_with? '?'
|
43
|
+
key = camel_case_key[0..-2]
|
44
|
+
_check_for_presence(key)
|
45
|
+
elsif method.end_with? '='
|
46
|
+
key = camel_case_key[0..-2]
|
47
|
+
_set_value(key, args.first)
|
48
|
+
else
|
49
|
+
if @_original_hash.key?(camel_case_key)
|
50
|
+
_get_value(camel_case_key)
|
51
|
+
else
|
52
|
+
super(method.to_sym, *args, &block)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
56
|
end
|
57
57
|
end
|
@@ -1,6 +1,7 @@
|
|
1
1
|
require 'controls/objects/security_control_finding'
|
2
2
|
|
3
3
|
module Controls
|
4
|
+
# A representation of the Asset resource
|
4
5
|
class Asset < Dish::Plate
|
5
6
|
coerce :discoveredAt, ->(value) { Time.at(value / 1000) if value }
|
6
7
|
|
@@ -8,30 +9,57 @@ module Controls
|
|
8
9
|
Controls.client.findings_by_asset_uuid(uuid)
|
9
10
|
end
|
10
11
|
|
12
|
+
# Returns the hostname, IP, and OS of the asset
|
13
|
+
#
|
14
|
+
# @example
|
15
|
+
# "jdoe.local (192.168.1.23) - Windows 7 Professional Edition"
|
16
|
+
#
|
17
|
+
# @return [String]
|
11
18
|
def to_s
|
12
19
|
%(#{host_name} (#{ipaddress}) - #{operating_system})
|
13
20
|
end
|
14
21
|
end
|
15
22
|
|
23
|
+
# A collection of Asset resources
|
16
24
|
class AssetCollection < Dish::Plate
|
17
25
|
coerce :resources, Asset
|
18
26
|
|
19
|
-
|
27
|
+
# [todo] - metaprogram any proxy methods?
|
28
|
+
# - be sure to define method_missing AND respond_to_missing?
|
29
|
+
|
30
|
+
# Acts as a proxy to resources.map
|
31
|
+
#
|
32
|
+
# @yield [resource] gives three resources
|
33
|
+
def map(&block)
|
20
34
|
resources.map(*args, &block)
|
21
35
|
end
|
22
36
|
|
37
|
+
# Acts as a proxy to resources.first
|
38
|
+
#
|
39
|
+
# @return [Controls::Asset]
|
23
40
|
def first
|
24
41
|
resources.first
|
25
42
|
end
|
26
43
|
|
44
|
+
# Acts as a proxy to resources.last
|
45
|
+
#
|
46
|
+
# @return [Controls::Asset] the last asset in the
|
47
|
+
# {Controls::AssetCollection}
|
27
48
|
def last
|
28
49
|
resources.last
|
29
50
|
end
|
30
51
|
|
52
|
+
# Acts as a proxy to resources.[]
|
53
|
+
#
|
54
|
+
# @param [Fixnum] index the index of the asset to fetch
|
55
|
+
# @return [Controls::Asset] the asset by index
|
31
56
|
def [](index)
|
32
57
|
resources[index]
|
33
58
|
end
|
34
59
|
|
60
|
+
# Returns a comma separated list of IP addresses
|
61
|
+
#
|
62
|
+
# @return [String]
|
35
63
|
def to_s
|
36
64
|
resources.sort_by(&:ipaddress).map(&:to_s).join("\n")
|
37
65
|
end
|
@@ -1,10 +1,14 @@
|
|
1
1
|
require 'controls/objects/coverage_information'
|
2
2
|
|
3
3
|
module Controls
|
4
|
+
# A representation of the Configuration resource w/ coverage information
|
4
5
|
class Configuration < Dish::Plate
|
5
6
|
coerce :assessmentTimestamp, ->(value) { Time.at(value / 1000) if value }
|
6
7
|
coerce :coverage, Controls::CoverageInformation
|
7
8
|
|
9
|
+
# The title of the configuration
|
10
|
+
#
|
11
|
+
# @return [String]
|
8
12
|
def to_s
|
9
13
|
title
|
10
14
|
end
|
@@ -1,5 +1,11 @@
|
|
1
1
|
module Controls
|
2
|
+
# A representation of the ConfigurationFinding resource
|
2
3
|
class ConfigurationFinding < Dish::Plate
|
4
|
+
coerce :reason, ->(value) { value.strip! }
|
5
|
+
|
6
|
+
# Returns the name, state, and reason data for the configuration finding
|
7
|
+
#
|
8
|
+
# "antivirus-installed: state: TRUE reason: Endpoint Security installed"
|
3
9
|
def to_s
|
4
10
|
"#{name}: state: #{state} reason: #{reason.strip}"
|
5
11
|
end
|
@@ -1,7 +1,7 @@
|
|
1
1
|
module Controls
|
2
|
+
# A representation of the CoverageInformation for SecurityControl or
|
3
|
+
# Configuration coverage
|
2
4
|
class CoverageInformation < Dish::Plate
|
3
|
-
|
4
|
-
"#<#{self.class}: total: #{total}, covered: #{covered}, uncovered: #{uncovered}, percent_covered: #{percent_covered}>"
|
5
|
-
end
|
5
|
+
alias_method :to_s, :inspect
|
6
6
|
end
|
7
7
|
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
module Controls
|
2
|
+
# A class under the Controls namespace to wrap API errors
|
3
|
+
class Error < StandardError
|
4
|
+
# @!attribute message
|
5
|
+
# The message related to the error
|
6
|
+
attr_accessor :message
|
7
|
+
|
8
|
+
# @!attribute message
|
9
|
+
# The status code for the error response
|
10
|
+
attr_accessor :status
|
11
|
+
end
|
12
|
+
end
|
@@ -1,16 +1,29 @@
|
|
1
1
|
module Controls
|
2
|
+
# A representation of the Event resource
|
2
3
|
class Event < Dish::Plate
|
3
4
|
coerce :createdAt, ->(value) { Time.at(value / 1000) if value }
|
4
5
|
|
6
|
+
# Coerces the payload into the appropriate type
|
7
|
+
#
|
8
|
+
# @return [Controls::SecurityControlChangeEventPayload,Controls::SiteChangeEventPayload,Controls::ProductEventPayload]
|
9
|
+
# the payload respective of the event type
|
5
10
|
def payload
|
6
11
|
value = _get_value('payload')
|
7
12
|
Dish(value, Controls.const_get("#{type}Payload"))
|
8
13
|
end
|
9
14
|
|
15
|
+
# Overrides #inspect to use the proper event type
|
16
|
+
#
|
17
|
+
# @return [String] the result of super with the corrected event type
|
10
18
|
def inspect
|
11
19
|
super.sub('Event', type)
|
12
20
|
end
|
13
21
|
|
22
|
+
# Returns the event type
|
23
|
+
#
|
24
|
+
# [todo] - is the type all we want to return?
|
25
|
+
#
|
26
|
+
# @return [string] the event type
|
14
27
|
def to_s
|
15
28
|
type
|
16
29
|
end
|
data/lib/controls/response.rb
CHANGED
@@ -3,11 +3,20 @@ require 'controls/objects'
|
|
3
3
|
module Controls
|
4
4
|
# A module to encapsulate middleware customization
|
5
5
|
module Response
|
6
|
-
|
6
|
+
module_function
|
7
|
+
|
8
|
+
# Returns the parsed JSON response after marshaling through Dish
|
9
|
+
#
|
10
|
+
# @param [String] obj the JSON object to parse
|
11
|
+
# @param [String] path the requested API endpoint's path
|
12
|
+
# @return [Dish::Plate,Object] a marshaled representation of the JSON response
|
13
|
+
def parse(obj, path = nil)
|
7
14
|
hash_or_array = JSON.parse(obj)
|
8
15
|
|
9
16
|
if hash_or_array.is_a?(Hash) && hash_or_array.key?('message') && hash_or_array.key?('documentationUrl')
|
10
17
|
type = Controls::Error
|
18
|
+
elsif hash_or_array.is_a?(Hash) && hash_or_array.key?('message') && hash_or_array.key?('status')
|
19
|
+
type = Controls::Error
|
11
20
|
end
|
12
21
|
|
13
22
|
type ||=
|
data/lib/controls/version.rb
CHANGED
@@ -7,7 +7,9 @@ describe '/api/assessments' do
|
|
7
7
|
it 'returns a list of assessments' do
|
8
8
|
assessments = Controls.assessments
|
9
9
|
|
10
|
-
|
10
|
+
assessments.each do |assessment|
|
11
|
+
expect(assessment).to match_assessment_format
|
12
|
+
end
|
11
13
|
end
|
12
14
|
end
|
13
15
|
|
data/spec/matchers.rb
CHANGED
@@ -4,43 +4,18 @@ require 'rspec/expectations'
|
|
4
4
|
RSpec::Matchers.define :match_assessment_format do
|
5
5
|
match do |resource|
|
6
6
|
# Reverses the coercion
|
7
|
-
resource.timestamp = resource.timestamp.to_i
|
8
7
|
[
|
9
8
|
resource.high_risk_asset_count,
|
10
9
|
resource.id,
|
11
10
|
resource.low_risk_asset_count,
|
12
11
|
resource.medium_risk_asset_count,
|
13
|
-
resource.timestamp,
|
12
|
+
resource.timestamp.to_i,
|
14
13
|
resource.total_asset_count
|
15
14
|
].each do |attribute|
|
16
15
|
expect(attribute.class).to eq(Fixnum)
|
17
16
|
end
|
18
17
|
|
19
|
-
expect(resource.assessing.class).to
|
20
|
-
expect(resource.overall_risk_score.class).to
|
18
|
+
expect([TrueClass, FalseClass].include?(resource.assessing.class)).to be_true
|
19
|
+
expect([Float].include?(resource.overall_risk_score.class)).to be_true
|
21
20
|
end
|
22
21
|
end
|
23
|
-
|
24
|
-
#RSpec::Matchers.define :match_event_format do
|
25
|
-
# [Fixnum].include? resource.createdAt
|
26
|
-
# [Hash].include? resource.payload
|
27
|
-
# [String].include? resource.type
|
28
|
-
# [String].include? resource.user
|
29
|
-
#end
|
30
|
-
#
|
31
|
-
#RSpec::Matchers.define :match_site_change_event_payload_format do
|
32
|
-
# [TrueClass,FalseClass].include? resource.impactsGrade.class
|
33
|
-
# [String].include? resource.notes.class
|
34
|
-
# [String].include? resource.productVersion.class
|
35
|
-
#end
|
36
|
-
#
|
37
|
-
#RSpec::Matchers.define :match_security_control_change_event_payload_format do
|
38
|
-
# [String,NilClass].include?resource.reason
|
39
|
-
# [String]}]].include?resource.changes:[Array,[Hash,{securityControlName:[String],action
|
40
|
-
#end
|
41
|
-
#
|
42
|
-
#RSpec::Matchers.define :match__change_event_payload_format do
|
43
|
-
# [String,NilClass].include?resource.reason
|
44
|
-
# [Fixnum]}]].include?resource.enabledSites:[Array,[Hash,{name:[String],id
|
45
|
-
# [TrueClass.include?resource.importAllFalseClass]
|
46
|
-
#end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: controls
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.7.0
|
4
|
+
version: 1.7.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Erran Carey
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-03-
|
11
|
+
date: 2014-03-18 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: dish
|
@@ -121,6 +121,7 @@ files:
|
|
121
121
|
- lib/controls/objects/configuration.rb
|
122
122
|
- lib/controls/objects/configuration_finding.rb
|
123
123
|
- lib/controls/objects/coverage_information.rb
|
124
|
+
- lib/controls/objects/error.rb
|
124
125
|
- lib/controls/objects/event.rb
|
125
126
|
- lib/controls/objects/guidance.rb
|
126
127
|
- lib/controls/objects/product_change_event_payload.rb
|
@@ -135,7 +136,6 @@ files:
|
|
135
136
|
- lib/controls/response.rb
|
136
137
|
- lib/controls/version.rb
|
137
138
|
- spec/controls/client/assessments_spec.rb
|
138
|
-
- spec/controls/client/events_spec.rb
|
139
139
|
- spec/matchers.rb
|
140
140
|
- spec/spec_helper.rb
|
141
141
|
homepage: ''
|
@@ -153,9 +153,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
153
153
|
version: '0'
|
154
154
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
155
155
|
requirements:
|
156
|
-
- - "
|
156
|
+
- - ">="
|
157
157
|
- !ruby/object:Gem::Version
|
158
|
-
version:
|
158
|
+
version: '0'
|
159
159
|
requirements: []
|
160
160
|
rubyforge_project:
|
161
161
|
rubygems_version: 2.2.0
|
@@ -164,7 +164,6 @@ specification_version: 4
|
|
164
164
|
summary: This gem interfaces to Rapid7's **controls**insight API.
|
165
165
|
test_files:
|
166
166
|
- spec/controls/client/assessments_spec.rb
|
167
|
-
- spec/controls/client/events_spec.rb
|
168
167
|
- spec/matchers.rb
|
169
168
|
- spec/spec_helper.rb
|
170
169
|
has_rdoc:
|
@@ -1,54 +0,0 @@
|
|
1
|
-
#describe '/api/events' do
|
2
|
-
# before do
|
3
|
-
# login_to_environment
|
4
|
-
# end
|
5
|
-
#
|
6
|
-
# context 'GET /api/events' do
|
7
|
-
# it 'returns a list of events' do
|
8
|
-
# events = Controls.events
|
9
|
-
#
|
10
|
-
# expect(events).to match_format(event_format)
|
11
|
-
# end
|
12
|
-
# end
|
13
|
-
#
|
14
|
-
# context 'GET /api/events?filter=ProductChangeEvent' do
|
15
|
-
# it 'returns a list of events' do
|
16
|
-
# events = Controls.events filter: 'ProductChangeEvent'
|
17
|
-
#
|
18
|
-
# expect(events).to match_format(event_format)
|
19
|
-
# expect(events.map(&:payload)).to match_format(product_change_format)
|
20
|
-
# end
|
21
|
-
# end
|
22
|
-
#
|
23
|
-
# context 'GET /api/events?filter=SecurityControlChangeEvent' do
|
24
|
-
# it 'returns a list of events' do
|
25
|
-
# events = Controls.events filter: 'SecurityControlChangeEvent'
|
26
|
-
# expected_payload_format = security_control_change_format
|
27
|
-
#
|
28
|
-
# expect(events).to match_format(event_format)
|
29
|
-
# expect(events.map(&:payload)).to match_format(expected_payload_format)
|
30
|
-
#
|
31
|
-
# events.map(&:payload).each do |payload|
|
32
|
-
# payload.keys.map(&:to_sym).each do |key|
|
33
|
-
# expect(payload.send(key)).to match_format(expected_payload_format[key].last)
|
34
|
-
# end
|
35
|
-
# end
|
36
|
-
# end
|
37
|
-
# end
|
38
|
-
#
|
39
|
-
# context 'GET /api/events?filter=SiteChangeEvent' do
|
40
|
-
# it 'returns a list of events' do
|
41
|
-
# events = Controls.events filter: 'SiteChangeEvent'
|
42
|
-
# expected_payload_format = site_change_format
|
43
|
-
#
|
44
|
-
# expect(events).to match_format(event_format)
|
45
|
-
# expect(events.map(&:payload)).to match_format(expected_payload_format)
|
46
|
-
#
|
47
|
-
# events.map(&:payload).each do |payload|
|
48
|
-
# payload.keys.map(&:to_sym).each do |key|
|
49
|
-
# expect(payload.send(key)).to match_format(expected_payload_format[key].last)
|
50
|
-
# end
|
51
|
-
# end
|
52
|
-
# end
|
53
|
-
# end
|
54
|
-
#end
|