rails_surrogate_key_logging 0.1.0 → 0.2.1

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
  SHA256:
3
- metadata.gz: df6c120ebe49e9724f0c2324feefd62dc61c317b87c118e438d3735364193897
4
- data.tar.gz: a33a587bdc49075fcf20917797a9a631702ff4b52bd54f021e0088f383e36d3a
3
+ metadata.gz: 4f209a8f18098917e60dbd9dbe93a1d6367eeaf16e2da104bbbba0351fb09956
4
+ data.tar.gz: 0d70d2d6ed37c4981c286fbed4f409f397babd3695a90141a886d24b19ba2634
5
5
  SHA512:
6
- metadata.gz: f5020c84352f1dc337ae18b2a8e4f6c8a576bea91ecdaf710fe9d0402fc31f82c6c865a23dee02269e45f7e3faa8e72208f8f64764357b765591a97ae8d5157f
7
- data.tar.gz: e7c60aeebd180e87480c3e5857d94cd3926cb945545cc8c0a56f4bc5735dcd4c40b075aae1373e098a8186c3a641d5a3041582f88d42ae5740ebee6ed11c7abc
6
+ metadata.gz: c12692d51a77d61a606193aee29f77016f2386076c6334e35f8dfeb324a52f3caed7860a3ea83c44f2a91c861cd379eb8d2ed21002ace9732f5e9b8acfebe6fb
7
+ data.tar.gz: 969985b95d785c04f0c90042e57ce3bf2826d529adf96aac611994fa061a7c254175e4bcaa13eee4f39f18e95000bbd2ddaa268547d1317f6893d3e24a43d5db
data/README.md CHANGED
@@ -1 +1,134 @@
1
1
  # rails-surrogate-key-logging
2
+
3
+ This gem enhances and uses Rails' built-in `ParameterFilter` to add "Surrogate Key" logging.
4
+
5
+ ## Installation
6
+
7
+ - Add `gem :rails_surrogate_key_logging` to your Gemfile.
8
+ - Run `bin/bundle install`
9
+ - Add `include SurrogateKeyLogging::ActionController::Params` to your `ApplicationController`
10
+ - Add `include SurrogateKeyLogging::ActiveRecord::Attributes` to your `ApplicationRecord`
11
+
12
+
13
+
14
+ ## Configuration
15
+
16
+ In a new application initializer (`config/initializers/surrogate_key_logging.rb`) or in your `config/environments/*.rb`, use the following block:
17
+
18
+ ```ruby
19
+ SurrogateKeyLogging.configure do |config|
20
+ config.key = value
21
+ end
22
+ ```
23
+
24
+ ### Config
25
+
26
+ Key | Type | Default | Description
27
+ ---|---|---|---
28
+ `enabled` | Boolean | `Rails.env.production?` | Whether surrogate logging is injected into Rails.
29
+ `debug` | Boolean | `false` | Whether to log a statement showing that a surrogate replacement happened and what the mapping from surrogate to value, and logs from the key store (Such as queries made by ActiveRecord to it's Surrogate model).
30
+ `key_prefix` | String | `''` | This string will be prepended to generated surrogates. Can make it easier to identify a surrogate in logs.
31
+ `key_for` | Proc \| Lambda \| `responds_to?(:call)` | `-> (value) { "#{config.key_prefix}#{SecureRandom.uuid}" }` | The method used to generate a surrogate for a given value. While the `value` is supplied to the method, it is generally considered insecure for the surrogate to be derivable from it's value.
32
+ `cache` | Boolean | `true` | Should the key mananger maintain an in-memory cache of value -> surrogate map that have been used. When in a server context, this cache will last for the lifetime of a single request. The cache can also be manually busted at any time by calling `SurrogateKeyLogging.reset!`.
33
+ `cache_key_for` | Proc \| Lambda \| `responds_to?(:call)` | `-> (value) { value }` | The method used to create the keys used in the cache. Typically this should be left to the default unless you expect to make many surrogates for very large values.
34
+ `key_ttl` | Integer | `90.days` | Used by `bin/rails skl:clear:stale` to delete old surrogates.
35
+ `key_store` | Symbol | None | The key store to use. See [Key Stores](#key-stores).
36
+
37
+
38
+
39
+ ## Key Stores
40
+
41
+ Key Store | Config Value
42
+ ---|---
43
+ [ActiveRecord](#active-record) | `:active_record`
44
+
45
+ ### Active Record
46
+
47
+ This will use a `SurrogateKeyLogging::Surrogate` model to manage surrogates. This will require adding `surrogate_key_logging_#{Rails.env}` to your application's `config/database.yml` See [Example](#example-database-yml). After configuring your `config/database.yml` you will need to run `bin/rails skl:key_store:active_record:db:create` and `bin/rails skl:key_store:active_record:db:migrate`.
48
+
49
+ #### Example database.yml
50
+ ```yml
51
+ default: &default
52
+ adapter: mysql2
53
+ username: <%= Rails.application.credentials.database[:username] %>
54
+ password: <%= Rails.application.credentials.database[:password] %>
55
+ host: 127.0.0.1
56
+ port: 3306
57
+ database: myapp_<%= Rails.env %>
58
+ prepared_statements: true
59
+
60
+ surrogate_key_logging_default: &surrogate_key_logging_default
61
+ <<: *default
62
+ database: surrogate_keys_<%= Rails.env %>
63
+
64
+
65
+
66
+ development:
67
+ <<: *default
68
+
69
+ test:
70
+ <<: *default
71
+
72
+ production:
73
+ <<: *default
74
+
75
+
76
+
77
+ surrogate_key_logging_development:
78
+ <<: *surrogate_key_logging_default
79
+
80
+ surrogate_key_logging_test:
81
+ <<: *surrogate_key_logging_default
82
+
83
+ surrogate_key_logging_production:
84
+ <<: *surrogate_key_logging_default
85
+ ```
86
+
87
+
88
+
89
+ ## Usage
90
+
91
+ ### Controllers
92
+
93
+ In any controller including `SurrogateKeyLogging::ActionController::Params` you may use the `surrogate_params(*params, action: '*')` method. This method may be used multiple times. Pass the `action` argument to limit those `params` to only that `action` or omit it to apply those `params` to ALL actions in that controller.
94
+
95
+ #### Params format
96
+ Param | Examples | Output
97
+ ---|---|---
98
+ `:foo` | `{ foo: 'bar1', another: {foo: 'baz1'}, foobar: 'barbaz' }` | `{foo: SURROGATE, another: { foo: SURROGATE }, foobar: 'barbaz' }`
99
+ `'another.foo'` | `{ foo: 'bar1', another: { foo: 'baz1' }, foobar: { another: { foo: 'barbaz' } } }` | `{ foo: 'bar1', another: { foo: SURROGATE }, foobar: { another: { foo: SURROGATE } } }`
100
+ `'another[foo]'` | `{ foo: 'bar1', another: { foo: 'baz1' }, foobar: { another: { foo: 'barbaz' } } }` | `{ foo: 'bar1', another: { foo: SURROGATE }, foobar: { another: { foo: 'barbaz' } } }`
101
+
102
+ #### Example Controller
103
+ ```ruby
104
+ class WidgetsController < ApplicationController
105
+ surrogate_params :name
106
+ surrogate_params :owner, action: :search
107
+
108
+ def name
109
+ ...
110
+ end
111
+
112
+ def search
113
+ ...
114
+ end
115
+ end
116
+ ```
117
+
118
+ In this example the `name` parameter will be surrogated in all requests to this controller, and the `owner` parameter will surrogated only in requests to the `search` action.
119
+
120
+
121
+
122
+ ### Models
123
+
124
+ In any controller including `SurrogateKeyLogging::ActiveRecord::Attributes` you may use `surrogate_parent_names(*names)` and `surrogate_attributes(*attrs)`. All permutations of parent names to attributes will be used to create filters. By default `surrogate_parent_names` is initialized with the singular and plural names of the model.
125
+
126
+ #### Example Model
127
+ ```ruby
128
+ class Widget < ApplicationRecord
129
+ surrogate_parent_names :things
130
+ surrogate_attributes :name, :owner
131
+ end
132
+ ```
133
+
134
+ In this example, the following filters will be used to look for attributes to be surrogated: `widget.name`, `widget[name]`, `[widget][name]`, `widgets.name`, `widgets[name]`, `[widgets][name]`, `things.name`, `things[name]`, `[things][name]`
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_support/concern'
4
+
5
+ module SurrogateKeyLogging
6
+ module ActionController
7
+ module Params
8
+ extend ActiveSupport::Concern
9
+
10
+ class_methods do
11
+ def surrogate_params(*params, action: '*')
12
+ @surrogate_params ||= ActiveSupport::HashWithIndifferentAccess.new {|h,k| h[k] = [] }
13
+ params.each do |param|
14
+ param = param.to_s
15
+ @surrogate_params[action] << param
16
+ if param.include?('.')
17
+ dots = param.split('.')
18
+ @surrogate_params[action] << [dots.first, dots[1..-1].map{|p| "[#{p}]"}].compact.join('')
19
+ @surrogate_params[action] << URI.encode_www_form_component(@surrogate_params[action].last)
20
+ @surrogate_params[action] << dots.map{|p| "[#{p}]"}.join('')
21
+ @surrogate_params[action] << URI.encode_www_form_component(@surrogate_params[action].last)
22
+ elsif param.include?('[') && param.include?(']')
23
+ @surrogate_params[action] << URI.encode_www_form_component(param)
24
+ else
25
+ @surrogate_params[action] << param
26
+ end
27
+ end
28
+ @surrogate_params
29
+ end
30
+ end
31
+
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SurrogateKeyLogging
4
+ module ActionDispatch
5
+ class ParamsFilter
6
+
7
+ class << self
8
+ def call(params, req = nil)
9
+ new(params, req).filtered
10
+ end
11
+ end
12
+
13
+ attr_reader :params, :req
14
+
15
+ def initialize(params, req = nil)
16
+ @params = params
17
+ @req = req
18
+ end
19
+
20
+ def filterable_params
21
+ @filterable_params ||= if req.controller_class.respond_to?(:surrogate_params)
22
+ surrogate_params = req.controller_class.surrogate_params
23
+ surrogate_params[params[:action]] + surrogate_params['*']
24
+ else
25
+ []
26
+ end
27
+ end
28
+
29
+ def params_filter
30
+ return @params_filter if @params_filter
31
+ attrs = SurrogateKeyLogging.parameter_filter.instance_variable_get(:@filters).dup
32
+ attrs += filterable_params
33
+ @params_filter = SurrogateKeyLogging.filter_for_attributes(attrs)
34
+ end
35
+
36
+ def filtered
37
+ @filtered ||= params_filter.filter params
38
+ end
39
+
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SurrogateKeyLogging
4
+ module ActionDispatch
5
+ class QueryStringFilter
6
+
7
+ class << self
8
+ def call(qs, req = nil)
9
+ new(qs, req).filtered
10
+ end
11
+ end
12
+
13
+ attr_reader :qs, :req
14
+
15
+ def initialize(qs, req = nil)
16
+ @qs = qs
17
+ @req = req
18
+ end
19
+
20
+ def path_params
21
+ @path_params ||= begin
22
+ req.routes.recognize_path_with_request(req, req.path, {})
23
+ rescue
24
+ {}
25
+ end
26
+ end
27
+
28
+ def controller_class
29
+ @controller_class = req.controller_class_for(path_params[:controller])
30
+ end
31
+
32
+ def filterable_params
33
+ @filterable_params ||= if controller_class.respond_to?(:surrogate_params)
34
+ surrogate_params = controller_class.surrogate_params
35
+ surrogate_params[path_params[:action]] + surrogate_params['*']
36
+ else
37
+ []
38
+ end
39
+ end
40
+
41
+ def params_filter
42
+ return @params_filter if @params_filter
43
+ attrs = SurrogateKeyLogging.parameter_filter.instance_variable_get(:@filters).dup
44
+ attrs += filterable_params
45
+ @params_filter = SurrogateKeyLogging.filter_for_attributes(attrs)
46
+ end
47
+
48
+ def filtered
49
+ @filtered ||= qs.gsub(::ActionDispatch::Request::PAIR_RE) do |_|
50
+ params_filter.filter(::Regexp.last_match(1) => ::Regexp.last_match(2)).first.join('=')
51
+ end
52
+ end
53
+
54
+ end
55
+ end
56
+ end
@@ -4,10 +4,12 @@ module SurrogateKeyLogging
4
4
  module ActionDispatch
5
5
  module Request
6
6
 
7
+ def filtered_parameters
8
+ @filtered_parameters ||= ParamsFilter.call(super, self)
9
+ end
10
+
7
11
  def filtered_query_string
8
- super.gsub(::ActionDispatch::Request::PAIR_RE) do |_|
9
- SurrogateKeyLogging.filter_parameters(::Regexp.last_match(1) => ::Regexp.last_match(2)).first.join('=')
10
- end
12
+ QueryStringFilter.call(super, self)
11
13
  end
12
14
 
13
15
  end
@@ -4,6 +4,8 @@ module SurrogateKeyLogging
4
4
  module ActionDispatch
5
5
  extend ActiveSupport::Autoload
6
6
 
7
+ autoload :ParamsFilter
8
+ autoload :QueryStringFilter
7
9
  autoload :Request
8
10
 
9
11
  end
@@ -7,13 +7,9 @@ module SurrogateKeyLogging
7
7
  module Attributes
8
8
  extend ActiveSupport::Concern
9
9
 
10
- included do
11
- surrogate_parent_names model_name.singular, model_name.plural
12
- end
13
-
14
10
  class_methods do
15
11
  def surrogate_parent_names(*names)
16
- @surrogate_parent_names ||= []
12
+ @surrogate_parent_names ||= [model_name.singular, model_name.plural]
17
13
  names.each do |name|
18
14
  @surrogate_parent_names << name.to_sym
19
15
  surrogate_attributes.each do |attr|
@@ -25,7 +25,7 @@ module SurrogateKeyLogging
25
25
  initializer 'surrogate_key_logging.config' do |app|
26
26
  SurrogateKeyLogging.configure do |config|
27
27
  config.enabled = Rails.env.production? unless config.key?(:enabled)
28
- config.debug = !Rails.env.production? unless config.key?(:debug)
28
+ config.debug = false unless config.key?(:debug)
29
29
  config.key_prefix = '' unless config.key?(:key_prefix)
30
30
  config.key_for ||= -> (value) { "#{config.key_prefix}#{SecureRandom.uuid}" }
31
31
  config.cache = true unless config.key?(:cache)
@@ -49,11 +49,5 @@ module SurrogateKeyLogging
49
49
  end
50
50
  end
51
51
 
52
- initializer 'surrogate_key_logging.middleware' do
53
- if SurrogateKeyLogging.config.enabled
54
- Rails.application.config.middleware.insert_before 0, Middleware
55
- end
56
- end
57
-
58
52
  end
59
53
  end
@@ -36,7 +36,7 @@ module SurrogateKeyLogging
36
36
 
37
37
  def call(_key, value, _parents = [], _original_params = nil)
38
38
  surrogate = get(value)
39
- # Rails.logger.tagged('SurrogateKeyLogging') { Rails.logger.info "Surrogate: `#{surrogate}`, value: `#{value}`" } if SurrogateKeyLogging.config.debug
39
+ Rails.logger.tagged('SurrogateKeyLogging') { Rails.logger.info "Surrogate: `#{surrogate}`, value: `#{value}`" } if SurrogateKeyLogging.config.debug
40
40
  surrogate
41
41
  end
42
42
 
@@ -4,8 +4,8 @@ module SurrogateKeyLogging
4
4
 
5
5
  module Version
6
6
  MAJOR = 0
7
- MINOR = 1
8
- PATCH = 0
7
+ MINOR = 2
8
+ PATCH = 1
9
9
 
10
10
  end
11
11
 
@@ -49,7 +49,11 @@ module SurrogateKeyLogging
49
49
  end
50
50
 
51
51
  def parameter_filter
52
- @parameter_filter ||= ::ActiveSupport::ParameterFilter.new(surrogate_attributes, mask: key_manager)
52
+ @parameter_filter ||= filter_for_attributes(surrogate_attributes)
53
+ end
54
+
55
+ def filter_for_attributes(attrs)
56
+ ::ActiveSupport::ParameterFilter.new(attrs, mask: key_manager)
53
57
  end
54
58
 
55
59
  def key_store
@@ -27,8 +27,8 @@ Gem::Specification.new do |s|
27
27
 
28
28
  s.required_ruby_version = '>= 2.7.0'
29
29
 
30
- s.add_dependency('actionpack', '>= 6.0.0')
31
- s.add_dependency('activerecord', '>= 6.0.0')
32
- s.add_dependency('activesupport', '>= 6.0.0')
33
- s.add_dependency('railties', '>= 6.0.0')
30
+ s.add_dependency('actionpack', '>= 6.0.0', '< 7.0.0')
31
+ s.add_dependency('activerecord', '>= 6.0.0', '< 7.0.0')
32
+ s.add_dependency('activesupport', '>= 6.0.0', '< 7.0.0')
33
+ s.add_dependency('railties', '>= 6.0.0', '< 7.0.0')
34
34
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rails_surrogate_key_logging
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Taylor Yelverton
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-01-20 00:00:00.000000000 Z
11
+ date: 2023-01-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: actionpack
@@ -17,6 +17,9 @@ dependencies:
17
17
  - - ">="
18
18
  - !ruby/object:Gem::Version
19
19
  version: 6.0.0
20
+ - - "<"
21
+ - !ruby/object:Gem::Version
22
+ version: 7.0.0
20
23
  type: :runtime
21
24
  prerelease: false
22
25
  version_requirements: !ruby/object:Gem::Requirement
@@ -24,6 +27,9 @@ dependencies:
24
27
  - - ">="
25
28
  - !ruby/object:Gem::Version
26
29
  version: 6.0.0
30
+ - - "<"
31
+ - !ruby/object:Gem::Version
32
+ version: 7.0.0
27
33
  - !ruby/object:Gem::Dependency
28
34
  name: activerecord
29
35
  requirement: !ruby/object:Gem::Requirement
@@ -31,6 +37,9 @@ dependencies:
31
37
  - - ">="
32
38
  - !ruby/object:Gem::Version
33
39
  version: 6.0.0
40
+ - - "<"
41
+ - !ruby/object:Gem::Version
42
+ version: 7.0.0
34
43
  type: :runtime
35
44
  prerelease: false
36
45
  version_requirements: !ruby/object:Gem::Requirement
@@ -38,6 +47,9 @@ dependencies:
38
47
  - - ">="
39
48
  - !ruby/object:Gem::Version
40
49
  version: 6.0.0
50
+ - - "<"
51
+ - !ruby/object:Gem::Version
52
+ version: 7.0.0
41
53
  - !ruby/object:Gem::Dependency
42
54
  name: activesupport
43
55
  requirement: !ruby/object:Gem::Requirement
@@ -45,6 +57,9 @@ dependencies:
45
57
  - - ">="
46
58
  - !ruby/object:Gem::Version
47
59
  version: 6.0.0
60
+ - - "<"
61
+ - !ruby/object:Gem::Version
62
+ version: 7.0.0
48
63
  type: :runtime
49
64
  prerelease: false
50
65
  version_requirements: !ruby/object:Gem::Requirement
@@ -52,6 +67,9 @@ dependencies:
52
67
  - - ">="
53
68
  - !ruby/object:Gem::Version
54
69
  version: 6.0.0
70
+ - - "<"
71
+ - !ruby/object:Gem::Version
72
+ version: 7.0.0
55
73
  - !ruby/object:Gem::Dependency
56
74
  name: railties
57
75
  requirement: !ruby/object:Gem::Requirement
@@ -59,6 +77,9 @@ dependencies:
59
77
  - - ">="
60
78
  - !ruby/object:Gem::Version
61
79
  version: 6.0.0
80
+ - - "<"
81
+ - !ruby/object:Gem::Version
82
+ version: 7.0.0
62
83
  type: :runtime
63
84
  prerelease: false
64
85
  version_requirements: !ruby/object:Gem::Requirement
@@ -66,6 +87,9 @@ dependencies:
66
87
  - - ">="
67
88
  - !ruby/object:Gem::Version
68
89
  version: 6.0.0
90
+ - - "<"
91
+ - !ruby/object:Gem::Version
92
+ version: 7.0.0
69
93
  description: ''
70
94
  email: rubygems@yelvert.io
71
95
  executables: []
@@ -77,7 +101,10 @@ files:
77
101
  - lib/surrogate_key_logging.rb
78
102
  - lib/surrogate_key_logging/action_controller.rb
79
103
  - lib/surrogate_key_logging/action_controller/log_subscriber.rb
104
+ - lib/surrogate_key_logging/action_controller/params.rb
80
105
  - lib/surrogate_key_logging/action_dispatch.rb
106
+ - lib/surrogate_key_logging/action_dispatch/params_filter.rb
107
+ - lib/surrogate_key_logging/action_dispatch/query_string_filter.rb
81
108
  - lib/surrogate_key_logging/action_dispatch/request.rb
82
109
  - lib/surrogate_key_logging/active_record.rb
83
110
  - lib/surrogate_key_logging/active_record/attributes.rb
@@ -89,7 +116,6 @@ files:
89
116
  - lib/surrogate_key_logging/key_store.rb
90
117
  - lib/surrogate_key_logging/key_store/active_record.rb
91
118
  - lib/surrogate_key_logging/key_store/base.rb
92
- - lib/surrogate_key_logging/middleware.rb
93
119
  - lib/surrogate_key_logging/version.rb
94
120
  - lib/tasks/key_store/active_record.rake
95
121
  - lib/tasks/surrogate_key_logging.rake
@@ -1,17 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module SurrogateKeyLogging
4
- class Middleware
5
- attr_reader :app
6
-
7
- def initialize(app)
8
- @app = app
9
- end
10
-
11
- def call(env)
12
- SurrogateKeyLogging.reset
13
- @app.call(env)
14
- end
15
-
16
- end
17
- end