active_security 1.0.0

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.
@@ -0,0 +1,194 @@
1
+ module ActiveSecurity
2
+ # @guide begin
3
+ #
4
+ # ## Setting Up ActiveSecurity in Your Model
5
+ #
6
+ # To use ActiveSecurity in your ActiveRecord models, you must first either extend or
7
+ # include the ActiveSecurity module (it makes no difference), then invoke the
8
+ # {ActiveSecurity::Base#active_security active_security} method to configure your desired
9
+ # options:
10
+ #
11
+ # class Foo < ActiveRecord::Base
12
+ # include ActiveSecurity
13
+ # active_security :use => [:finders, :scoped], scope: :bar_id
14
+ # end
15
+ #
16
+ # The most important option is `:use`, which you use to tell ActiveSecurity which
17
+ # addons it should use. See the documentation for {ActiveSecurity::Base#active_security} for a list of all
18
+ # available addons, or skim through the rest of the docs to get a high-level
19
+ # overview.
20
+ #
21
+ # *A note about single table inheritance (STI): you must extend ActiveSecurity in*
22
+ # *all classes that participate in STI, both your parent classes and their*
23
+ # *children.*
24
+ #
25
+ # ### The Default Setup: Simple Models
26
+ #
27
+ # The simplest way to use ActiveSecurity is to have it ensure that finds are executed within a scope:
28
+ #
29
+ # class User < ActiveRecord::Base
30
+ # extend ActiveSecurity
31
+ # end
32
+ #
33
+ # User.restricted.find(1) # blows up, because no scope
34
+ # User.where(...).restricted.find(1) # returns the user
35
+ #
36
+ # ### The Strict Setup: Simple Models
37
+ #
38
+ # The problem with the above approach is that a naked find (`User.find(1)`)
39
+ # still works, and is just as insecure as before. The `:finders` plugin fixes
40
+ # this problem, so you don't need to add `restricted` everywhere.
41
+ #
42
+ # class User < ActiveRecord::Base
43
+ # extend ActiveSecurity
44
+ # active_security use: {finders: {default_finders: :restricted}}
45
+ # end
46
+ #
47
+ # User.find(1) # blows up, because no scope
48
+ # User.where(...).find(1) # returns the user
49
+ #
50
+ # @guide end
51
+ module Base
52
+ # Configure ActiveSecurity's behavior in a model.
53
+ #
54
+ # class Post < ActiveRecord::Base
55
+ # extend ActiveSecurity
56
+ # active_security use: :finders
57
+ # end
58
+ #
59
+ # When given the optional block, this method will yield the class's instance
60
+ # of {ActiveSecurity::Configuration} to the block before evaluating other
61
+ # arguments, so configuration values set in the block may be overwritten by
62
+ # the arguments. This order was chosen to allow passing the same proc to
63
+ # multiple models, while being able to override the values it sets. Here is
64
+ # a contrived example:
65
+ #
66
+ # $active_security_config_proc = Proc.new do |config|
67
+ # config.use :finders
68
+ # end
69
+ #
70
+ # class Foo < ActiveRecord::Base
71
+ # extend ActiveSecurity
72
+ # active_security &$active_security_config_proc
73
+ # end
74
+ #
75
+ # class Bar < ActiveRecord::Base
76
+ # extend ActiveSecurity
77
+ # active_security &$active_security_config_proc
78
+ # end
79
+ #
80
+ # However, it's usually better to use {ActiveSecurity.defaults} for this:
81
+ #
82
+ # ActiveSecurity.defaults do |config|
83
+ # config.use :finders, default_finders: :restricted
84
+ # end
85
+ #
86
+ # class Foo < ActiveRecord::Base
87
+ # extend ActiveSecurity
88
+ # end
89
+ #
90
+ # class Bar < ActiveRecord::Base
91
+ # extend ActiveSecurity
92
+ # end
93
+ #
94
+ # In general you should use the block syntax either because of your personal
95
+ # aesthetic preference, or because you need to share some functionality
96
+ # between multiple models that can't be well encapsulated by
97
+ # {ActiveSecurity.defaults}.
98
+ #
99
+ # ### Order Method Calls in a Block vs Ordering Options
100
+ #
101
+ # When calling this method without a block, you may set the hash options in
102
+ # any order.
103
+ #
104
+ # However, when using block-style invocation, be sure to call
105
+ # ActiveSecurity::Configuration's {ActiveSecurity::Configuration#use use} method
106
+ # *prior* to the associated configuration options, because it will include
107
+ # modules into your class, and these modules in turn may add required
108
+ # configuration options to the `@active_security_configuration`'s class:
109
+ #
110
+ # class Person < ActiveRecord::Base
111
+ # active_security do |config|
112
+ # # This will work
113
+ # config.use :scoped
114
+ # config.scope = "family_id"
115
+ # end
116
+ # end
117
+ #
118
+ # class Person < ActiveRecord::Base
119
+ # active_security do |config|
120
+ # # This will fail
121
+ # config.scope = "family_id"
122
+ # config.use :scoped
123
+ # end
124
+ # end
125
+ #
126
+ # ### Including Your Own Modules
127
+ #
128
+ # Because :use can accept a name or a Module, {ActiveSecurity.defaults defaults}
129
+ # can be a convenient place to set up behavior common to all classes using
130
+ # ActiveSecurity. You can include any module, or more conveniently, define one
131
+ # on-the-fly. For example, let's say you want to override the error that is
132
+ # raised when no scope is used:
133
+ #
134
+ # ActiveSecurity.defaults do |config|
135
+ # config.use :finders
136
+ # config.use Module.new {
137
+ # def self.setup(model_class)
138
+ # model_class.instance_eval do
139
+ # relation.class.send(:prepend, RaiseOverride)
140
+ # model_class.singleton_class.send(:prepend, RaiseOverride)
141
+ # end
142
+ #
143
+ # association_relation_delegate_class = model_class.relation_delegate_class(::ActiveRecord::AssociationRelation)
144
+ # association_relation_delegate_class.send(:prepend, RaiseOverride)
145
+ # end
146
+ #
147
+ # module RaiseOverride
148
+ # def raise_if_not_scoped
149
+ # puts "My errors are better than yours"
150
+ # raise StandardError, "Calm Down"
151
+ # end
152
+ # end
153
+ # }
154
+ # end
155
+ #
156
+ #
157
+ # @option options [Symbol,Module] :use The addon or name of an addon to use.
158
+ # By default, ActiveSecurity provides {ActiveSecurity::Finders :finders},
159
+ # {ActiveSecurity::Restricted :restricted}, {ActiveSecurity::Privileged :privileged},
160
+ # and {ActiveSecurity::Scoped :scoped}.
161
+ #
162
+ # @option options [Symbol] :scope Available when using `:scoped`.
163
+ # Sets the relation or column which will be considered a required scope.
164
+ # This option has no default value.
165
+ #
166
+ # @yield Provides access to the model class's active_security_config, which
167
+ # allows an alternate configuration syntax, and conditional configuration
168
+ # logic.
169
+ #
170
+ # @yieldparam config The model class's {ActiveSecurity::Configuration active_security_config}.
171
+ def active_security(options = {}, &block)
172
+ yield active_security_config if block
173
+ use_mods = options.delete(:use)
174
+ if use_mods
175
+ active_security_config.use(use_mods) do |config|
176
+ config.send(:set, options)
177
+ end
178
+ else
179
+ active_security_config.send(:set, options)
180
+ end
181
+ end
182
+
183
+ # Returns the model class's {ActiveSecurity::Configuration active_security_config}.
184
+ # @note In the case of Single Table Inheritance (STI), this method will
185
+ # duplicate the parent class's ActiveSecurity::Configuration and relation class
186
+ # on first access. If you're concerned about thread safety, then be sure
187
+ # to invoke {#active_security} in your class for each model.
188
+ def active_security_config
189
+ @active_security_config ||= base_class.active_security_config.dup.tap do |config|
190
+ config.model_class = self
191
+ end
192
+ end
193
+ end
194
+ end
@@ -0,0 +1,107 @@
1
+ module ActiveSecurity
2
+ # The configuration parameters passed to {Base#active_security} will be stored in
3
+ # this object.
4
+ class Configuration
5
+ # The default configuration options.
6
+ attr_reader :defaults
7
+
8
+ # The modules in use
9
+ attr_reader :modules
10
+
11
+ # The model class that this configuration belongs to.
12
+ # @return ActiveRecord::Base
13
+ attr_accessor :model_class
14
+
15
+ # The module to use for finders
16
+ attr_accessor :finder_methods
17
+
18
+ # Where logs will be sent
19
+ attr_accessor :logger
20
+
21
+ def initialize(model_class, values = nil)
22
+ @model_class = model_class
23
+ @defaults = {}
24
+ @logger = ActiveRecord::Base.logger
25
+ @modules = []
26
+ @finder_methods = ActiveSecurity::FinderMethods
27
+ set(values)
28
+ end
29
+
30
+ # Lets you specify the addon modules to use with ActiveSecurity.
31
+ #
32
+ # This method is invoked by {ActiveSecurity::Base#active_security active_security} when
33
+ # passing the `:use` option, or when using {ActiveSecurity::Base#active_security
34
+ # active_security} with a block.
35
+ #
36
+ # @example
37
+ # class Book < ActiveRecord::Base
38
+ # extend ActiveSecurity
39
+ # active_security use: :finders
40
+ # end
41
+ #
42
+ # @param [#to_s, Module, Hash[[#to_s, Module], Array[Hash[#to_s, any]]]] modules
43
+ # Arguments should be Modules, or symbols or
44
+ # strings that correspond with the name of an addon to use with ActiveSecurity,
45
+ # or a hash/array of hashes where the keys are Modules, symbols, or strings corresponding as previously described, and
46
+ # the values are the Hashes of key value pairs of configuration attributes and their assigned values.
47
+ # By default ActiveSecurity provides `:finders`, `:privileged`, `:restricted` and `:scoped`.
48
+ def use(*modules, &block)
49
+ mods = modules.to_a.compact
50
+ mods.map.with_index do |object, idx|
51
+ case object
52
+ when Array
53
+ object.each do |obj|
54
+ if obj.is_a?(Hash)
55
+ _handle_hash(obj)
56
+ else
57
+ mod = get_module(obj)
58
+ _use(mod, idx, &block)
59
+ end
60
+ end
61
+ when Hash
62
+ _handle_hash(object)
63
+ when String, Symbol, Module
64
+ mod = get_module(object)
65
+ _use(mod, idx, &block)
66
+ else
67
+ raise InvalidConfig, "Unknown Argument Type #{object.class}: #{object.inspect}"
68
+ end
69
+ end
70
+ end
71
+
72
+ def _handle_hash(hash)
73
+ hash.each do |mod_key, attrs|
74
+ mod = get_module(mod_key)
75
+ _use(mod, 0) do |config|
76
+ config.send(:set, attrs)
77
+ end
78
+ end
79
+ end
80
+
81
+ def _use(mod, idx, &block)
82
+ mod.setup(@model_class) if mod.respond_to?(:setup)
83
+ @model_class.send(:include, mod) unless uses?(mod)
84
+ # Only yield on the first module, so as to not run the block multiple times,
85
+ # and because later modules may require the attributes of a prior module to exist.
86
+ # The block structure won't work for more complex config than that.
87
+ # For more complex configuration pass a Hash where the keys are the "modules"
88
+ yield self if block_given? && idx.zero?
89
+ mod.after_config(@model_class) if mod.respond_to?(:after_config)
90
+ end
91
+
92
+ # Returns whether the given module is in use.
93
+ def uses?(mod)
94
+ @model_class < get_module(mod)
95
+ end
96
+
97
+ private
98
+
99
+ def get_module(object)
100
+ (Module === object) ? object : ActiveSecurity.const_get(object.to_s.titleize.camelize.gsub(/\s+/, ""))
101
+ end
102
+
103
+ def set(values)
104
+ values&.each { |name, value| send(:"#{name}=", value) }
105
+ end
106
+ end
107
+ end
@@ -0,0 +1,64 @@
1
+ module ActiveSecurity
2
+ module FinderMethods
3
+ # Finds a record using the given id.
4
+ # Because this is injected into the Model as well as the relation,
5
+ # we need handling for both.
6
+ def find(*args)
7
+ if is_a?(ActiveRecord::Relation)
8
+ _active_security_enforce_if_not_scoped
9
+ else
10
+ _active_security_not_scoped_handler
11
+ end
12
+
13
+ super
14
+ end
15
+
16
+ private
17
+
18
+ def _active_security_enforce_if_not_scoped
19
+ scoped_securely =
20
+ if active_security_config.respond_to?(:scope_columns)
21
+ values[:where].present? && active_security_config.scope_columns.all? do |scope_column|
22
+ predicates = values[:where].send(:predicates)
23
+ _active_security_check_predicates_for_scope(predicates, scope_column)
24
+ end
25
+ else
26
+ # If we don't have a specific scope requirement, then just ensure there is some scope.
27
+ values[:where].present?
28
+ end
29
+ _active_security_not_scoped_handler unless scoped_securely
30
+ end
31
+
32
+ def _active_security_check_predicates_for_scope(predicates, scope_column)
33
+ predicates.detect do |predicate|
34
+ # Consider alternative...
35
+ # comp =
36
+ # case predicate
37
+ # when Arel::Nodes::HomogeneousIn
38
+ # predicate.attribute.name
39
+ # when Arel::Nodes::Equality
40
+ # predicate.right.name
41
+ # end
42
+ comp =
43
+ if predicate.respond_to?(:attribute) && predicate.attribute.respond_to?(:name)
44
+ # Handles Arel::Nodes::HomogeneousIn
45
+ predicate.attribute.name
46
+ elsif predicate.respond_to?(:right) && predicate.right.respond_to?(:name)
47
+ # Handles Arel::Nodes::Equality
48
+ predicate.right.name
49
+ else
50
+ _active_security_unhandled_predicate(predicate)
51
+ end
52
+ comp == scope_column
53
+ end
54
+ end
55
+
56
+ def _active_security_name_for
57
+ if respond_to?(name)
58
+ "(#{name})"
59
+ else
60
+ "(#{(self.class.name == "Class") ? to_s : self.class.name})"
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,118 @@
1
+ module ActiveSecurity
2
+ # @guide begin
3
+ #
4
+ # ## Performing Finds with ActiveSecurity
5
+ #
6
+ # ActiveSecurity offers enhanced finders which will search for your record while
7
+ # ensuring that a particular scope is present. This makes it easy
8
+ # to add ActiveSecurity to an existing application with minimal code modification.
9
+ #
10
+ # By default, these enhanced finders are available only on the `restricted` scope:
11
+ #
12
+ # Restaurant.restricted.find(23) #=> Will blow up, because no scope!
13
+ # Restaurant.find(23) #=> works
14
+ #
15
+ # ActiveSecurity overrides the default finder methods to perform
16
+ # secure finds all the time. This requires modifying parts of Rails that do
17
+ # not have a public API, which is hard to maintain and may cause
18
+ # compatibility issues.
19
+ #
20
+ # class Restaurant < ActiveRecord::Base
21
+ # extend ActiveSecurity
22
+ #
23
+ # scope :active, -> {where(active: true)}
24
+ #
25
+ # active_security use: [:finders]
26
+ # end
27
+ #
28
+ # Restaurant.restricted.find(23) #=> blows up, because no scope!
29
+ # Restaurant.find(23) #=> also blows up, because no scope!
30
+ # Restaurant.active.find(23) #=> works, because scoped!
31
+ # Restaurant.active.restricted.find(23) #=> also works, because scoped!
32
+ #
33
+ # ### Updating your application to use ActiveSecurity's finders
34
+ #
35
+ # Unless you've chosen to use the `:finders` addon, be sure to modify the finders
36
+ # in your controllers to use the `restricted` scope. For example:
37
+ #
38
+ # # before
39
+ # def set_restaurant
40
+ # @restaurant = Restaurant.find(params[:id])
41
+ # end
42
+ #
43
+ # # after
44
+ # def set_restaurant
45
+ # @restaurant = Restaurant.restricted.find(params[:id])
46
+ # end
47
+ #
48
+ # #### Active Admin
49
+ #
50
+ # Unless you use the `:finders` addon, you should modify your admin controllers
51
+ # for models that use ActiveSecurity with something similar to the following:
52
+ #
53
+ # controller do
54
+ # def find_resource
55
+ # scoped_collection.restricted.find(params[:id])
56
+ # end
57
+ # end
58
+ #
59
+ # @guide end
60
+ module Finders
61
+ class << self
62
+ # ActiveSecurity::Config.use will invoke this method when present, to allow
63
+ # loading dependent modules prior to overriding them when necessary.
64
+ def setup(model_class)
65
+ model_class.class_eval do
66
+ relation.class.send(:include, active_security_config.finder_methods)
67
+ extend(active_security_config.finder_methods)
68
+ end
69
+
70
+ association_relation_delegate_class = model_class.relation_delegate_class(::ActiveRecord::AssociationRelation)
71
+ association_relation_delegate_class.send(:include, model_class.active_security_config.finder_methods)
72
+ end
73
+
74
+ # Sets up behavior and configuration options for finders feature.
75
+ def included(model_class)
76
+ model_class.active_security_config.instance_eval do
77
+ self.class.send(:include, Configuration)
78
+ defaults[:default_finders] ||= :restricted
79
+ end
80
+ end
81
+
82
+ # Sets up behavior and that depends on configuration
83
+ def after_config(model_class)
84
+ raise InvalidConfig, ":finders plugin must be used with default_finders set to one of :privileged, or :restricted" unless %i[restricted privileged].include?(model_class.active_security_config.default_finders)
85
+
86
+ model_class.active_security_config.use(model_class.active_security_config.default_finders)
87
+ model_class.class_eval do
88
+ if active_security_config.default_finders == :privileged
89
+ relation.class.send(:include, active_security_config.privileged_hooks)
90
+ send(:extend, active_security_config.privileged_hooks)
91
+ else
92
+ relation.class.send(:include, active_security_config.restricted_hooks)
93
+ send(:extend, active_security_config.restricted_hooks)
94
+ end
95
+ end
96
+ association_relation_delegate_class = model_class.relation_delegate_class(::ActiveRecord::AssociationRelation)
97
+ if model_class.active_security_config.default_finders == :privileged
98
+ association_relation_delegate_class.send(:include, model_class.active_security_config.privileged_hooks)
99
+ else
100
+ model_class.active_security_config.default_finders
101
+ association_relation_delegate_class.send(:include, model_class.active_security_config.restricted_hooks)
102
+ end
103
+ end
104
+ end
105
+
106
+ # This module adds the `:default_finders` configuration option to
107
+ # {ActiveSecurity::Configuration ActiveSecurity::Configuration}.
108
+ module Configuration
109
+ # Gets the default_finders value.
110
+ #
111
+ # When setting this value, the argument should be a symbol, either
112
+ # :restricted or :privileged. Default is :restricted.
113
+ #
114
+ # @return Symbol The default_finders value
115
+ attr_accessor :default_finders
116
+ end
117
+ end
118
+ end
@@ -0,0 +1,39 @@
1
+ module ActiveSecurity
2
+ module Privileged
3
+ class << self
4
+ # Sets up behavior and configuration options for privileged feature.
5
+ def included(model_class)
6
+ model_class.active_security_config.instance_eval do
7
+ self.class.send(:include, Configuration)
8
+ defaults[:privileged_hooks] ||= ActiveSecurity::PrivilegedHooks
9
+ end
10
+ model_class.class_eval do
11
+ extend(PrivilegedScope)
12
+ end
13
+ end
14
+ end
15
+
16
+ # This module adds `:privileged_hooks` to
17
+ # {ActiveSecurity::Configuration ActiveSecurity::Configuration}.
18
+ module Configuration
19
+ attr_writer :privileged_hooks
20
+
21
+ def privileged_hooks
22
+ @privileged_hooks ||= defaults[:privileged_hooks]
23
+ end
24
+ end
25
+
26
+ module PrivilegedScope
27
+ # Returns a scope that is allowed to not require the secure scope, but will
28
+ # be logged.
29
+ # @see ActiveSecurity::FinderMethods
30
+ # @see ActiveSecurity::Privileged
31
+ def privileged
32
+ all.extending(
33
+ active_security_config.finder_methods,
34
+ active_security_config.privileged_hooks,
35
+ )
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,13 @@
1
+ module ActiveSecurity
2
+ module PrivilegedHooks
3
+ extend ActiveSupport::Concern
4
+
5
+ def _active_security_not_scoped_handler
6
+ active_security_config.logger.warn("[Privileged] #{_active_security_name_for} does not have secure scope: #{respond_to?(:to_sql) ? to_sql : ""}")
7
+ end
8
+
9
+ def _active_security_unhandled_predicate(predicate)
10
+ active_security_config.logger.error("[Privileged] #{_active_security_name_for} predicate type #{predicate.class.name} is unhandled; See: https://www.rubydoc.info/github/rails/rails/Arel/Nodes")
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,71 @@
1
+ module ActiveSecurity
2
+ module Restricted
3
+ class << self
4
+ # Sets up behavior and configuration options for restricted feature.
5
+ def included(model_class)
6
+ model_class.active_security_config.instance_eval do
7
+ self.class.send(:include, Configuration)
8
+ defaults[:restricted_hooks] ||= ActiveSecurity::RestrictedHooks
9
+ defaults[:on_restricted_no_scope] ||= :log_and_raise
10
+ defaults[:on_restricted_unhandled_predicate] ||= :log_and_raise
11
+ end
12
+ model_class.class_eval do
13
+ extend(RestrictedScope)
14
+ end
15
+ end
16
+ end
17
+
18
+ # This module adds `:restricted_hooks`, `:on_restricted_no_scope`
19
+ # and `:on_restricted_unhandled_predicate` configuration options to
20
+ # {ActiveSecurity::Configuration ActiveSecurity::Configuration}.
21
+ module Configuration
22
+ attr_writer :restricted_hooks
23
+ # Gets the on_restricted_no_scope value.
24
+ #
25
+ # When setting this value, the argument should either be a callable lambda/proc,
26
+ # or one of :log, :log_and_raise, :raise.
27
+ attr_writer :on_restricted_no_scope
28
+
29
+ # Gets the on_restricted_unhandled_predicate value.
30
+ #
31
+ # When setting this value, the argument should either be a callable lambda/proc,
32
+ # or one of :log, :log_and_raise, :raise.
33
+ attr_writer :on_restricted_unhandled_predicate
34
+
35
+ # @return Module The module to use for restricted_hooks
36
+ def restricted_hooks
37
+ @restricted_hooks ||= defaults[:restricted_hooks]
38
+ end
39
+
40
+ # @return Symbol The on_restricted_no_scope value
41
+ def on_restricted_no_scope
42
+ @on_restricted_no_scope ||= defaults[:on_restricted_no_scope]
43
+ end
44
+
45
+ # @return Symbol The on_restricted_unhandled_predicate value
46
+ def on_restricted_unhandled_predicate
47
+ @on_restricted_unhandled_predicate ||= defaults[:on_restricted_unhandled_predicate]
48
+ end
49
+ end
50
+
51
+ module RestrictedScope
52
+ # Returns a scope that includes the active_security restricted hooks.
53
+ # @see ActiveSecurity::FinderMethods
54
+ # @see ActiveSecurity::RestrictedHooks
55
+ def restricted
56
+ # Guess what? This causes Rails to invoke `extend` on the scope, which has
57
+ # the well-known effect of blowing away Ruby's method cache. It would be
58
+ # possible to make this more performant by subclassing the model's
59
+ # relation class, extending that, and returning an instance of it in this
60
+ # method. However, using Rails' public API improves compatibility
61
+ # and maintainability. If you'd like to improve the performance, your
62
+ # efforts would be best directed at improving it at the root cause
63
+ # of the problem - in Rails - because it would benefit more people.
64
+ all.extending(
65
+ active_security_config.finder_methods,
66
+ active_security_config.restricted_hooks,
67
+ )
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,41 @@
1
+ module ActiveSecurity
2
+ module RestrictedHooks
3
+ extend ActiveSupport::Concern
4
+
5
+ VALID_CONFIG_VALUES = %i[log log_and_raise raise]
6
+
7
+ def _active_security_not_scoped_handler
8
+ return active_security_config.on_restricted_no_scope.call(active_security_config) if active_security_config.on_restricted_no_scope.respond_to?(:call)
9
+
10
+ unless VALID_CONFIG_VALUES.include?(active_security_config.on_restricted_no_scope)
11
+ raise InvalidConfig, "on_restricted_no_scope must either be set to a (callable lambda/proc) or one of [:log, :log_and_raise, :raise]"
12
+ end
13
+
14
+ if /log/.match?(active_security_config.on_restricted_no_scope)
15
+ active_security_config.logger.error("#{_active_security_name_for} does not have secure scope: #{respond_to?(:to_sql) ? to_sql : ""}")
16
+ end
17
+
18
+ if /raise/.match?(active_security_config.on_restricted_no_scope)
19
+ raise RestrictedAccessError.new("prevented query without a secure scope #{_active_security_name_for}")
20
+ end
21
+ end
22
+
23
+ def _active_security_unhandled_predicate(predicate)
24
+ return active_security_config.on_restricted_unhandled_predicate.call(active_security_config) if active_security_config.on_restricted_unhandled_predicate.respond_to?(:call)
25
+
26
+ unless VALID_CONFIG_VALUES.include?(active_security_config.on_restricted_unhandled_predicate)
27
+ raise InvalidConfig, "on_restricted_unhandled_predicate must either be set to a (callable lambda/proc) or one of [:log, :log_and_raise, :raise]"
28
+ end
29
+
30
+ if /log/.match?(active_security_config.on_restricted_unhandled_predicate)
31
+ active_security_config.logger.error("#{_active_security_name_for} predicate type #{predicate.class.name} is unhandled; See: https://www.rubydoc.info/github/rails/rails/Arel/Nodes")
32
+ end
33
+
34
+ if /raise/.match?(active_security_config.on_restricted_unhandled_predicate)
35
+ raise UnhandledArelPredicateError.new(
36
+ "#{_active_security_name_for} predicate type #{predicate.class.name} is unhandled; See: https://www.rubydoc.info/github/rails/rails/Arel/Nodes",
37
+ )
38
+ end
39
+ end
40
+ end
41
+ end