controls 1.7.6 → 1.7.7
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 +4 -4
- data/.travis.yml +1 -4
- data/README.md +3 -3
- data/apiary.apib +1 -2
- data/lib/controls/client.rb +3 -2
- data/lib/controls/client/configurations.rb +0 -11
- data/lib/controls/client/coverage.rb +15 -1
- data/lib/controls/configurable.rb +2 -1
- data/lib/controls/objects.rb +6 -4
- data/lib/controls/objects/asset.rb +5 -48
- data/lib/controls/objects/asset_collection.rb +48 -0
- data/lib/controls/objects/configuration.rb +8 -1
- data/lib/controls/objects/configuration_coverage.rb +42 -0
- data/lib/controls/objects/coverage_information.rb +8 -1
- data/lib/controls/objects/error.rb +26 -12
- data/lib/controls/objects/guidance.rb +9 -11
- data/lib/controls/objects/guidance/reference.rb +17 -0
- data/lib/controls/objects/prioritized_guidance.rb +8 -0
- data/lib/controls/objects/security_control.rb +3 -0
- data/lib/controls/objects/security_control_coverage.rb +20 -2
- data/lib/controls/objects/security_control_finding.rb +3 -0
- data/lib/controls/objects/threat.rb +2 -0
- data/lib/controls/objects/threat_vector.rb +2 -0
- data/lib/controls/objects/trend.rb +2 -0
- data/lib/controls/response.rb +10 -3
- data/lib/controls/version.rb +1 -1
- data/spec/controls/client/assessments_spec.rb +2 -0
- data/spec/controls/client/assets_spec.rb +47 -6
- metadata +6 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3e6a9bd17f0766f610281ebc13747a1ef6ee630c
|
4
|
+
data.tar.gz: 3474dd374d83875e8e17063db12ebdadfcb719fe
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9a6c8af6d7629a5d7260058f3e79b6cdb2410a9d5037161e58633596972d6eceda82c5b2648c3586c6a6e98aa200c109049f80ea8c751af23b6ba3f44a4cbe23
|
7
|
+
data.tar.gz: 161e19f2e28c9c7cfae24fed1c0bdf94d103911dedbba878887d2868fa738e00372101d449e46d0b81901b3c1a857a62667cd9c6d9d3adab9b14524bf24799dc
|
data/.travis.yml
CHANGED
@@ -2,7 +2,4 @@ language: ruby
|
|
2
2
|
rvm:
|
3
3
|
- 2.1.0
|
4
4
|
env:
|
5
|
-
- CONTROLS_API_ENDPOINT="https://controlsinsight.apiary.io/insight/controls/api"
|
6
|
-
- CONTROLS_WEB_ENDPOINT="https://controlsinsight.apiary.io/insight/controls"
|
7
|
-
- CONTROLS_USERNAME="johndoe"
|
8
|
-
- CONTROLS_PASSWORD="Password123!"
|
5
|
+
- CONTROLS_API_ENDPOINT="https://controlsinsight.apiary.io/insight/controls/api" CONTROLS_WEB_ENDPOINT="https://controlsinsight.apiary.io/insight/controls" CONTROLS_USERNAME="johndoe" CONTROLS_PASSWORD="Password123!"
|
data/README.md
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
# controlsinsight client gem
|
2
2
|
[](https://travis-ci.org/erran/controls.rb)
|
3
3
|
|
4
|
-
The
|
4
|
+
The controlsinsight (controls) gem interfaces with [Rapid7's controlsinsight API](http://rapid7.github.io/controlsinsight.rb).
|
5
5
|
|
6
6
|
## Installation
|
7
7
|
Add this line to your application's Gemfile:
|
@@ -33,7 +33,7 @@ Controls.api_endpoint = "#{Controls.web_endpoint}/api/1.0"
|
|
33
33
|
# If your endpoint uses a self-signed cert. turn off SSL cert. verification
|
34
34
|
Controls.verify_ssl = false
|
35
35
|
|
36
|
-
Controls.login
|
36
|
+
Controls.login(username: 'admin', password: 'password')
|
37
37
|
|
38
38
|
Controls.client.api_methods
|
39
39
|
# => [:applicable_assets, :assessments, :asset_search, :assets, :assets_by_configuration, :assets_by_guidance, ..., :uncovered_assets, :undefended_assets, :update_security_controls]
|
@@ -163,7 +163,7 @@ Controls.threat_vectors('network-borne')
|
|
163
163
|
|
164
164
|
### Trends
|
165
165
|
```ruby
|
166
|
-
# Retrieve a
|
166
|
+
# Retrieve a list of trend points over time
|
167
167
|
Controls.threat_trends('overall-malware')
|
168
168
|
# => [#<Controls::Trend: grade: 1.1723226070935302, assessment_timestamp: 2013-12-15 10:07:39 -0600, total_assets: 18>,
|
169
169
|
# #<Controls::Trend: grade: 3.2684235618866317, assessment_timestamp: 2014-02-06 17:58:06 -0600, total_assets: 42>]
|
data/apiary.apib
CHANGED
@@ -2,7 +2,6 @@ FORMAT: 1A
|
|
2
2
|
HOST: https://nexpose.local:3780/insight/controls/api/
|
3
3
|
|
4
4
|
# ControlsInsight
|
5
|
-
Notes API is a *short texts saving* service similar to its physical paper presence on your table.
|
6
5
|
|
7
6
|
# Group Assessments
|
8
7
|
## Assessment Collection [/assessments]
|
@@ -59,4 +58,4 @@ Notes API is a *short texts saving* service similar to its physical paper presen
|
|
59
58
|
"hostName": "CMMNCTR2K7R2-U",
|
60
59
|
"ipaddress": "10.4.19.25"
|
61
60
|
}
|
62
|
-
}
|
61
|
+
}
|
data/lib/controls/client.rb
CHANGED
@@ -97,6 +97,7 @@ module Controls
|
|
97
97
|
def get(path, params = {}, headers = {})
|
98
98
|
headers = connection_options[:headers].merge(headers)
|
99
99
|
url = URI.escape(File.join(api_endpoint, path))
|
100
|
+
puts url, params, headers
|
100
101
|
resp = middleware.get(url, params, headers)
|
101
102
|
@_last_request = {
|
102
103
|
response: resp,
|
@@ -107,7 +108,7 @@ module Controls
|
|
107
108
|
fail exception('Invalid content-type error')
|
108
109
|
end
|
109
110
|
|
110
|
-
Response.parse(resp.body, path)
|
111
|
+
Response.parse(resp.body, resp.status, path)
|
111
112
|
rescue Faraday::Error::ConnectionFailed => e
|
112
113
|
if e.message =~ /^SSL_connect/
|
113
114
|
warn(*SSL_WARNING)
|
@@ -135,7 +136,7 @@ module Controls
|
|
135
136
|
|
136
137
|
return resp.status if resp.status == 200
|
137
138
|
|
138
|
-
Response.parse(resp.body, path)
|
139
|
+
Response.parse(resp.body, resp.status, path)
|
139
140
|
rescue Faraday::Error::ConnectionFailed => e
|
140
141
|
if e.message =~ /^SSL_connect/
|
141
142
|
warn(*SSL_WARNING)
|
@@ -7,17 +7,6 @@ module Controls
|
|
7
7
|
module Configurations
|
8
8
|
# @!group Configuration Methods
|
9
9
|
|
10
|
-
# @param [String] configuration the name of the configuration to search
|
11
|
-
# for
|
12
|
-
# @return [Array<Hash>] a list of hashes representing configurations
|
13
|
-
def configurations(configuration = nil)
|
14
|
-
if configuration
|
15
|
-
get "/configurations/#{configuration}"
|
16
|
-
else
|
17
|
-
get "/configurations"
|
18
|
-
end
|
19
|
-
end
|
20
|
-
|
21
10
|
# @param [String] control the security control look up configurations for
|
22
11
|
# @return [Array<Hash>] a list of hashes representing configurations
|
23
12
|
def security_control_configurations(control)
|
@@ -7,7 +7,8 @@ module Controls
|
|
7
7
|
module Coverage
|
8
8
|
# Either returns coverage for all security controls or one by name
|
9
9
|
#
|
10
|
-
# @param [String] security_control_name the security control to return
|
10
|
+
# @param [String] security_control_name the security control to return
|
11
|
+
# coverage for
|
11
12
|
# @return [Array<Controls::SecurityControlCoverage>,Controls::SecurityControlCoverage]
|
12
13
|
def security_control_coverage(security_control_name = nil)
|
13
14
|
if security_control_name
|
@@ -16,6 +17,19 @@ module Controls
|
|
16
17
|
get '/coverage/security_controls'
|
17
18
|
end
|
18
19
|
end
|
20
|
+
|
21
|
+
# Either returns coverage for all configurations or one by name
|
22
|
+
#
|
23
|
+
# @param [String] configuration_name the security control to return
|
24
|
+
# coverage for
|
25
|
+
# @return [Array<Controls::ConfigurationCoverage>,Controls::ConfigurationCoverage]
|
26
|
+
def configuration_coverage(configuration_name = nil)
|
27
|
+
if security_control_name
|
28
|
+
get "/coverage/configurations/#{configuration_name}"
|
29
|
+
else
|
30
|
+
get '/coverage/configurations'
|
31
|
+
end
|
32
|
+
end
|
19
33
|
end
|
20
34
|
end
|
21
35
|
end
|
data/lib/controls/objects.rb
CHANGED
@@ -1,16 +1,18 @@
|
|
1
1
|
require 'dish'
|
2
2
|
require 'controls/ext/dish/plate'
|
3
3
|
require 'controls/objects/assessment'
|
4
|
+
require 'controls/objects/asset_collection'
|
4
5
|
require 'controls/objects/asset'
|
6
|
+
require 'controls/objects/configuration'
|
5
7
|
require 'controls/objects/error'
|
6
8
|
require 'controls/objects/event'
|
7
|
-
require 'controls/objects/product_change_event_payload'
|
8
|
-
require 'controls/objects/configuration'
|
9
9
|
require 'controls/objects/guidance'
|
10
|
-
require 'controls/objects/
|
10
|
+
require 'controls/objects/prioritized_guidance'
|
11
|
+
require 'controls/objects/product_change_event_payload'
|
11
12
|
require 'controls/objects/security_control_change_event_payload'
|
12
13
|
require 'controls/objects/security_control_coverage'
|
14
|
+
require 'controls/objects/security_control'
|
13
15
|
require 'controls/objects/site_change_event_payload'
|
14
|
-
require 'controls/objects/threat'
|
15
16
|
require 'controls/objects/threat_vector'
|
17
|
+
require 'controls/objects/threat'
|
16
18
|
require 'controls/objects/trend'
|
@@ -1,12 +1,14 @@
|
|
1
|
-
require 'controls/objects/security_control_finding'
|
2
|
-
|
3
1
|
module Controls
|
4
2
|
# A representation of the Asset resource
|
5
3
|
class Asset < Dish::Plate
|
6
4
|
coerce :discoveredAt, ->(value) { Time.at(value / 1000) if value }
|
7
5
|
|
6
|
+
# Retreives the security control and configuration findings for this
|
7
|
+
# {Asset} instance by UUID
|
8
|
+
#
|
9
|
+
# @return [Array<SecurityControlFindings>]
|
8
10
|
def findings
|
9
|
-
Controls.client.findings_by_asset_uuid(uuid)
|
11
|
+
@findings ||= Controls.client.findings_by_asset_uuid(uuid)
|
10
12
|
end
|
11
13
|
|
12
14
|
# Returns the hostname, IP, and OS of the asset
|
@@ -19,49 +21,4 @@ module Controls
|
|
19
21
|
%(#{host_name} (#{ipaddress}) - #{operating_system})
|
20
22
|
end
|
21
23
|
end
|
22
|
-
|
23
|
-
# A collection of Asset resources
|
24
|
-
class AssetCollection < Dish::Plate
|
25
|
-
coerce :resources, Asset
|
26
|
-
|
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)
|
34
|
-
resources.map(*args, &block)
|
35
|
-
end
|
36
|
-
|
37
|
-
# Acts as a proxy to resources.first
|
38
|
-
#
|
39
|
-
# @return [Controls::Asset]
|
40
|
-
def first
|
41
|
-
resources.first
|
42
|
-
end
|
43
|
-
|
44
|
-
# Acts as a proxy to resources.last
|
45
|
-
#
|
46
|
-
# @return [Controls::Asset] the last asset in the
|
47
|
-
# {Controls::AssetCollection}
|
48
|
-
def last
|
49
|
-
resources.last
|
50
|
-
end
|
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
|
56
|
-
def [](index)
|
57
|
-
resources[index]
|
58
|
-
end
|
59
|
-
|
60
|
-
# Returns a comma separated list of IP addresses
|
61
|
-
#
|
62
|
-
# @return [String]
|
63
|
-
def to_s
|
64
|
-
resources.sort_by(&:ipaddress).map(&:to_s).join("\n")
|
65
|
-
end
|
66
|
-
end
|
67
24
|
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'controls/objects/asset'
|
2
|
+
|
3
|
+
module Controls
|
4
|
+
# A collection of Asset resources
|
5
|
+
class AssetCollection < Dish::Plate
|
6
|
+
coerce :resources, Asset
|
7
|
+
|
8
|
+
# [todo] - metaprogram any proxy methods?
|
9
|
+
# - be sure to define method_missing AND respond_to_missing?
|
10
|
+
|
11
|
+
# Acts as a proxy to resources.map
|
12
|
+
#
|
13
|
+
# @yield [resource] gives three resources
|
14
|
+
def map(&block)
|
15
|
+
resources.map(*args, &block)
|
16
|
+
end
|
17
|
+
|
18
|
+
# Acts as a proxy to resources.first
|
19
|
+
#
|
20
|
+
# @return [Controls::Asset]
|
21
|
+
def first
|
22
|
+
resources.first
|
23
|
+
end
|
24
|
+
|
25
|
+
# Acts as a proxy to resources.last
|
26
|
+
#
|
27
|
+
# @return [Controls::Asset] the last asset in the
|
28
|
+
# {Controls::AssetCollection}
|
29
|
+
def last
|
30
|
+
resources.last
|
31
|
+
end
|
32
|
+
|
33
|
+
# Acts as a proxy to resources.[]
|
34
|
+
#
|
35
|
+
# @param [Fixnum] index the index of the asset to fetch
|
36
|
+
# @return [Controls::Asset] the asset by index
|
37
|
+
def [](index)
|
38
|
+
resources[index]
|
39
|
+
end
|
40
|
+
|
41
|
+
# Returns a comma separated list of IP addresses
|
42
|
+
#
|
43
|
+
# @return [String]
|
44
|
+
def to_s
|
45
|
+
resources.sort_by(&:ipaddress).map(&:to_s).join("\n")
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -8,12 +8,19 @@ module Controls
|
|
8
8
|
coerce :assessmentTimestamp, ->(value) { Time.at(value / 1000) if value }
|
9
9
|
coerce :coverage, Controls::CoverageInformation
|
10
10
|
|
11
|
+
# Allows for comparison with other objects with coverage information
|
12
|
+
#
|
13
|
+
# @return [Fixnum] returns one of the following based on the percent of
|
14
|
+
# assets that are covered -1 (less than `other`), 0 (equal to `other`),
|
15
|
+
# or 1 (greater than `other`)
|
11
16
|
def <=>(other)
|
12
17
|
return unless other.respond_to? :coverage
|
13
18
|
coverage.percent_covered <=> other.coverage.percent_covered
|
14
19
|
end
|
15
20
|
|
16
|
-
# [review] -
|
21
|
+
# [review] - shouldn't this be covered by the Dish coercion?
|
22
|
+
# @return [Boolean] true if the method is :coverage otherwise calls
|
23
|
+
# `Dish::Plate#method_missing`
|
17
24
|
def respond_to?(method_name, *)
|
18
25
|
if method_name.eql? :coverage
|
19
26
|
true
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'controls/objects/coverage_information'
|
2
|
+
|
3
|
+
module Controls
|
4
|
+
# A representation of the Configuration resource with coverage information
|
5
|
+
class ConfigurationCoverage < Dish::Plate
|
6
|
+
include Comparable
|
7
|
+
|
8
|
+
coerce :assessmentTimestamp, ->(value) { Time.at(value / 1000) if value }
|
9
|
+
coerce :coverage, Controls::CoverageInformation
|
10
|
+
|
11
|
+
def <=>(other)
|
12
|
+
return unless other.respond_to? :coverage
|
13
|
+
coverage.percent_covered <=> other.coverage.percent_covered
|
14
|
+
end
|
15
|
+
|
16
|
+
# [review] - define this in Dish?
|
17
|
+
def respond_to?(method_name, *)
|
18
|
+
if method_name.eql? :coverage
|
19
|
+
true
|
20
|
+
else
|
21
|
+
super
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def without_coverage
|
26
|
+
Controls::Configuration.new(enabled: enabled, name: name)
|
27
|
+
end
|
28
|
+
|
29
|
+
def without_coverage!
|
30
|
+
@_original_hash.delete_if do |key, _value|
|
31
|
+
not %w[enabled name].include?(key)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
# The title of the configuration
|
36
|
+
#
|
37
|
+
# @return [String]
|
38
|
+
def to_s
|
39
|
+
title
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -4,12 +4,19 @@ module Controls
|
|
4
4
|
class CoverageInformation < Dish::Plate
|
5
5
|
include Comparable
|
6
6
|
|
7
|
+
# Allows for sorting of objects that have :coverage ({CoverageInformation})
|
8
|
+
#
|
9
|
+
# @return [Fixnum] returns one of the following based on the percent of
|
10
|
+
# assets that are covered -1 (less than `other`), 0 (equal to `other`),
|
11
|
+
# or 1 (greater than `other`)
|
7
12
|
def <=>(other)
|
8
13
|
return unless other.respond_to? :percent_covered
|
9
14
|
percent_covered <=> other.percent_covered
|
10
15
|
end
|
11
16
|
|
12
|
-
# [review] -
|
17
|
+
# [review] - shouldn't this be covered by the Dish coercion?
|
18
|
+
# @return [Boolean] true if the method is :percent_covered otherwise calls
|
19
|
+
# `Dish::Plate#method_missing`
|
13
20
|
def respond_to?(method_name, *)
|
14
21
|
if method_name.eql? :percent_covered
|
15
22
|
true
|
@@ -3,14 +3,17 @@ module Controls
|
|
3
3
|
#
|
4
4
|
# [review] - subclass Dish::Plate instead of StandardError?
|
5
5
|
class Error < StandardError
|
6
|
-
# @!attribute message
|
7
|
-
#
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
6
|
+
# @!attribute [rw] message
|
7
|
+
# @return [String] the message related to the error
|
8
|
+
# @!attribute [rw] status
|
9
|
+
# @return [String] the status code for the error response
|
10
|
+
attr_accessor :message, :status
|
11
|
+
|
12
|
+
# @param [Hash] attributes the key/value pairs to set instance variables
|
13
|
+
# with
|
14
|
+
# @option :message [String] the error's associated message
|
15
|
+
# @option :status [String] the error's associated status code
|
16
|
+
# @return [self] the {Controls::Error} with the given attributes
|
14
17
|
def initialize(attributes = {})
|
15
18
|
@__attributes__ = attributes
|
16
19
|
@__attributes__.each do |attribute, value|
|
@@ -18,10 +21,8 @@ module Controls
|
|
18
21
|
end
|
19
22
|
end
|
20
23
|
|
21
|
-
|
22
|
-
|
23
|
-
end
|
24
|
-
|
24
|
+
# @return [String] a string representing the error and all of it's
|
25
|
+
# attributes
|
25
26
|
def inspect
|
26
27
|
vars = to_h.map do |attribute, value|
|
27
28
|
"#{attribute}: #{value}"
|
@@ -30,12 +31,25 @@ module Controls
|
|
30
31
|
"#<#{self.class}: #{vars.join(', ')}>"
|
31
32
|
end
|
32
33
|
|
34
|
+
# @return [String] the JSON representation of the attributes
|
35
|
+
def to_json
|
36
|
+
@__attributes__.to_json
|
37
|
+
end
|
38
|
+
|
39
|
+
# @return [Hash] the attributes used to initialize this error
|
40
|
+
def to_h
|
41
|
+
@__attributes__
|
42
|
+
end
|
43
|
+
|
44
|
+
# @return [String] the error message if available otherwise calls {#inspect}
|
33
45
|
def to_s
|
34
46
|
@message or inspect
|
35
47
|
end
|
36
48
|
|
37
49
|
private
|
38
50
|
|
51
|
+
# @!attribute [r] message
|
52
|
+
# @return [String] the message related to the error
|
39
53
|
attr_reader :__attributes__
|
40
54
|
end
|
41
55
|
end
|
@@ -1,22 +1,20 @@
|
|
1
1
|
module Controls
|
2
|
-
|
3
|
-
|
4
|
-
Guidance
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
2
|
+
# An object to represent the guidance resource returned by the
|
3
|
+
# ControlsInsight API
|
4
|
+
class Guidance < Dish::Plate
|
5
|
+
# [review] - Is there another way to ensure a proper subclass setup?
|
6
|
+
require 'controls/objects/guidance/reference'
|
7
|
+
|
8
|
+
# A {Controls::Guidance} instance includes HTML markup in a sections list
|
9
|
+
Section = Class.new(Dish::Plate)
|
10
10
|
|
11
|
-
class Guidance
|
12
11
|
coerce :assessmentTimestamp, ->(value) { Time.at(value / 1000) if value }
|
13
12
|
coerce :references, Reference
|
14
13
|
coerce :sections, Section
|
15
14
|
|
15
|
+
# @return [String] the title of the guidance
|
16
16
|
def to_s
|
17
17
|
title
|
18
18
|
end
|
19
19
|
end
|
20
|
-
|
21
|
-
PrioritizedGuidance = Class.new(Guidance)
|
22
20
|
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'controls/objects/guidance'
|
2
|
+
|
3
|
+
module Controls
|
4
|
+
class Guidance
|
5
|
+
# A object that represents the reference resource of a {Controls::Guidance}
|
6
|
+
class Reference < Dish::Plate
|
7
|
+
private
|
8
|
+
|
9
|
+
# Ensures that calling url wouldn't raise a KeyError when {nil}
|
10
|
+
#
|
11
|
+
# @return [Array] the keys that are allowed to be {nil}
|
12
|
+
def _allowed_keys
|
13
|
+
%w[url]
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -1,19 +1,26 @@
|
|
1
1
|
require 'controls/objects/coverage_information'
|
2
2
|
|
3
3
|
module Controls
|
4
|
+
# A representation of the SecuritControl resource with coverage information
|
4
5
|
class SecurityControlCoverage < Dish::Plate
|
5
6
|
include Comparable
|
6
7
|
|
7
|
-
|
8
8
|
coerce :assessmentTimestamp, ->(value) { Time.at(value / 1000) if value }
|
9
9
|
coerce :coverage, Controls::CoverageInformation
|
10
10
|
|
11
|
+
# Allows for comparison with other objects with coverage information
|
12
|
+
#
|
13
|
+
# @return [Fixnum] returns one of the following based on the percent of
|
14
|
+
# assets that are covered -1 (less than `other`), 0 (equal to `other`),
|
15
|
+
# or 1 (greater than `other`)
|
11
16
|
def <=>(other)
|
12
17
|
return unless other.respond_to? :coverage
|
13
18
|
coverage.percent_covered <=> other.coverage.percent_covered
|
14
19
|
end
|
15
20
|
|
16
|
-
# [review] -
|
21
|
+
# [review] - shouldn't this be covered by the Dish coercion?
|
22
|
+
# @return [Boolean] true if the method is :coverage otherwise calls
|
23
|
+
# `Dish::Plate#method_missing`
|
17
24
|
def respond_to?(method_name, *)
|
18
25
|
if method_name.eql? :coverage
|
19
26
|
true
|
@@ -22,16 +29,27 @@ module Controls
|
|
22
29
|
end
|
23
30
|
end
|
24
31
|
|
32
|
+
# Converts the object into a {Controls::SecurityControl} by name and
|
33
|
+
# whether it is enabled/disabled
|
34
|
+
#
|
35
|
+
# @return {Controls::SecurityControl}
|
25
36
|
def without_coverage
|
26
37
|
Controls::SecurityControl.new(enabled: enabled, name: name)
|
27
38
|
end
|
28
39
|
|
40
|
+
# Removes the coverage from the {SecurityControlCoverage} object, making it
|
41
|
+
# equivalent to a {SecurityControl} in terms of duck-typing
|
42
|
+
#
|
43
|
+
# @return {Controls::SecurityControl}
|
29
44
|
def without_coverage!
|
30
45
|
@_original_hash.delete_if do |key, _value|
|
31
46
|
not %w[enabled name].include?(key)
|
32
47
|
end
|
33
48
|
end
|
34
49
|
|
50
|
+
# The title of the security control
|
51
|
+
#
|
52
|
+
# @return [String]
|
35
53
|
def to_s
|
36
54
|
title
|
37
55
|
end
|
@@ -1,10 +1,13 @@
|
|
1
1
|
require 'controls/objects/configuration_finding'
|
2
2
|
|
3
3
|
module Controls
|
4
|
+
# An object to represent the highest level of the finding API resources
|
4
5
|
class SecurityControlFinding < Dish::Plate
|
5
6
|
coerce :assessmentTimestamp, ->(value) { Time.at(value / 1000) if value }
|
6
7
|
coerce :findings, Controls::ConfigurationFinding
|
7
8
|
|
9
|
+
# @return [String] a representation of the findings prefixed with the
|
10
|
+
# collection name
|
8
11
|
def to_s
|
9
12
|
"#{name}: #{findings.map { |finding| "\n #{finding}" }.join}"
|
10
13
|
end
|
data/lib/controls/response.rb
CHANGED
@@ -7,11 +7,11 @@ module Controls
|
|
7
7
|
|
8
8
|
# Returns the parsed JSON response after marshaling through Dish
|
9
9
|
#
|
10
|
-
# @param [String]
|
10
|
+
# @param [String] response_body the JSON object to parse
|
11
11
|
# @param [String] path the requested API endpoint's path
|
12
12
|
# @return [Dish::Plate,Object] a marshaled representation of the JSON response
|
13
|
-
def parse(
|
14
|
-
hash_or_array = JSON.parse(
|
13
|
+
def parse(response_body, response_status = nil, path = nil)
|
14
|
+
hash_or_array = JSON.parse(response_body)
|
15
15
|
|
16
16
|
if hash_or_array.is_a?(Hash) && hash_or_array.key?('message') && hash_or_array.key?('documentationUrl')
|
17
17
|
type = Controls::Error
|
@@ -41,6 +41,13 @@ module Controls
|
|
41
41
|
end
|
42
42
|
|
43
43
|
Dish(hash_or_array, type)
|
44
|
+
rescue JSON::ParserError
|
45
|
+
opts = { message: 'An error occurred', status: response_status }
|
46
|
+
opts.delete_if do |_, value|
|
47
|
+
value.nil?
|
48
|
+
end
|
49
|
+
|
50
|
+
Controls::Error.new(opts)
|
44
51
|
end
|
45
52
|
end
|
46
53
|
end
|
data/lib/controls/version.rb
CHANGED
@@ -10,6 +10,7 @@ describe '/api/assessments' do
|
|
10
10
|
assessments = Controls.assessments
|
11
11
|
|
12
12
|
assessments.each do |assessment|
|
13
|
+
expect(assessment).to be_kind_of(Controls::Assessment)
|
13
14
|
expect(assessment).to match_assessment_format
|
14
15
|
end
|
15
16
|
end
|
@@ -19,6 +20,7 @@ describe '/api/assessments' do
|
|
19
20
|
it 'returns a single assessment' do
|
20
21
|
assessment = Controls.assessments(1)
|
21
22
|
|
23
|
+
expect(assessment).to be_kind_of(Controls::Assessment)
|
22
24
|
expect(assessment).to match_assessment_format
|
23
25
|
expect(assessment.id).to eq(1)
|
24
26
|
expect(assessment.assessing?).to be_false
|
@@ -30,18 +30,20 @@ describe '/api/assets' do
|
|
30
30
|
end
|
31
31
|
|
32
32
|
it 'returns a 400 Bad Request on a bad page.sort parameter' do
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
expect(
|
33
|
+
error = Controls.assets(
|
34
|
+
'page.sort' => 'asdfghjkl'
|
35
|
+
)
|
36
|
+
|
37
|
+
expect(error).to be_kind_of(Controls::Error)
|
38
|
+
expect(error.status).to eq('400')
|
39
|
+
expect(error.message).to eq('Invalid sort parameter: asdfghjkl: ASC')
|
39
40
|
end
|
40
41
|
end
|
41
42
|
|
42
43
|
context 'GET /api/assets/search?query=Windows' do
|
43
44
|
it 'returns only assets with Windows assets' do
|
44
45
|
asset_collection = Controls.asset_search('Windows')
|
46
|
+
expect(asset_collection).not_to be_kind_of(Controls::Error)
|
45
47
|
expect(asset_collection).to be_kind_of(Controls::AssetCollection)
|
46
48
|
|
47
49
|
asset_collection.resources.map(&:operating_system).each do |operating_system|
|
@@ -49,4 +51,43 @@ describe '/api/assets' do
|
|
49
51
|
end
|
50
52
|
end
|
51
53
|
end
|
54
|
+
|
55
|
+
context 'GET /api/guidance/enable-email-attachment-filtering/applicable_assets' do
|
56
|
+
it 'returns a 400 Bad Request on a bad page.sort parameter' do
|
57
|
+
error = Controls.applicable_assets(
|
58
|
+
'enable-email-attachment-filtering',
|
59
|
+
'page.sort' => 'asdfghjkl'
|
60
|
+
)
|
61
|
+
|
62
|
+
expect(error).to be_kind_of(Controls::Error)
|
63
|
+
expect(error.status).to eq('400')
|
64
|
+
expect(error.message).to eq('Invalid sort parameter: asdfghjkl: ASC')
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
context 'GET /api/security_control/desktops-with-antivirus-deployed/uncovered_assets' do
|
69
|
+
it 'returns a 400 Bad Request on a bad page.sort parameter' do
|
70
|
+
error = Controls.uncovered_assets(
|
71
|
+
'desktops-with-antivirus-deployed',
|
72
|
+
'page.sort' => 'asdfghjkl'
|
73
|
+
)
|
74
|
+
|
75
|
+
expect(error).to be_kind_of(Controls::Error)
|
76
|
+
expect(error.message).to eq('Invalid sort parameter: asdfghjkl: ASC')
|
77
|
+
expect(error.status).to eq('400')
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
context 'GET /api/threat_vectors/network-borne/undefended_assets' do
|
82
|
+
it 'returns a 400 Bad Request on a bad page.sort parameter' do
|
83
|
+
error = Controls.undefended_assets(
|
84
|
+
'network-borne',
|
85
|
+
'page.sort' => 'asdfghjkl'
|
86
|
+
)
|
87
|
+
|
88
|
+
expect(error).to be_kind_of(Controls::Error)
|
89
|
+
expect(error.message).to eq('Invalid sort parameter: asdfghjkl: ASC')
|
90
|
+
expect(error.status).to eq('400')
|
91
|
+
end
|
92
|
+
end
|
52
93
|
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.
|
4
|
+
version: 1.7.7
|
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-
|
11
|
+
date: 2014-05-27 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: dish
|
@@ -118,12 +118,16 @@ files:
|
|
118
118
|
- lib/controls/objects.rb
|
119
119
|
- lib/controls/objects/assessment.rb
|
120
120
|
- lib/controls/objects/asset.rb
|
121
|
+
- lib/controls/objects/asset_collection.rb
|
121
122
|
- lib/controls/objects/configuration.rb
|
123
|
+
- lib/controls/objects/configuration_coverage.rb
|
122
124
|
- lib/controls/objects/configuration_finding.rb
|
123
125
|
- lib/controls/objects/coverage_information.rb
|
124
126
|
- lib/controls/objects/error.rb
|
125
127
|
- lib/controls/objects/event.rb
|
126
128
|
- lib/controls/objects/guidance.rb
|
129
|
+
- lib/controls/objects/guidance/reference.rb
|
130
|
+
- lib/controls/objects/prioritized_guidance.rb
|
127
131
|
- lib/controls/objects/product_change_event_payload.rb
|
128
132
|
- lib/controls/objects/security_control.rb
|
129
133
|
- lib/controls/objects/security_control_change_event_payload.rb
|