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,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative './log_subscriber'
4
+
5
+ module Cased
6
+ module Instrumentation
7
+ module Controller
8
+ extend ActiveSupport::Concern
9
+
10
+ module ClassMethods
11
+ def log_process_action(payload)
12
+ messages = super
13
+ count = payload[:cased_events]
14
+ if count
15
+ messages << format('Cased: %<count>d %<suffix>s', count: count, suffix: 'event'.pluralize(count))
16
+ end
17
+ messages
18
+ end
19
+ end
20
+
21
+ protected
22
+
23
+ def process_action(action, *args)
24
+ Cased::Instrumentation::LogSubscriber.reset_events
25
+ super
26
+ end
27
+
28
+ def append_info_to_payload(payload)
29
+ super
30
+ payload[:cased_events] = Cased::Instrumentation::LogSubscriber.events
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_support/log_subscriber'
4
+
5
+ module Cased
6
+ module Instrumentation
7
+ class LogSubscriber < ActiveSupport::LogSubscriber
8
+ def self.events=(value)
9
+ Thread.current['cased_events'] = value
10
+ end
11
+
12
+ def self.events
13
+ Thread.current['cased_events'] ||= 0
14
+ end
15
+
16
+ def self.reset_events
17
+ self.events = 0
18
+ end
19
+
20
+ def audit_event(event)
21
+ self.class.events += 1
22
+
23
+ event = JSON.generate(event.payload[:event])
24
+ name = color('Cased', CYAN, true)
25
+ debug " #{name} #{event}"
26
+ end
27
+
28
+ attach_to :cased
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'sidekiq'
4
+ require 'cased/integrations/sidekiq/client_middleware'
5
+ require 'cased/integrations/sidekiq/server_middleware'
6
+
7
+ Sidekiq.configure_client do |config|
8
+ config.client_middleware do |chain|
9
+ chain.add Cased::Integrations::Sidekiq::ClientMiddleware
10
+ end
11
+ end
12
+
13
+ Sidekiq.configure_server do |config|
14
+ config.server_middleware do |chain|
15
+ chain.add Cased::Integrations::Sidekiq::ServerMiddleware
16
+ end
17
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Cased
4
+ module Integrations
5
+ module Sidekiq
6
+ class ClientMiddleware
7
+ def call(_worker_class, job, _queue, _redis_pool)
8
+ job['cased_context'] = Cased.context.context
9
+ yield
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Cased
4
+ module Integrations
5
+ module Sidekiq
6
+ class ServerMiddleware
7
+ def call(_worker, job, _queue)
8
+ context = (job['cased_context'] || {})
9
+ context['job_class'] = job['class']
10
+
11
+ Cased::Context.current = context
12
+
13
+ yield
14
+ ensure
15
+ Cased::Context.clear!
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,98 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_support/concern'
4
+ require 'active_support/inflector'
5
+ require 'active_support/core_ext/hash/deep_merge'
6
+ require 'cased/context'
7
+
8
+ module Cased
9
+ module Model
10
+ extend ActiveSupport::Concern
11
+
12
+ module ClassMethods
13
+ # Establishes a default `cased_category` that matches nicely to
14
+ # the class name. The default instance level `cased_category` uses this.
15
+ #
16
+ # @return [Symbol]
17
+ def cased_category
18
+ @cased_category ||= begin
19
+ category = ActiveSupport::Inflector.underscore(name)
20
+ category.to_sym
21
+ end
22
+ end
23
+ end
24
+
25
+ # Instruments events for the model. These events are sent directly to Cased.
26
+ #
27
+ # @param action [String, Symbol] suffix of the action.
28
+ # @param category [String, Symbol] action category name.
29
+ # @param payload [Hash] additional payload information about the event.
30
+ #
31
+ # @return [Array] of responses from Cased.publishers
32
+ def cased(action, category: cased_category, payload: {})
33
+ body = cased_payload.deep_merge(payload)
34
+
35
+ Cased.publish(body.merge(action: "#{category}.#{action}"))
36
+ end
37
+
38
+ # Defines the event key prefix for all model events.
39
+ #
40
+ # Defaults to :my_model if the class this thing is included in is MyModel.
41
+ # Feel free to override to provide a more semantic, meaningful name
42
+ # if you so desire.
43
+ #
44
+ # @return [String]
45
+ def cased_category
46
+ self.class.cased_category
47
+ end
48
+
49
+ # Defines the default payload for every event.
50
+ #
51
+ # @return [Hash]
52
+ def cased_payload
53
+ {
54
+ cased_category => self,
55
+ }
56
+ end
57
+
58
+ # Defines the Cased identifier for the current instance.
59
+ #
60
+ # @example
61
+ # user.cased_id # => User;1
62
+ #
63
+ # @example
64
+ # client.cased_id # => Client;1
65
+ #
66
+ # @raise [Cased::Error::MissingIdentifier] description
67
+ # @return [String]
68
+ def cased_id; end
69
+
70
+ # Internal: The String representation within the Cased object.
71
+ #
72
+ # @return [String] if cased_category key is present in cased_context.
73
+ def cased_human
74
+ cased_context[cased_category]
75
+ end
76
+
77
+ # Internal: Defines the payload to describe the current subject.
78
+ # Can be overridden in model to change fields returned.
79
+ #
80
+ # @param [String, Symbol] category prefix that can be set to override Hash prefix.
81
+ #
82
+ # @example
83
+ # model.cased_context # => { user_id: "User;1", user: "flynn" }
84
+ # model.cased_context(category: :actor) # => { actor_id: "User;1", actor: "flynn" }
85
+ #
86
+ # @return [Hash]
87
+ def cased_context(category: cased_category)
88
+ context = {}
89
+ context["#{category}_id".to_sym] = cased_id if cased_id
90
+
91
+ if method(:to_s).owner == self.class
92
+ context[category] = Cased::Sensitive::String.new(to_s, label: self.class.name)
93
+ end
94
+
95
+ context
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'cased/query'
4
+
5
+ module Cased
6
+ class Policy
7
+ attr_reader :api_key
8
+ attr_reader :client
9
+
10
+ def initialize(api_key:)
11
+ @api_key = api_key
12
+ @client = Cased::Clients.create(api_key: @api_key)
13
+ end
14
+
15
+ def events(phrase: nil, variables: {})
16
+ Query.new(@client, phrase: phrase, variables: variables)
17
+ end
18
+
19
+ def event(id)
20
+ response = @client.get("events/#{id}")
21
+ response.body
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'cased/publishers/active_support_publisher'
4
+ require 'cased/publishers/http_publisher'
5
+ require 'cased/publishers/null_publisher'
6
+ require 'cased/publishers/test_publisher'
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ # ActiveSupport::Notifications will fail if concurrent isn't loaded
4
+ require 'concurrent'
5
+ require 'active_support/notifications'
6
+ require 'cased/publishers/base'
7
+
8
+ module Cased
9
+ module Publishers
10
+ class ActiveSupportPublisher < Base
11
+ def publish(event)
12
+ ::ActiveSupport::Notifications.instrument('event.cased', event: event)
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'cased/publishers/error'
4
+
5
+ module Cased
6
+ module Publishers
7
+ class Base
8
+ def publish(_audit_event)
9
+ raise "#{self.class} must implement the #{self.class}#publish method"
10
+ end
11
+
12
+ def ==(other)
13
+ self.class == other.class
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'cased/error'
4
+
5
+ module Cased
6
+ module Publishers
7
+ # Public: Standard exception class all Cased publisher errors inherit from.
8
+ class Error < Cased::Error
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'cased/publishers/base'
4
+
5
+ module Cased
6
+ module Publishers
7
+ class HTTPPublisher < Base
8
+ def publish(audit_event)
9
+ Cased.clients.publish.post do |req|
10
+ req.body = JSON.generate(audit_event)
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'cased/publishers/base'
4
+
5
+ module Cased
6
+ module Publishers
7
+ class NullPublisher < Base
8
+ def publish(_event); end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'cased/publishers/base'
4
+
5
+ module Cased
6
+ module Publishers
7
+ class TestPublisher < Base
8
+ attr_reader :events
9
+
10
+ def initialize
11
+ @events = []
12
+ end
13
+
14
+ def publish(event)
15
+ @events << event
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,87 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'cased/collection_response'
4
+
5
+ module Cased
6
+ class Query
7
+ # @param client [Cased::HTTP::Client] the HTTP client authorized to query an
8
+ # audit trail policy
9
+ # @param phrase [String, nil] the phrase to search for audit trail events
10
+ # @param variables [Hash] the query variables
11
+ def initialize(client, phrase: nil, variables: {})
12
+ raise ArgumentError, 'variables must be a Hash' unless variables.is_a?(Hash)
13
+
14
+ @client = client
15
+ @phrase = phrase
16
+ @page = 1
17
+ @limit = 25
18
+ @variables = variables
19
+ end
20
+
21
+ # @param new_phrase [String] The audit trail policy search phrase.
22
+ # @return [Cased::Query]
23
+ def phrase(new_phrase)
24
+ @phrase = new_phrase
25
+
26
+ self
27
+ end
28
+
29
+ # @param new_limit [Integer] The number of audit trail events to return.
30
+ # @return [Cased::Query]
31
+ def limit(new_limit)
32
+ @limit = [[new_limit, 100].min, 1].max
33
+
34
+ self
35
+ end
36
+
37
+ # @param new_page [Integer] The page of audit trail events to request.
38
+ # @return [Cased::Query]
39
+ def page(new_page)
40
+ @page = [1, new_page.to_i].max
41
+
42
+ self
43
+ end
44
+
45
+ # If any of these methods are called we need the request to fulfil the response.
46
+ delegate :results, \
47
+ :total_count, \
48
+ :total_pages, \
49
+ :next_page_url?, \
50
+ :next_page_url, \
51
+ :next_page, \
52
+ :next_page?, \
53
+ :previous_page_url?, \
54
+ :previous_page_url, \
55
+ :previous_page, \
56
+ :previous_page?, \
57
+ :first_page_url?, \
58
+ :first_page_url, \
59
+ :first_page, \
60
+ :first_page?, \
61
+ :last_page_url?, \
62
+ :last_page_url, \
63
+ :last_page, \
64
+ :last_page?, \
65
+ :error, \
66
+ :error?, \
67
+ :success?, \
68
+ to: :response
69
+
70
+ def response
71
+ return @response if defined?(@response)
72
+
73
+ @response = begin
74
+ resp = @client.get('events') do |req|
75
+ req.params['phrase'] = @phrase unless @phrase.nil?
76
+ req.params['per_page'] = @limit unless @limit.nil?
77
+ req.params['page'] = @page unless @page.nil?
78
+ req.params['variables'] = @variables unless @variables.nil?
79
+ end
80
+
81
+ CollectionResponse.new(response: resp)
82
+ rescue Cased::HTTP::Error, Faraday::Error => e
83
+ CollectionResponse.new(exception: e)
84
+ end
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Cased
4
+ class RackMiddleware
5
+ def initialize(app)
6
+ @app = app
7
+ end
8
+
9
+ def call(env)
10
+ @app.call(env)
11
+ ensure
12
+ Cased::Context.clear!
13
+ end
14
+ end
15
+ end