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