active_security 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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