controls 1.7.6 → 1.7.7
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
[![Build Status](https://travis-ci.org/erran/controls.rb.png?branch=master)](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
|