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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: e3090d635c0d18a35479c7821930b53e1068ccae
4
- data.tar.gz: 68c3be9f61e4e37ded0aaa335f540fe5016e7f78
3
+ metadata.gz: 3e6a9bd17f0766f610281ebc13747a1ef6ee630c
4
+ data.tar.gz: 3474dd374d83875e8e17063db12ebdadfcb719fe
5
5
  SHA512:
6
- metadata.gz: f0bc75c87fd92513706d4864e2b7ddccaa66b9c7aaeaba61cd12ceb2e1d913b168e22d02b173fc2818c1b7726f3798693bf5e00f0c2614fe7d67da296022bf73
7
- data.tar.gz: 90f11277cc0112b547aa5db0a8e72b1054fabf391c357aa658bd8624868b29433800f677174fc954895a9e0d91ba6a8ec331316e647bce368dc3d2b717bdccb5
6
+ metadata.gz: 9a6c8af6d7629a5d7260058f3e79b6cdb2410a9d5037161e58633596972d6eceda82c5b2648c3586c6a6e98aa200c109049f80ea8c751af23b6ba3f44a4cbe23
7
+ data.tar.gz: 161e19f2e28c9c7cfae24fed1c0bdf94d103911dedbba878887d2868fa738e00372101d449e46d0b81901b3c1a857a62667cd9c6d9d3adab9b14524bf24799dc
@@ -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 **controls**insight (controls) gem interfaces with [Rapid7's **controls**insight API](http://rapid7.github.io/controlsinsight.rb).
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 :user => 'admin', :password => 'password'
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 set of statistics over time
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>]
@@ -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
+ }
@@ -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 coverage for
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
@@ -69,7 +69,8 @@ module Controls
69
69
 
70
70
  self
71
71
  end
72
- # NOTE: This method currently leaves some "updated defaults" intact
72
+
73
+ # [note] - This method currently leaves some "updated defaults" intact
73
74
  # alias_method :reset!, :setup
74
75
 
75
76
  private
@@ -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/security_control'
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] - define this in Dish?
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] - define this in Dish?
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
- # The message related to the error
8
- attr_accessor :message
9
-
10
- # @!attribute message
11
- # The status code for the error response
12
- attr_accessor :status
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
- def to_h
22
- @__attributes__
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
- Guidance = Class.new(Dish::Plate)
3
- Guidance::Section = Class.new(Dish::Plate)
4
- Guidance::Reference = Class.new(Dish::Plate) do
5
- private
6
- def _allowed_keys
7
- %w[url]
8
- end
9
- end
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
@@ -0,0 +1,8 @@
1
+ require 'controls/objects/guidance'
2
+
3
+ module Controls
4
+ # A guidance subclass for when guidance is returned with priority.
5
+ #
6
+ # [todo] - this should include Comparable
7
+ PrioritizedGuidance = Class.new(Guidance)
8
+ end
@@ -1,5 +1,8 @@
1
1
  module Controls
2
+ # An object to represent the security control resource (without coverage)
3
+ # returned by the ControlsInsight API
2
4
  class SecurityControl < Dish::Plate
5
+ # @return [String] the name of the security control
3
6
  def to_s
4
7
  name
5
8
  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] - define this in Dish?
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
@@ -1,7 +1,9 @@
1
1
  module Controls
2
+ # A representation of the threat API resource
2
3
  class Threat < Dish::Plate
3
4
  coerce :assessmentTimestamp, ->(value) { Time.at(value / 1000) if value }
4
5
 
6
+ # @return [String] the title of the threat
5
7
  def to_s
6
8
  title
7
9
  end
@@ -1,7 +1,9 @@
1
1
  module Controls
2
+ # A representation of the threat vector API resource
2
3
  class ThreatVector < Dish::Plate
3
4
  coerce :assessmentTimestamp, ->(value) { Time.at(value / 1000) if value }
4
5
 
6
+ # @return [String] the title of the threat vector
5
7
  def to_s
6
8
  title
7
9
  end
@@ -1,7 +1,9 @@
1
1
  module Controls
2
+ # A object to represent the
2
3
  class Trend < Dish::Plate
3
4
  coerce :assessmentTimestamp, ->(value) { Time.at(value / 1000) if value }
4
5
 
6
+ # @return [String] the grade of the trend
5
7
  def to_s
6
8
  format('%05.2f', grade)
7
9
  end
@@ -7,11 +7,11 @@ module Controls
7
7
 
8
8
  # Returns the parsed JSON response after marshaling through Dish
9
9
  #
10
- # @param [String] obj the JSON object to parse
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(obj, path = nil)
14
- hash_or_array = JSON.parse(obj)
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
@@ -1,4 +1,4 @@
1
1
  module Controls
2
2
  # The version of the Controls gem
3
- VERSION = '1.7.6'
3
+ VERSION = '1.7.7'
4
4
  end
@@ -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
- asset_collection = expect {
34
- Controls.assets(
35
- 'page.sort' => 'asdfghjkl;'
36
- )
37
- }.not_to raise_error, "expected: 200 OK\ngot: 500 Internal Server Error"
38
- expect(asset_collection).to be_kind_of(Controls::Error)
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.6
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-04-15 00:00:00.000000000 Z
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