cased-ruby 0.3.3

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.
Files changed (93) hide show
  1. checksums.yaml +7 -0
  2. data/.github/workflows/rubocop.yml +21 -0
  3. data/.github/workflows/ruby.yml +46 -0
  4. data/.gitignore +10 -0
  5. data/.rubocop.yml +88 -0
  6. data/.ruby-version +1 -0
  7. data/.travis.yml +7 -0
  8. data/CODE_OF_CONDUCT.md +74 -0
  9. data/Gemfile +8 -0
  10. data/Gemfile.lock +107 -0
  11. data/LICENSE +21 -0
  12. data/README.md +661 -0
  13. data/Rakefile +12 -0
  14. data/bin/console +15 -0
  15. data/bin/rubocop +29 -0
  16. data/cased-ruby.gemspec +48 -0
  17. data/lib/cased-ruby.rb +3 -0
  18. data/lib/cased.rb +260 -0
  19. data/lib/cased/clients.rb +21 -0
  20. data/lib/cased/collection_response.rb +117 -0
  21. data/lib/cased/config.rb +172 -0
  22. data/lib/cased/context.rb +50 -0
  23. data/lib/cased/context/expander.rb +33 -0
  24. data/lib/cased/error.rb +8 -0
  25. data/lib/cased/http/client.rb +83 -0
  26. data/lib/cased/http/error.rb +99 -0
  27. data/lib/cased/instrumentation/controller.rb +34 -0
  28. data/lib/cased/instrumentation/log_subscriber.rb +31 -0
  29. data/lib/cased/integrations/sidekiq.rb +17 -0
  30. data/lib/cased/integrations/sidekiq/client_middleware.rb +14 -0
  31. data/lib/cased/integrations/sidekiq/server_middleware.rb +20 -0
  32. data/lib/cased/model.rb +98 -0
  33. data/lib/cased/policy.rb +24 -0
  34. data/lib/cased/publishers.rb +6 -0
  35. data/lib/cased/publishers/active_support_publisher.rb +16 -0
  36. data/lib/cased/publishers/base.rb +17 -0
  37. data/lib/cased/publishers/error.rb +11 -0
  38. data/lib/cased/publishers/http_publisher.rb +15 -0
  39. data/lib/cased/publishers/null_publisher.rb +11 -0
  40. data/lib/cased/publishers/test_publisher.rb +19 -0
  41. data/lib/cased/query.rb +87 -0
  42. data/lib/cased/rack_middleware.rb +15 -0
  43. data/lib/cased/response.rb +37 -0
  44. data/lib/cased/sensitive.rb +4 -0
  45. data/lib/cased/sensitive/handler.rb +54 -0
  46. data/lib/cased/sensitive/processor.rb +78 -0
  47. data/lib/cased/sensitive/range.rb +54 -0
  48. data/lib/cased/sensitive/result.rb +8 -0
  49. data/lib/cased/sensitive/string.rb +43 -0
  50. data/lib/cased/test_helper.rb +188 -0
  51. data/lib/cased/version.rb +5 -0
  52. data/vendor/cache/activesupport-6.0.3.4.gem +0 -0
  53. data/vendor/cache/addressable-2.7.0.gem +0 -0
  54. data/vendor/cache/ast-2.4.0.gem +0 -0
  55. data/vendor/cache/byebug-11.0.1.gem +0 -0
  56. data/vendor/cache/concurrent-ruby-1.1.7.gem +0 -0
  57. data/vendor/cache/connection_pool-2.2.2.gem +0 -0
  58. data/vendor/cache/crack-0.4.3.gem +0 -0
  59. data/vendor/cache/docile-1.3.2.gem +0 -0
  60. data/vendor/cache/dotpath-0.1.0.gem +0 -0
  61. data/vendor/cache/faraday-1.1.0.gem +0 -0
  62. data/vendor/cache/faraday_middleware-1.0.0.gem +0 -0
  63. data/vendor/cache/hashdiff-1.0.1.gem +0 -0
  64. data/vendor/cache/i18n-1.8.5.gem +0 -0
  65. data/vendor/cache/jaro_winkler-1.5.4.gem +0 -0
  66. data/vendor/cache/json-2.3.1.gem +0 -0
  67. data/vendor/cache/minitest-5.13.0.gem +0 -0
  68. data/vendor/cache/mocha-1.11.2.gem +0 -0
  69. data/vendor/cache/multipart-post-2.1.1.gem +0 -0
  70. data/vendor/cache/net-http-persistent-3.1.0.gem +0 -0
  71. data/vendor/cache/parallel-1.19.1.gem +0 -0
  72. data/vendor/cache/parser-2.7.1.3.gem +0 -0
  73. data/vendor/cache/public_suffix-4.0.5.gem +0 -0
  74. data/vendor/cache/rack-2.2.2.gem +0 -0
  75. data/vendor/cache/rack-protection-2.0.8.1.gem +0 -0
  76. data/vendor/cache/rainbow-3.0.0.gem +0 -0
  77. data/vendor/cache/rake-10.5.0.gem +0 -0
  78. data/vendor/cache/redis-4.1.4.gem +0 -0
  79. data/vendor/cache/rubocop-0.78.0.gem +0 -0
  80. data/vendor/cache/rubocop-performance-1.5.2.gem +0 -0
  81. data/vendor/cache/ruby-progressbar-1.10.1.gem +0 -0
  82. data/vendor/cache/ruby2_keywords-0.0.2.gem +0 -0
  83. data/vendor/cache/safe_yaml-1.0.5.gem +0 -0
  84. data/vendor/cache/sidekiq-6.0.7.gem +0 -0
  85. data/vendor/cache/simplecov-0.18.5.gem +0 -0
  86. data/vendor/cache/simplecov-html-0.12.2.gem +0 -0
  87. data/vendor/cache/thread_safe-0.3.6.gem +0 -0
  88. data/vendor/cache/tzinfo-1.2.7.gem +0 -0
  89. data/vendor/cache/unicode-display_width-1.6.1.gem +0 -0
  90. data/vendor/cache/webmock-3.8.3.gem +0 -0
  91. data/vendor/cache/yard-0.9.24.gem +0 -0
  92. data/vendor/cache/zeitwerk-2.4.0.gem +0 -0
  93. metadata +375 -0
@@ -0,0 +1,172 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Cased
4
+ class Config
5
+ # The amount of time in seconds to allow the HTTP client to open a
6
+ # connection.
7
+ #
8
+ # @example
9
+ # CASED_HTTP_OPEN_TIMEOUT="5" rails server
10
+ #
11
+ # @example
12
+ # Cased.configure do |config|
13
+ # config.http_open_timeout = 5
14
+ # end
15
+ attr_reader :http_open_timeout
16
+
17
+ # The amount of time in seconds to allow the HTTP client to read a response
18
+ # from the server before timing out.
19
+ #
20
+ # @example
21
+ # CASED_HTTP_READ_TIMEOUT="10" rails server
22
+ #
23
+ # @example
24
+ # Cased.configure do |config|
25
+ # config.http_read_timeout = 10
26
+ # end
27
+ attr_reader :http_read_timeout
28
+
29
+ # The Cased HTTP API URL. Defaults to https://api.cased.com
30
+ #
31
+ # @example
32
+ # CASED_API_URL="https://api.cased.com" rails server
33
+ #
34
+ # @example
35
+ # Cased.configure do |config|
36
+ # config.api_url = "https://api.cased.com"
37
+ # end
38
+ attr_accessor :api_url
39
+
40
+ # The URL to publish audit events to. Defaults to https://publish.cased.com
41
+ #
42
+ # @example
43
+ # CASED_PUBLISH_URL="https://publish.cased.com" rails server
44
+ #
45
+ # @example
46
+ # Cased.configure do |config|
47
+ # config.publish_url = "https://publish.cased.com"
48
+ # end
49
+ attr_accessor :publish_url
50
+
51
+ # Publish keys are used to publish to an audit trail.
52
+ #
53
+ # A publish key is associated with a single audit trail and is required if
54
+ # you intend to publish events to Cased in your application.
55
+ #
56
+ # @example
57
+ # CASED_PUBLISH_KEY="publish_test_5dSfh6xZAuL2Esn3Z2XSM6ReMS21" rails server
58
+ #
59
+ # @example
60
+ # Cased.configure do |config|
61
+ # config.publish_key = "publish_test_5dSfh6xZAuL2Esn3Z2XSM6ReMS21"
62
+ # end
63
+ attr_accessor :publish_key
64
+
65
+ #
66
+ # @example
67
+ # CASED_ORGANIZATION_POLICY_KEY="policy_live_1dQpY2mRu3pTCcNGB7a6ewx4WFp" \
68
+ # CASED_SECURITY_POLICY_KEY="policy_live_1dQpY9Erf3cKUrooz0BQKmCD4Oc" \
69
+ # CASED_USER_POLICY_KEY="policy_live_1dSHQRbtjj17LanuAgpHd3QKtOO" \
70
+ # rails server
71
+ #
72
+ # @example
73
+ # Cased.configure do |config|
74
+ # config.policy_keys = {
75
+ # organization: "policy_live_1dQpY2mRu3pTCcNGB7a6ewx4WFp",
76
+ # security: "policy_live_1dQpY9Erf3cKUrooz0BQKmCD4Oc",
77
+ # user: "policy_live_1dSHQRbtjj17LanuAgpHd3QKtOO",
78
+ # }
79
+ # end
80
+ attr_reader :policy_keys
81
+
82
+ # Policy keys are used to query for events from audit trails.
83
+ #
84
+ # @example
85
+ # CASED_POLICY_KEY="policy_live_1dQpY1fUFHzENWhTVMvjCilAKp9" rails server
86
+ #
87
+ # @example
88
+ # Cased.configure do |config|
89
+ # config.raise_on_errors = !Rails.env.production?
90
+ # end
91
+ attr_reader :raise_on_errors
92
+
93
+ # Configure whether or not Cased will attempt to publish any events.
94
+ #
95
+ # If the CASED_SILENCE environment variable is not nil Cased will
96
+ # not publish events.
97
+ #
98
+ # @example
99
+ # CASED_SILENCE="1" rails server
100
+ #
101
+ # @example
102
+ # Cased.configure do |config|
103
+ # config.silence = Rails.env.test?
104
+ # end
105
+ #
106
+ # @example
107
+ # Cased.silence do
108
+ # User.create!
109
+ # end
110
+ attr_writer :silence
111
+
112
+ def initialize
113
+ @http_read_timeout = ENV.fetch('CASED_HTTP_READ_TIMEOUT', 10).to_i
114
+ @http_open_timeout = ENV.fetch('CASED_HTTP_OPEN_TIMEOUT', 5).to_i
115
+ @raise_on_errors = !ENV['CASED_RAISE_ON_ERRORS'].nil?
116
+ @api_url = ENV.fetch('CASED_API_URL', 'https://api.cased.com')
117
+ @publish_url = ENV.fetch('CASED_PUBLISH_URL', 'https://publish.cased.com')
118
+ @publish_key = ENV['CASED_PUBLISH_KEY']
119
+ @silence = !ENV['CASED_SILENCE'].nil?
120
+ @policy_keys = Hash.new do |hash, key|
121
+ normalized_key = key.to_sym
122
+ if normalized_key == :default
123
+ hash[normalized_key] = ENV['CASED_POLICY_KEY']
124
+ else
125
+ env_policy_name = key.to_s.tr(' ', '_').tr('-', '_').upcase
126
+ api_key = ENV["CASED_#{env_policy_name}_POLICY_KEY"]
127
+
128
+ hash[normalized_key] = api_key if api_key
129
+ end
130
+ end
131
+ end
132
+
133
+ # Policy keys are used to query for events from audit trails.
134
+ #
135
+ # @param [Symbol] policy name
136
+ #
137
+ # @example
138
+ # CASED_POLICY_KEY="policy_live_1dQpY1fUFHzENWhTVMvjCilAKp9" rails server
139
+ #
140
+ # @example
141
+ # Cased.configure do |config|
142
+ # config.policy_key = "policy_live_1dQpY1fUFHzENWhTVMvjCilAKp9"
143
+ # end
144
+ def policy_key(policy = :default)
145
+ policy_keys[policy.to_sym]
146
+ end
147
+
148
+ def policy_key=(api_key)
149
+ policy_keys[:default] = api_key
150
+ end
151
+
152
+ def raise_on_errors=(new_val)
153
+ @raise_on_errors = !!new_val
154
+ end
155
+
156
+ def http_read_timeout=(new_val)
157
+ @http_read_timeout = new_val.to_i
158
+ end
159
+
160
+ def http_open_timeout=(new_val)
161
+ @http_open_timeout = new_val.to_i
162
+ end
163
+
164
+ def raise_on_errors?
165
+ @raise_on_errors
166
+ end
167
+
168
+ def silence?
169
+ @silence || !ENV['CASED_SILENCE'].nil?
170
+ end
171
+ end
172
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'cased/context/expander'
4
+ require 'active_support/core_ext/hash/deep_merge'
5
+
6
+ module Cased
7
+ class Context
8
+ def self.current
9
+ Thread.current[:cased_context] ||= new
10
+ end
11
+
12
+ def self.current=(context)
13
+ Thread.current[:cased_context] = new(context)
14
+ end
15
+
16
+ def self.clear!
17
+ Thread.current[:cased_context] = nil
18
+ end
19
+
20
+ attr_reader :context
21
+
22
+ def initialize(context = {})
23
+ @context = Cased::Context::Expander.expand(context || {})
24
+ end
25
+
26
+ def clear
27
+ @context = {}
28
+ end
29
+
30
+ def merge(new_context = {})
31
+ if block_given?
32
+ old_context = @context.dup
33
+ @context.deep_merge!(Cased::Context::Expander.expand(new_context))
34
+ yield
35
+ else
36
+ @context.deep_merge!(Cased::Context::Expander.expand(new_context))
37
+ end
38
+ ensure
39
+ @context = old_context if block_given?
40
+ end
41
+
42
+ def [](key)
43
+ @context[key]
44
+ end
45
+
46
+ def []=(key, value)
47
+ merge(key => value)
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Cased
4
+ class Context
5
+ class Expander
6
+ def self.expand(payload)
7
+ return {} if payload.nil?
8
+ return payload unless payload.respond_to?(:each)
9
+
10
+ cased_payload = payload.dup
11
+ payload.each do |key, value|
12
+ if value.respond_to?(:cased_context)
13
+ cased_payload.delete(key)
14
+ cased_payload.update(value.cased_context(category: key))
15
+ elsif value.is_a?(Hash)
16
+ cased_payload[key] = expand(value)
17
+ elsif value.is_a?(Array)
18
+ values = value.collect do |val|
19
+ if val.respond_to?(:cased_context)
20
+ val.cased_context
21
+ else
22
+ val
23
+ end
24
+ end
25
+ cased_payload.update(key => values)
26
+ end
27
+ end
28
+
29
+ cased_payload
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Cased
4
+ class Error < StandardError
5
+ SystemActorMissing = Class.new(self)
6
+ MissingIdentifier = Class.new(self)
7
+ end
8
+ end
@@ -0,0 +1,83 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'cased/http/error'
4
+ require 'faraday'
5
+ require 'faraday_middleware'
6
+
7
+ module Cased
8
+ module HTTP
9
+ class Client
10
+ def initialize(url:, api_key:)
11
+ @client = Faraday.new(url: url) do |conn|
12
+ conn.headers[:user_agent] = "cased-ruby/v#{Cased::VERSION}"
13
+ conn.headers[:content_type] = 'application/json'
14
+ conn.headers[:accept] = 'application/json'
15
+ conn.headers[:authorization] = "Bearer #{api_key}"
16
+
17
+ conn.request :json
18
+ conn.response :json, content_type: /\bjson$/
19
+
20
+ conn.options.timeout = Cased.config.http_read_timeout
21
+ conn.options.open_timeout = Cased.config.http_open_timeout
22
+
23
+ conn.adapter :net_http_persistent
24
+ end
25
+ end
26
+
27
+ # Requests with bodies
28
+
29
+ def put(url = nil, body = nil, headers = nil, &block)
30
+ request(:put, url, body, headers, &block)
31
+ end
32
+
33
+ def post(url = nil, body = nil, headers = nil, &block)
34
+ request(:post, url, body, headers, &block)
35
+ end
36
+
37
+ def patch(url = nil, body = nil, headers = nil, &block)
38
+ request(:patch, url, body, headers, &block)
39
+ end
40
+
41
+ # Requests without bodies
42
+
43
+ def get(url = nil, params = nil, headers = nil)
44
+ request(:get, url, nil, headers) do |req|
45
+ req.params.update(params) if params
46
+ yield req if block_given?
47
+ end
48
+ end
49
+
50
+ def head(url = nil, params = nil, headers = nil)
51
+ request(:head, url, nil, headers) do |req|
52
+ req.params.update(params) if params
53
+ yield req if block_given?
54
+ end
55
+ end
56
+
57
+ def delete(url = nil, params = nil, headers = nil)
58
+ request(:delete, url, nil, headers) do |req|
59
+ req.params.update(params) if params
60
+ yield req if block_given?
61
+ end
62
+ end
63
+
64
+ def trace(url = nil, params = nil, headers = nil)
65
+ request(:trace, url, nil, headers) do |req|
66
+ req.params.update(params) if params
67
+ yield req if block_given?
68
+ end
69
+ end
70
+
71
+ private
72
+
73
+ def request(method, url = nil, params_or_body = nil, headers = nil, &block)
74
+ response = @client.send(method, url, params_or_body, headers, &block)
75
+ return response if response.success?
76
+
77
+ # TODO: Look to return a Cased::Response here with error wrapped.
78
+ klass = Cased::HTTP::Error.class_from_response(response)
79
+ raise klass.from_response(response)
80
+ end
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,99 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'cased/error'
4
+
5
+ module Cased
6
+ module HTTP
7
+ class Error < Cased::Error
8
+ attr_reader :code
9
+
10
+ def initialize(message = '', code = nil)
11
+ super(message)
12
+ @code = code
13
+ end
14
+
15
+ def self.from_response(response)
16
+ new(response.body, response.status)
17
+ end
18
+
19
+ def self.class_from_response(response)
20
+ klass = ERRORS[response.status]
21
+
22
+ if klass
23
+ klass
24
+ elsif (300...400).cover?(response.status)
25
+ RedirectionError
26
+ elsif (400...500).cover?(response.status)
27
+ ClientError
28
+ elsif (500...600).cover?(response.status)
29
+ ServerError
30
+ else
31
+ self
32
+ end
33
+ end
34
+
35
+ # 3xx
36
+ RedirectionError = Class.new(self)
37
+
38
+ # 4xx
39
+ ClientError = Class.new(self)
40
+
41
+ # 400
42
+ BadRequest = Class.new(ClientError)
43
+
44
+ # 401
45
+ Unauthorized = Class.new(ClientError)
46
+
47
+ # 403
48
+ Forbidden = Class.new(ClientError)
49
+
50
+ # 404
51
+ NotFound = Class.new(ClientError)
52
+
53
+ # 406
54
+ NotAcceptable = Class.new(ClientError)
55
+
56
+ # 408
57
+ RequestTimeout = Class.new(ClientError)
58
+
59
+ # 409
60
+ Conflict = Class.new(ClientError)
61
+
62
+ # 422
63
+ UnprocessableEntity = Class.new(ClientError)
64
+
65
+ # 429
66
+ TooManyRequests = Class.new(ClientError)
67
+
68
+ # 5xx
69
+ ServerError = Class.new(self)
70
+
71
+ # 500
72
+ InternalServerError = Class.new(ServerError)
73
+
74
+ # 502
75
+ BadGateway = Class.new(ServerError)
76
+
77
+ # 503
78
+ ServiceUnavailable = Class.new(ServerError)
79
+
80
+ # 504
81
+ GatewayTimeout = Class.new(ServerError)
82
+
83
+ ERRORS = {
84
+ 400 => BadRequest,
85
+ 401 => Unauthorized,
86
+ 403 => Forbidden,
87
+ 404 => NotFound,
88
+ 406 => NotAcceptable,
89
+ 408 => RequestTimeout,
90
+ 422 => UnprocessableEntity,
91
+ 429 => TooManyRequests,
92
+ 500 => InternalServerError,
93
+ 502 => BadGateway,
94
+ 503 => ServiceUnavailable,
95
+ 504 => GatewayTimeout,
96
+ }.freeze
97
+ end
98
+ end
99
+ end