esp_sdk 1.1.0 → 2.0.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (199) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +0 -2
  3. data/.rubocop.yml +53 -6
  4. data/.ruby-version +1 -1
  5. data/.travis.yml +8 -2
  6. data/Gemfile.lock +171 -0
  7. data/Guardfile +47 -0
  8. data/README.md +230 -5
  9. data/Rakefile +14 -1
  10. data/assets/logo.png +0 -0
  11. data/bin/esp_console +71 -0
  12. data/esp_sdk.gemspec +27 -18
  13. data/lib/esp/exceptions.rb +3 -0
  14. data/lib/esp/extensions/active_resource/formats/json_api_format.rb +105 -0
  15. data/lib/esp/extensions/active_resource/paginated_collection.rb +198 -0
  16. data/lib/esp/extensions/active_resource/validations.rb +45 -0
  17. data/lib/esp/resources/alert.rb +135 -0
  18. data/lib/esp/resources/cloud_trail_event.rb +45 -0
  19. data/lib/esp/resources/concerns/stat_totals.rb +79 -0
  20. data/lib/esp/resources/contact_request.rb +42 -0
  21. data/lib/esp/resources/custom_signature.rb +224 -0
  22. data/lib/esp/resources/dashboard.rb +31 -0
  23. data/lib/esp/resources/external_account.rb +89 -0
  24. data/lib/esp/resources/organization.rb +61 -0
  25. data/lib/esp/resources/region.rb +46 -0
  26. data/lib/esp/resources/report.rb +100 -0
  27. data/lib/esp/resources/resource.rb +66 -0
  28. data/lib/esp/resources/service.rb +31 -0
  29. data/lib/esp/resources/signature.rb +106 -0
  30. data/lib/esp/resources/stat.rb +124 -0
  31. data/lib/esp/resources/stat_custom_signature.rb +121 -0
  32. data/lib/esp/resources/stat_region.rb +121 -0
  33. data/lib/esp/resources/stat_service.rb +121 -0
  34. data/lib/esp/resources/stat_signature.rb +121 -0
  35. data/lib/esp/resources/sub_organization.rb +69 -0
  36. data/lib/esp/resources/suppression/region.rb +99 -0
  37. data/lib/esp/resources/suppression/signature.rb +107 -0
  38. data/lib/esp/resources/suppression/unique_identifier.rb +60 -0
  39. data/lib/esp/resources/suppression.rb +86 -0
  40. data/lib/esp/resources/tag.rb +45 -0
  41. data/lib/esp/resources/team.rb +79 -0
  42. data/lib/esp/resources/user.rb +46 -0
  43. data/lib/esp/version.rb +3 -0
  44. data/lib/esp.rb +95 -0
  45. data/lib/esp_sdk.rb +9 -45
  46. data/lib/tasks/rubocop.rake +2 -0
  47. data/lib/tasks/testing.rake +3 -0
  48. data/rdoc/ActiveResource/Formats.html +176 -0
  49. data/rdoc/ActiveResource/PaginatedCollection.html +910 -0
  50. data/rdoc/ActiveResource.html +180 -0
  51. data/rdoc/ESP/Alert.html +771 -0
  52. data/rdoc/ESP/CloudTrailEvent.html +375 -0
  53. data/rdoc/ESP/ContactRequest.html +366 -0
  54. data/rdoc/ESP/CustomSignature.html +746 -0
  55. data/rdoc/ESP/Dashboard.html +355 -0
  56. data/rdoc/ESP/ExternalAccount.html +565 -0
  57. data/rdoc/ESP/Organization.html +590 -0
  58. data/rdoc/ESP/Region.html +399 -0
  59. data/rdoc/ESP/Report.html +622 -0
  60. data/rdoc/ESP/Service.html +380 -0
  61. data/rdoc/ESP/Signature.html +555 -0
  62. data/rdoc/ESP/Stat.html +1778 -0
  63. data/rdoc/ESP/StatCustomSignature.html +1599 -0
  64. data/rdoc/ESP/StatRegion.html +1598 -0
  65. data/rdoc/ESP/StatService.html +1598 -0
  66. data/rdoc/ESP/StatSignature.html +1598 -0
  67. data/rdoc/ESP/SubOrganization.html +540 -0
  68. data/rdoc/ESP/Suppression/Region.html +454 -0
  69. data/rdoc/ESP/Suppression/Signature.html +470 -0
  70. data/rdoc/ESP/Suppression/UniqueIdentifier.html +417 -0
  71. data/rdoc/ESP/Suppression.html +649 -0
  72. data/rdoc/ESP/Tag.html +371 -0
  73. data/rdoc/ESP/Team.html +584 -0
  74. data/rdoc/ESP/User.html +483 -0
  75. data/rdoc/ESP.html +546 -0
  76. data/rdoc/README_md.html +501 -0
  77. data/rdoc/created.rid +30 -0
  78. data/rdoc/images/add.png +0 -0
  79. data/rdoc/images/arrow_up.png +0 -0
  80. data/rdoc/images/brick.png +0 -0
  81. data/rdoc/images/brick_link.png +0 -0
  82. data/rdoc/images/bug.png +0 -0
  83. data/rdoc/images/bullet_black.png +0 -0
  84. data/rdoc/images/bullet_toggle_minus.png +0 -0
  85. data/rdoc/images/bullet_toggle_plus.png +0 -0
  86. data/rdoc/images/date.png +0 -0
  87. data/rdoc/images/delete.png +0 -0
  88. data/rdoc/images/find.png +0 -0
  89. data/rdoc/images/loadingAnimation.gif +0 -0
  90. data/rdoc/images/macFFBgHack.png +0 -0
  91. data/rdoc/images/package.png +0 -0
  92. data/rdoc/images/page_green.png +0 -0
  93. data/rdoc/images/page_white_text.png +0 -0
  94. data/rdoc/images/page_white_width.png +0 -0
  95. data/rdoc/images/plugin.png +0 -0
  96. data/rdoc/images/ruby.png +0 -0
  97. data/rdoc/images/tag_blue.png +0 -0
  98. data/rdoc/images/tag_green.png +0 -0
  99. data/rdoc/images/transparent.png +0 -0
  100. data/rdoc/images/wrench.png +0 -0
  101. data/rdoc/images/wrench_orange.png +0 -0
  102. data/rdoc/images/zoom.png +0 -0
  103. data/rdoc/index.html +134 -0
  104. data/rdoc/js/darkfish.js +155 -0
  105. data/rdoc/js/jquery.js +4 -0
  106. data/rdoc/js/navigation.js +142 -0
  107. data/rdoc/js/search.js +94 -0
  108. data/rdoc/js/search_index.js +1 -0
  109. data/rdoc/js/searcher.js +228 -0
  110. data/rdoc/rdoc.css +595 -0
  111. data/rdoc/table_of_contents.html +927 -0
  112. data/test/esp/extensions/active_resource/formats/json_api_format_test.rb +109 -0
  113. data/test/esp/extensions/active_resource/paginated_collection_test.rb +435 -0
  114. data/test/esp/extensions/active_resource/validations_test.rb +59 -0
  115. data/test/esp/resources/alert_test.rb +263 -0
  116. data/test/esp/resources/cloud_trail_event_test.rb +98 -0
  117. data/test/esp/resources/contact_request_test.rb +73 -0
  118. data/test/esp/resources/custom_signature_test.rb +241 -0
  119. data/test/esp/resources/dashboard_test.rb +71 -0
  120. data/test/esp/resources/external_account_test.rb +125 -0
  121. data/test/esp/resources/organization_test.rb +175 -0
  122. data/test/esp/resources/region_test.rb +84 -0
  123. data/test/esp/resources/report_test.rb +180 -0
  124. data/test/esp/resources/resource_test.rb +183 -0
  125. data/test/esp/resources/service_test.rb +64 -0
  126. data/test/esp/resources/signature_test.rb +177 -0
  127. data/test/esp/resources/stat_custom_signature_test.rb +115 -0
  128. data/test/esp/resources/stat_region_test.rb +114 -0
  129. data/test/esp/resources/stat_service_test.rb +114 -0
  130. data/test/esp/resources/stat_signature_test.rb +115 -0
  131. data/test/esp/resources/stat_test.rb +159 -0
  132. data/test/esp/resources/sub_organization_test.rb +127 -0
  133. data/test/esp/resources/suppression/region_test.rb +115 -0
  134. data/test/esp/resources/suppression/signature_test.rb +117 -0
  135. data/test/esp/resources/suppression/unique_identifier_test.rb +79 -0
  136. data/test/esp/resources/suppression_test.rb +226 -0
  137. data/test/esp/resources/tag_test.rb +98 -0
  138. data/test/esp/resources/team_test.rb +140 -0
  139. data/test/esp/resources/user_test.rb +113 -0
  140. data/test/esp_test.rb +139 -0
  141. data/test/factories/alerts.rb +234 -0
  142. data/test/factories/cloud_trail_events.rb +16 -0
  143. data/test/factories/contact_requests.rb +14 -0
  144. data/test/factories/custom_signatures.rb +30 -0
  145. data/test/factories/dashboards.rb +91 -0
  146. data/test/factories/errors.rb +24 -0
  147. data/test/factories/external_accounts.rb +44 -0
  148. data/test/factories/organizations.rb +48 -0
  149. data/test/factories/regions.rb +9 -0
  150. data/test/factories/reports.rb +56 -0
  151. data/test/factories/services.rb +12 -0
  152. data/test/factories/signatures.rb +28 -0
  153. data/test/factories/stat_custom_signatures.rb +97 -0
  154. data/test/factories/stat_regions.rb +97 -0
  155. data/test/factories/stat_services.rb +97 -0
  156. data/test/factories/stat_signautures.rb +97 -0
  157. data/test/factories/stats.rb +129 -0
  158. data/test/factories/sub_organizations.rb +34 -0
  159. data/test/factories/suppression/regions.rb +90 -0
  160. data/test/factories/suppression/signatures.rb +117 -0
  161. data/test/factories/suppression/unique_identifiers.rb +111 -0
  162. data/test/factories/suppressions.rb +71 -0
  163. data/test/factories/tags.rb +12 -0
  164. data/test/factories/teams.rb +32 -0
  165. data/test/factories/users.rb +54 -0
  166. data/test/json_strategy.rb +25 -0
  167. data/test/test_helper.rb +44 -5
  168. metadata +387 -119
  169. data/bin/esp_repl +0 -60
  170. data/lib/esp_sdk/api.rb +0 -33
  171. data/lib/esp_sdk/client.rb +0 -62
  172. data/lib/esp_sdk/configure.rb +0 -40
  173. data/lib/esp_sdk/end_points/base.rb +0 -102
  174. data/lib/esp_sdk/end_points/contact_requests.rb +0 -6
  175. data/lib/esp_sdk/end_points/custom_signatures.rb +0 -41
  176. data/lib/esp_sdk/end_points/dashboard.rb +0 -35
  177. data/lib/esp_sdk/end_points/external_accounts.rb +0 -9
  178. data/lib/esp_sdk/end_points/organizations.rb +0 -6
  179. data/lib/esp_sdk/end_points/reports.rb +0 -6
  180. data/lib/esp_sdk/end_points/services.rb +0 -6
  181. data/lib/esp_sdk/end_points/signatures.rb +0 -39
  182. data/lib/esp_sdk/end_points/sub_organizations.rb +0 -6
  183. data/lib/esp_sdk/end_points/teams.rb +0 -6
  184. data/lib/esp_sdk/end_points/users.rb +0 -6
  185. data/lib/esp_sdk/exceptions.rb +0 -8
  186. data/lib/esp_sdk/extensions/rest_client/request.rb +0 -9
  187. data/lib/esp_sdk/repl.rb +0 -61
  188. data/lib/esp_sdk/version.rb +0 -3
  189. data/test/esp_sdk/api_test.rb +0 -36
  190. data/test/esp_sdk/client_test.rb +0 -129
  191. data/test/esp_sdk/configure_test.rb +0 -65
  192. data/test/esp_sdk/end_points/.keep +0 -0
  193. data/test/esp_sdk/end_points/base_test.rb +0 -230
  194. data/test/esp_sdk/end_points/custom_signatures_test.rb +0 -90
  195. data/test/esp_sdk/end_points/dashboard_test.rb +0 -55
  196. data/test/esp_sdk/end_points/external_accounts_test.rb +0 -20
  197. data/test/esp_sdk/end_points/signatures_test.rb +0 -83
  198. data/test/esp_sdk/exceptions_test.rb +0 -41
  199. data/test/esp_sdk_test.rb +0 -70
data/Rakefile CHANGED
@@ -1,9 +1,22 @@
1
1
  require 'bundler/gem_tasks'
2
2
  require 'rake/testtask'
3
+ load 'lib/tasks/rubocop.rake'
4
+ load 'lib/tasks/testing.rake'
5
+ require 'rdoc/task'
3
6
 
4
7
  Rake::TestTask.new do |task|
5
8
  task.libs << 'test'
6
9
  task.test_files = FileList['test/*_test.rb', 'test/**/*_test.rb']
7
10
  end
8
11
 
9
- task default: [:test]
12
+ task default: [:test]
13
+
14
+ RDoc::Task.new(:rdoc) do |rdoc|
15
+ rdoc.rdoc_dir = 'rdoc'
16
+ rdoc.title = 'ESPSDK'
17
+ rdoc.options << '--line-numbers'
18
+ rdoc.rdoc_files.include('README.md')
19
+ rdoc.rdoc_files.include('lib/esp/resources/**/*.rb')
20
+ rdoc.rdoc_files.include('lib/esp/extensions/active_resource/paginated_collection.rb')
21
+ rdoc.rdoc_files.include('lib/esp.rb')
22
+ end
data/assets/logo.png ADDED
Binary file
data/bin/esp_console ADDED
@@ -0,0 +1,71 @@
1
+ #!/usr/bin/env ruby
2
+ # https://www.fedux.org/articles/2015/08/26/creating-an-irb-based-repl-console-for-your-project.html
3
+
4
+ ENV['ESP_ENV'] = ARGV[0]
5
+ require 'irb'
6
+ require_relative '../lib/esp_sdk'
7
+
8
+ module ESP
9
+ class Console
10
+ def start
11
+ ARGV.clear
12
+ IRB.setup nil
13
+
14
+ IRB.conf[:PROMPT] = {}
15
+ IRB.conf[:IRB_NAME] = 'espsdk'
16
+ IRB.conf[:PROMPT][:ESPSDK] = {
17
+ :PROMPT_I => '%N:%03n:%i> ',
18
+ :PROMPT_N => '%N:%03n:%i> ',
19
+ :PROMPT_S => '%N:%03n:%i%l ',
20
+ :PROMPT_C => '%N:%03n:%i* ',
21
+ :RETURN => "# => %s\n"
22
+ }
23
+ IRB.conf[:PROMPT_MODE] = :ESPSDK
24
+
25
+ IRB.conf[:RC] = false
26
+
27
+ require 'irb/completion'
28
+ require 'irb/ext/save-history'
29
+ IRB.conf[:READLINE] = true
30
+ IRB.conf[:SAVE_HISTORY] = 1000
31
+ IRB.conf[:HISTORY_FILE] = '~/.esp_sdk_history'
32
+
33
+ context = Class.new do
34
+ include ESP
35
+ end
36
+
37
+ irb = IRB::Irb.new(IRB::WorkSpace.new(context.new))
38
+ IRB.conf[:MAIN_CONTEXT] = irb.context
39
+
40
+ trap("SIGINT") do
41
+ irb.signal_handle
42
+ end
43
+
44
+ begin
45
+ catch(:IRB_EXIT) do
46
+ irb.eval_input
47
+ end
48
+ ensure
49
+ IRB.irb_at_exit
50
+ end
51
+ end
52
+ end
53
+ end
54
+
55
+ begin
56
+ require 'catpix'
57
+ logo = File.expand_path(File.dirname(__FILE__) + '/../assets/logo.png')
58
+ Catpix::print_image logo
59
+ rescue
60
+ require 'artii'
61
+ artii = Artii::Base.new(font: 'slant')
62
+ print artii.asciify('E.S.P')
63
+ end
64
+ print <<-banner
65
+
66
+ Evident Security Platform Console #{ESP::VERSION}
67
+ Copyright (c) 2013-#{Time.now.year} Evident Security, All Rights Reserved.
68
+ http://www.evident.io
69
+ banner
70
+ ESP::Console.new.start
71
+
data/esp_sdk.gemspec CHANGED
@@ -1,15 +1,15 @@
1
1
  # coding: utf-8
2
2
  lib = File.expand_path('../lib', __FILE__)
3
3
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
- require 'esp_sdk/version'
4
+ require 'esp/version'
5
5
 
6
6
  Gem::Specification.new do |spec|
7
7
  spec.name = 'esp_sdk'
8
- spec.version = EspSdk::VERSION
8
+ spec.version = ESP::VERSION
9
9
  spec.authors = ['Evident.io']
10
10
  spec.email = ['support@evident.io']
11
- spec.summary = %q{SDK for interacting with the ESP API.}
12
- spec.homepage = ''
11
+ spec.summary = "SDK for interacting with the ESP API."
12
+ spec.homepage = 'https://github.com/EvidentSecurity/esp_sdk'
13
13
  spec.license = 'MIT'
14
14
 
15
15
  spec.files = `git ls-files -z`.split("\x0")
@@ -17,19 +17,28 @@ Gem::Specification.new do |spec|
17
17
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
18
18
  spec.require_paths = ['lib']
19
19
 
20
- spec.add_development_dependency 'bundler', '~> 1.6'
21
- spec.add_development_dependency 'rake', '~> 10.3.2'
22
- spec.add_development_dependency 'rubocop', '~> 0.27.1'
23
- spec.add_development_dependency 'minitest', '~> 5.4.2'
24
- spec.add_development_dependency 'minitest-reporters', '~> 1.0.7'
25
- spec.add_development_dependency 'shoulda', '~> 3.5.0'
26
- spec.add_development_dependency 'codeclimate-test-reporter', '~> 0.4.1'
27
- spec.add_development_dependency 'mocha', '~> 1.1.0'
28
- spec.add_development_dependency 'fakeweb', '~> 1.3'
20
+ spec.required_ruby_version = '>= 2.0.0'
29
21
 
30
- spec.add_runtime_dependency 'activesupport', '>= 3.0.0'
31
- spec.add_runtime_dependency 'rest_client', '~> 1.7.3'
32
- spec.add_runtime_dependency 'pry', '~> 0.10.1'
33
- spec.add_runtime_dependency 'awesome_print', '~> 1.2.0'
34
- spec.add_runtime_dependency 'artii', '~> 2.1.1'
22
+ spec.add_development_dependency 'bundler'
23
+ spec.add_development_dependency 'rake'
24
+ spec.add_development_dependency 'rubocop'
25
+ spec.add_development_dependency 'guard'
26
+ spec.add_development_dependency 'guard-minitest'
27
+ spec.add_development_dependency 'guard-rubocop'
28
+ spec.add_development_dependency 'minitest'
29
+ spec.add_development_dependency 'minitest-reporters'
30
+ spec.add_development_dependency 'shoulda'
31
+ spec.add_development_dependency 'mocha'
32
+ spec.add_development_dependency 'bourne'
33
+ spec.add_development_dependency 'webmock'
34
+ spec.add_development_dependency 'coveralls'
35
+ spec.add_development_dependency 'factory_girl'
36
+ spec.add_development_dependency 'rdoc'
37
+
38
+ spec.add_dependency 'activeresource', '~> 4.0.0'
39
+ spec.add_dependency 'api-auth'
40
+ spec.add_dependency 'rack'
41
+ spec.add_dependency 'awesome_print'
42
+ spec.add_dependency 'artii'
43
+ spec.add_dependency 'catpix'
35
44
  end
@@ -0,0 +1,3 @@
1
+ module ESP
2
+ class NotImplementedError < StandardError; end
3
+ end
@@ -0,0 +1,105 @@
1
+ require 'active_support/json'
2
+
3
+ module ActiveResource
4
+ class ConnectionError
5
+ def initialize(response)
6
+ @response = if response.respond_to?(:response)
7
+ message = decoded_errors(response.response.body)
8
+ Struct.new(:body, :code, :message).new(response.response.body, response.code, message)
9
+ else
10
+ response
11
+ end
12
+ end
13
+
14
+ private
15
+
16
+ def decoded_errors(json)
17
+ Array((Hash(ActiveSupport::JSON.decode(json)))['errors'].map { |e| e['title'] }).join(" ")
18
+ rescue
19
+ []
20
+ end
21
+ end
22
+
23
+ module Formats
24
+ module JsonAPIFormat
25
+ module_function
26
+
27
+ def extension
28
+ "json".freeze
29
+ end
30
+
31
+ def mime_type
32
+ "application/vnd.api+json".freeze
33
+ end
34
+
35
+ def encode(hash, options = nil)
36
+ ActiveSupport::JSON.encode(hash, options)
37
+ end
38
+
39
+ def decode(json)
40
+ # ap ActiveSupport::JSON.decode(json), index: false, indent: -2
41
+ Formats.remove_root(parse_json_api(ActiveSupport::JSON.decode(json)))
42
+ end
43
+
44
+ private
45
+
46
+ def self.parse_json_api(elements)
47
+ included = elements.delete('included')
48
+ elements.tap do |e|
49
+ Array.wrap(e.fetch('data', {})).each do |object|
50
+ parse_object!(object, included)
51
+ end
52
+ end
53
+ end
54
+
55
+ def self.parse_object!(object, included = nil)
56
+ return object unless object.respond_to?(:each)
57
+ merge_attributes!(object)
58
+ parse_elements(object)
59
+ parse_relationships!(object, included)
60
+ object
61
+ end
62
+
63
+ def self.parse_elements(object)
64
+ object.each_value do |value|
65
+ if value.is_a? Hash
66
+ parse_object!(value)
67
+ elsif value.is_a? Array
68
+ value.map! { |o| parse_object!(o) }
69
+ end
70
+ end
71
+ end
72
+
73
+ def self.parse_relationships!(object, included)
74
+ object.fetch('relationships', {}).each do |assoc, details|
75
+ extract_foreign_keys!(object, assoc, details['data'])
76
+ merge_included_objects!(object, assoc, details['data'], included)
77
+ end
78
+ end
79
+
80
+ def self.merge_attributes!(object)
81
+ return unless object.is_a? Hash
82
+ object.merge! object.delete('attributes') unless object['attributes'].blank?
83
+ end
84
+
85
+ def self.extract_foreign_keys!(object, assoc, data)
86
+ return if data.blank?
87
+ if data.is_a? Array
88
+ object["#{assoc.singularize}_ids"] = data.map { |d| d['id'] }
89
+ else
90
+ object["#{assoc}_id"] = data['id']
91
+ end
92
+ end
93
+
94
+ def self.merge_included_objects!(object, assoc, data, included)
95
+ return if included.blank?
96
+ object[assoc] = case data
97
+ when Array
98
+ included.select { |i| data.include?(parse_object!(i).slice('type', 'id')) }
99
+ when Hash
100
+ included.detect { |i| data == parse_object!(i).slice('type', 'id') }
101
+ end
102
+ end
103
+ end
104
+ end
105
+ end
@@ -0,0 +1,198 @@
1
+ require 'rack'
2
+
3
+ module ActiveResource
4
+ # Provides a mean to call the Evident.io API to easily retrieve paginated data
5
+ class PaginatedCollection < ActiveResource::Collection
6
+ attr_reader :next_page_params, :previous_page_params, :last_page_params #:nodoc:
7
+ attr_accessor :from #:nodoc:
8
+
9
+ def initialize(elements = []) #:nodoc:
10
+ # If a collection is sent without the pagination links, then elements will just be an array.
11
+ if elements.is_a? Hash
12
+ super(elements['data'])
13
+ parse_pagination_links(elements['links'])
14
+ else
15
+ super(elements)
16
+ end
17
+ end
18
+
19
+ # Returns a new PaginatedCollection with the first page of results.
20
+ #
21
+ # Returns self when on the first page and no API call is made.
22
+ #
23
+ # ==== Example
24
+ # alerts.current_page_number # => 5
25
+ # first_page = alerts.first_page
26
+ # alerts.current_page_number # => 5
27
+ # first_page.current_page_number # => 1
28
+ def first_page
29
+ previous_page? ? resource_class.all(from: from, params: { page: { number: 1 } }) : self
30
+ end
31
+
32
+ # Updates the existing PaginatedCollection object with the first page of data when not on the first page.
33
+ #
34
+ # ==== Example
35
+ # alerts.current_page_number # => 5
36
+ # alerts.first_page!
37
+ # alerts.current_page_number # => 1
38
+ def first_page!
39
+ first_page.tap { |page| update_self(page) }
40
+ end
41
+
42
+ # Returns a new PaginatedCollection with the previous page of results.
43
+ #
44
+ # Returns self when on the first page and no API call is made.
45
+ #
46
+ # ==== Example
47
+ # alerts.current_page_number # => 5
48
+ # previous_page = alerts.previous_page
49
+ # alerts.current_page_number # => 5
50
+ # previous_page.current_page_number # => 4
51
+ def previous_page
52
+ previous_page? ? resource_class.all(from: from, params: previous_page_params) : self
53
+ end
54
+
55
+ # Updates the existing PaginatedCollection object with the previous page of data when not on the first page.
56
+ #
57
+ # ==== Example
58
+ # alerts.current_page_number # => 5
59
+ # alerts.previous_page!
60
+ # alerts.current_page_number # => 4
61
+ def previous_page!
62
+ previous_page.tap { |page| update_self(page) }
63
+ end
64
+
65
+ # Returns a new PaginatedCollection with the next page of results.
66
+ #
67
+ # Returns self when on the last page and no API call is made.
68
+ #
69
+ # ==== Example
70
+ # alerts.current_page_number # => 5
71
+ # next_page = alerts.next_page
72
+ # alerts.current_page_number # => 5
73
+ # next_page.current_page_number # => 6
74
+ def next_page
75
+ next_page? ? resource_class.all(from: from, params: next_page_params) : self
76
+ end
77
+
78
+ # Updates the existing PaginatedCollection object with the last page of data when not on the last page.
79
+ #
80
+ # ==== Example
81
+ # alerts.current_page_number # => 5
82
+ # alerts.next_page!
83
+ # alerts.current_page_number # => 6
84
+ def next_page!
85
+ next_page.tap { |page| update_self(page) }
86
+ end
87
+
88
+ # Returns a new PaginatedCollection with the last page of results.
89
+ #
90
+ # Returns self when on the last page and no API call is made.
91
+ #
92
+ # ==== Example
93
+ # alerts.current_page_number # => 5
94
+ # last_page = alerts.last_page
95
+ # alerts.current_page_number # => 5
96
+ # last_page.current_page_number # => 25
97
+ def last_page
98
+ !last_page? ? resource_class.all(from: from, params: last_page_params) : self
99
+ end
100
+
101
+ # Updates the existing PaginatedCollection object with the last page of data when not on the last page.
102
+ #
103
+ # ==== Example
104
+ # alerts.current_page_number # => 5
105
+ # alerts.last_page!
106
+ # alerts.current_page_number # => 25
107
+ def last_page!
108
+ last_page.tap { |page| update_self(page) }
109
+ end
110
+
111
+ # Returns a new PaginatedCollection with the +page_number+ page of data.
112
+ #
113
+ # Returns self when +page_number+ == #current_page_number
114
+ #
115
+ # ==== Attribute
116
+ #
117
+ # +page_number+ - The page number of the data wanted. +page_number+ must be between 1 and #last_page_number.
118
+ #
119
+ # ==== Example
120
+ # alerts.current_page_number # => 5
121
+ # page = alerts.page(2)
122
+ # alerts.current_page_number # => 5
123
+ # page.current_page_number # => 2
124
+ def page(page_number = nil)
125
+ fail ArgumentError, "You must supply a page number." unless page_number.present?
126
+ fail ArgumentError, "Page number cannot be less than 1." if page_number.to_i < 1
127
+ fail ArgumentError, "Page number cannot be greater than the last page number." if page_number.to_i > last_page_number.to_i
128
+ page_number.to_i != current_page_number.to_i ? resource_class.all(from: from, params: { page: { number: page_number, size: (next_page_params || previous_page_params)['page']['size'] } }) : self
129
+ end
130
+
131
+ # Returns a new PaginatedCollection with the +page_number+ page of data when not already on page +page_number+.
132
+ #
133
+ # ==== Attribute
134
+ #
135
+ # +page_number+ - The page number of the data wanted. +page_number+ must be between 1 and #last_page_number.
136
+ #
137
+ # ==== Example
138
+ # alerts.current_page_number # => 5
139
+ # alerts.page!(2)
140
+ # alerts.current_page_number # => 2
141
+ def page!(page_number)
142
+ page(page_number).tap { |page| update_self(page) }
143
+ end
144
+
145
+ # The current page number of data.
146
+ def current_page_number
147
+ (previous_page_number.to_i + 1).to_s
148
+ end
149
+
150
+ # The previous page number of data.
151
+ def previous_page_number
152
+ Hash(previous_page_params).fetch('page', {}).fetch('number', nil)
153
+ end
154
+
155
+ # The next page number of data.
156
+ def next_page_number
157
+ Hash(next_page_params).fetch('page', {}).fetch('number', nil)
158
+ end
159
+
160
+ # The last page number of data.
161
+ def last_page_number
162
+ Hash(last_page_params).fetch('page', {}).fetch('number', nil)
163
+ end
164
+
165
+ # Returns whether or not there is a previous page of data in the collection.
166
+ def previous_page?
167
+ !previous_page_number.nil?
168
+ end
169
+
170
+ # Returns whether or not there is a next page of data in the collection.
171
+ def next_page?
172
+ !next_page_number.nil?
173
+ end
174
+
175
+ # Returns whether or not the collection is on the last page.
176
+ def last_page?
177
+ last_page_number.nil?
178
+ end
179
+
180
+ private
181
+
182
+ def update_self(page)
183
+ @elements = page.elements
184
+ @next_page_params = page.next_page_params
185
+ @previous_page_params = page.previous_page_params
186
+ @last_page_params = page.last_page_params
187
+ end
188
+
189
+ def parse_pagination_links(links)
190
+ @next_page_params = links['next'] ? Rack::Utils.parse_nested_query(CGI.unescape(URI.parse(links['next']).query)) : {}
191
+ @previous_page_params = links['prev'] ? Rack::Utils.parse_nested_query(CGI.unescape(URI.parse(links['prev']).query)) : {}
192
+ @last_page_params = links['last'] ? Rack::Utils.parse_nested_query(CGI.unescape(URI.parse(links['last']).query)) : {}
193
+ # The last page may not contain the full per page number of records, and will therefore come back with an incorrect size since the
194
+ # size is based on the collection size. This will mess up further calls to previous_page or first page so remove the size so it will bring back the default size.
195
+ previous_page_params['page'].delete('size') if last_page? && previous_page_params['page']
196
+ end
197
+ end
198
+ end
@@ -0,0 +1,45 @@
1
+ module ActiveResource
2
+ module Validations
3
+ # Loads the set of remote errors into the object's Errors based on the
4
+ # content-type of the error-block received.
5
+ def load_remote_errors(remote_errors, save_cache = false) #:nodoc:
6
+ if self.class.format == ActiveResource::Formats::JsonAPIFormat
7
+ errors.from_json_api(remote_errors.response.body, save_cache)
8
+ elsif self.class.format == ActiveResource::Formats[:json]
9
+ super
10
+ end
11
+ end
12
+ end
13
+
14
+ class Errors
15
+ def from_json_api(json, save_cache = false)
16
+ raw_errors = decoded_errors(json)
17
+ errors = meta_errors(raw_errors)
18
+ if errors.present?
19
+ from_hash errors, save_cache
20
+ else
21
+ from_array raw_errors.map { |e| e['title'] }
22
+ end
23
+ end
24
+
25
+ private
26
+
27
+ def decoded_errors(json)
28
+ Array((Hash(ActiveSupport::JSON.decode(json)))['errors'])
29
+ rescue
30
+ []
31
+ end
32
+
33
+ def meta_errors(raw_errors)
34
+ {}.tap do |errors|
35
+ raw_errors.each do |error|
36
+ next unless error['meta']
37
+ error['meta'].map do |attr, message|
38
+ errors[attr] ||= []
39
+ errors[attr] << message
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,135 @@
1
+ module ESP
2
+ class Alert < ESP::Resource
3
+ ##
4
+ # Returns the external account associated with this alert.
5
+ belongs_to :external_account, class_name: 'ESP::ExternalAccount'
6
+
7
+ ##
8
+ # Returns the region associated with this alert.
9
+ belongs_to :region, class_name: 'ESP::Region'
10
+
11
+ ##
12
+ # Returns the region associated with this alert. Either a signature or custom signature but not both will be present.
13
+ belongs_to :signature, class_name: 'ESP::Signature'
14
+
15
+ ##
16
+ # Returns the custom signature associated with this alert. Either a signature or custom signature but not both will be present.
17
+ belongs_to :custom_signature, class_name: 'ESP::CustomSignature'
18
+
19
+ ##
20
+ # Returns the suppression associated with this alert. If present the alert was suppressed.
21
+ belongs_to :suppression, class_name: 'ESP::Suppression'
22
+
23
+ ##
24
+ # Returns the cloud trail events associated with this alert. These may be added up to 10 minutes after the alert was created
25
+ has_many :cloud_trail_events, class_name: 'ESP::CloudTrailEvent'
26
+
27
+ ##
28
+ # Returns the tags associated with this alert.
29
+ has_many :tags, class_name: 'ESP::Tag'
30
+
31
+ # Not Implemented. You cannot create or update an Alert.
32
+ def save
33
+ fail ESP::NotImplementedError
34
+ end
35
+
36
+ # Not Implemented. You cannot destroy a an Alert.
37
+ def destroy
38
+ fail ESP::NotImplementedError
39
+ end
40
+
41
+ # Returns a paginated collection of alerts for the given report_id
42
+ # Convenience method to use instead of ::find since a report_id is required to return alerts.
43
+ #
44
+ # ==== Parameters
45
+ #
46
+ # +report_id+ | Required | The ID of the report to retrieve alerts for
47
+ #
48
+ # +arguments+ | Not Required | An optional hash of search criteria to filter the returned collection
49
+ #
50
+ # ===== Valid Arguments
51
+ #
52
+ # +region_id+ | Not Required | Return only alerts for this region.
53
+ #
54
+ # +status+ | Not Required | Return only alerts for the give status(es). Valid values are fail, warn, error, pass, info
55
+ #
56
+ # +first_seen+ | Not Required | Return only alerts that have started within a number of hours of the report. For example, first_seen of 3 will return alerts that started showing up within the last 3 hours of the report.
57
+ #
58
+ # +suppressed+ | Not Required | Return only suppressed alerts
59
+ #
60
+ # +team_id+ | Not Required | Return only alerts for the given team.
61
+ #
62
+ # +external_account_id+ | Not Required | Return only alerts for the given external id.
63
+ #
64
+ # +service_id+ | Not Required | Return only alerts on signatures with the given service.
65
+ #
66
+ # +signature_severity+ | Not Required | Return only alerts for signatures with the given risk_level. Valid values are Low, Medium, High
67
+ #
68
+ # +signature_name+ | Not Required | Return only alerts for signatures with the given name.
69
+ #
70
+ # +resource+ | Not Required | Return only alerts for the given resource or tag.
71
+ #
72
+ # +signature_identifier+ | Not Required | Return only alerts for signatures with the given identifier.
73
+ #
74
+ # ==== Example
75
+ # alerts = ESP::Alert.for_report(54, status: 'fail', signature_severity: 'High')
76
+ def self.for_report(report_id = nil, arguments = {})
77
+ fail ArgumentError, "You must supply a report id." unless report_id.present?
78
+ from = "#{prefix}reports/#{report_id}/alerts.json"
79
+ all(from: from, params: arguments)
80
+ end
81
+
82
+ # Find an Alert by id
83
+ #
84
+ # ==== Parameter
85
+ #
86
+ # +id+ | Required | The ID of the alert to retrieve
87
+ #
88
+ # :call-seq:
89
+ # find(id)
90
+ def self.find(*arguments)
91
+ scope = arguments.slice!(0)
92
+ options = (arguments.slice!(0) || {}).with_indifferent_access
93
+ return super(scope, options) if scope.is_a?(Numeric) || options[:from].present?
94
+ params = options.fetch(:params, {}).with_indifferent_access
95
+ report_id = params.delete(:report_id)
96
+ for_report(report_id, params)
97
+ end
98
+
99
+ # Suppress the signature associated with this alert.
100
+ # ==== Parameter
101
+ #
102
+ # +reason+ | Required | The reason for creating the suppression.
103
+ def suppress_signature(reason = nil)
104
+ suppress(Suppression::Signature, reason)
105
+ end
106
+
107
+ # Suppress the region associated with this alert.
108
+ # ==== Parameter
109
+ #
110
+ # +reason+ | Required | The reason for creating the suppression.
111
+ def suppress_region(reason = nil)
112
+ suppress(Suppression::Region, reason)
113
+ end
114
+
115
+ # Suppress the unique identifier associated with this alert.
116
+ # ==== Parameter
117
+ #
118
+ # +reason+ | Required | The reason for creating the suppression.
119
+ def suppress_unique_identifier(reason = nil)
120
+ suppress(Suppression::UniqueIdentifier, reason)
121
+ end
122
+
123
+ private
124
+
125
+ # Overridden because alerts does not use ransack for searching
126
+ def self.filters(params)
127
+ { filter: params }
128
+ end
129
+
130
+ def suppress(klass, reason)
131
+ fail ArgumentError, "You must specify the reason.".freeze unless reason.present?
132
+ klass.create(alert_id: id, reason: reason)
133
+ end
134
+ end
135
+ end
@@ -0,0 +1,45 @@
1
+ module ESP
2
+ class CloudTrailEvent < ESP::Resource
3
+ # Not Implemented. You cannot create or update a CloudTrailEvent.
4
+ def save
5
+ fail ESP::NotImplementedError
6
+ end
7
+
8
+ # Not Implemented. You cannot destroy a CloudTrailEvent.
9
+ def destroy
10
+ fail ESP::NotImplementedError
11
+ end
12
+
13
+ # Returns a paginated collection of cloud trail events for the given alert_id
14
+ # Convenience method to use instead of ::find since an alert_id is required to return cloud trail events.
15
+ #
16
+ # ==== Parameter
17
+ #
18
+ # +alert_id+ | Required | The ID of the alert to retrieve cloud trail events for
19
+ #
20
+ # ==== Example
21
+ # alerts = ESP::CloudTrailEvent.for_alert(1194)
22
+ def self.for_alert(alert_id = nil)
23
+ fail ArgumentError, "You must supply an alert id." unless alert_id.present?
24
+ from = "#{prefix}alerts/#{alert_id}/cloud_trail_events.json"
25
+ find(:all, from: from)
26
+ end
27
+
28
+ # Find a CloudTrailEvent by id
29
+ #
30
+ # ==== Parameter
31
+ #
32
+ # +id+ | Required | The ID of the cloud trail event to retrieve
33
+ #
34
+ # :call-seq:
35
+ # find(id)
36
+ def self.find(*arguments)
37
+ scope = arguments.slice!(0)
38
+ options = (arguments.slice!(0) || {}).with_indifferent_access
39
+ return super(scope, options) if scope.is_a?(Numeric) || options[:from].present?
40
+ params = options.fetch(:params, {}).with_indifferent_access
41
+ alert_id = params.delete(:alert_id)
42
+ for_alert(alert_id)
43
+ end
44
+ end
45
+ end