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.
- checksums.yaml +7 -0
- data/.github/workflows/rubocop.yml +21 -0
- data/.github/workflows/ruby.yml +46 -0
- data/.gitignore +10 -0
- data/.rubocop.yml +88 -0
- data/.ruby-version +1 -0
- data/.travis.yml +7 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +8 -0
- data/Gemfile.lock +107 -0
- data/LICENSE +21 -0
- data/README.md +661 -0
- data/Rakefile +12 -0
- data/bin/console +15 -0
- data/bin/rubocop +29 -0
- data/cased-ruby.gemspec +48 -0
- data/lib/cased-ruby.rb +3 -0
- data/lib/cased.rb +260 -0
- data/lib/cased/clients.rb +21 -0
- data/lib/cased/collection_response.rb +117 -0
- data/lib/cased/config.rb +172 -0
- data/lib/cased/context.rb +50 -0
- data/lib/cased/context/expander.rb +33 -0
- data/lib/cased/error.rb +8 -0
- data/lib/cased/http/client.rb +83 -0
- data/lib/cased/http/error.rb +99 -0
- data/lib/cased/instrumentation/controller.rb +34 -0
- data/lib/cased/instrumentation/log_subscriber.rb +31 -0
- data/lib/cased/integrations/sidekiq.rb +17 -0
- data/lib/cased/integrations/sidekiq/client_middleware.rb +14 -0
- data/lib/cased/integrations/sidekiq/server_middleware.rb +20 -0
- data/lib/cased/model.rb +98 -0
- data/lib/cased/policy.rb +24 -0
- data/lib/cased/publishers.rb +6 -0
- data/lib/cased/publishers/active_support_publisher.rb +16 -0
- data/lib/cased/publishers/base.rb +17 -0
- data/lib/cased/publishers/error.rb +11 -0
- data/lib/cased/publishers/http_publisher.rb +15 -0
- data/lib/cased/publishers/null_publisher.rb +11 -0
- data/lib/cased/publishers/test_publisher.rb +19 -0
- data/lib/cased/query.rb +87 -0
- data/lib/cased/rack_middleware.rb +15 -0
- data/lib/cased/response.rb +37 -0
- data/lib/cased/sensitive.rb +4 -0
- data/lib/cased/sensitive/handler.rb +54 -0
- data/lib/cased/sensitive/processor.rb +78 -0
- data/lib/cased/sensitive/range.rb +54 -0
- data/lib/cased/sensitive/result.rb +8 -0
- data/lib/cased/sensitive/string.rb +43 -0
- data/lib/cased/test_helper.rb +188 -0
- data/lib/cased/version.rb +5 -0
- data/vendor/cache/activesupport-6.0.3.4.gem +0 -0
- data/vendor/cache/addressable-2.7.0.gem +0 -0
- data/vendor/cache/ast-2.4.0.gem +0 -0
- data/vendor/cache/byebug-11.0.1.gem +0 -0
- data/vendor/cache/concurrent-ruby-1.1.7.gem +0 -0
- data/vendor/cache/connection_pool-2.2.2.gem +0 -0
- data/vendor/cache/crack-0.4.3.gem +0 -0
- data/vendor/cache/docile-1.3.2.gem +0 -0
- data/vendor/cache/dotpath-0.1.0.gem +0 -0
- data/vendor/cache/faraday-1.1.0.gem +0 -0
- data/vendor/cache/faraday_middleware-1.0.0.gem +0 -0
- data/vendor/cache/hashdiff-1.0.1.gem +0 -0
- data/vendor/cache/i18n-1.8.5.gem +0 -0
- data/vendor/cache/jaro_winkler-1.5.4.gem +0 -0
- data/vendor/cache/json-2.3.1.gem +0 -0
- data/vendor/cache/minitest-5.13.0.gem +0 -0
- data/vendor/cache/mocha-1.11.2.gem +0 -0
- data/vendor/cache/multipart-post-2.1.1.gem +0 -0
- data/vendor/cache/net-http-persistent-3.1.0.gem +0 -0
- data/vendor/cache/parallel-1.19.1.gem +0 -0
- data/vendor/cache/parser-2.7.1.3.gem +0 -0
- data/vendor/cache/public_suffix-4.0.5.gem +0 -0
- data/vendor/cache/rack-2.2.2.gem +0 -0
- data/vendor/cache/rack-protection-2.0.8.1.gem +0 -0
- data/vendor/cache/rainbow-3.0.0.gem +0 -0
- data/vendor/cache/rake-10.5.0.gem +0 -0
- data/vendor/cache/redis-4.1.4.gem +0 -0
- data/vendor/cache/rubocop-0.78.0.gem +0 -0
- data/vendor/cache/rubocop-performance-1.5.2.gem +0 -0
- data/vendor/cache/ruby-progressbar-1.10.1.gem +0 -0
- data/vendor/cache/ruby2_keywords-0.0.2.gem +0 -0
- data/vendor/cache/safe_yaml-1.0.5.gem +0 -0
- data/vendor/cache/sidekiq-6.0.7.gem +0 -0
- data/vendor/cache/simplecov-0.18.5.gem +0 -0
- data/vendor/cache/simplecov-html-0.12.2.gem +0 -0
- data/vendor/cache/thread_safe-0.3.6.gem +0 -0
- data/vendor/cache/tzinfo-1.2.7.gem +0 -0
- data/vendor/cache/unicode-display_width-1.6.1.gem +0 -0
- data/vendor/cache/webmock-3.8.3.gem +0 -0
- data/vendor/cache/yard-0.9.24.gem +0 -0
- data/vendor/cache/zeitwerk-2.4.0.gem +0 -0
- 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,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
|
data/lib/cased/model.rb
ADDED
|
@@ -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
|
data/lib/cased/policy.rb
ADDED
|
@@ -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,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,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,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
|
data/lib/cased/query.rb
ADDED
|
@@ -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
|