Class: Heimdallr::Evaluator
- Inherits:
-
Object
- Object
- Heimdallr::Evaluator
- Defined in:
- lib/heimdallr/evaluator.rb
Overview
Evaluator is a DSL for managing permissions on records with the field granularity. It works by evaluating a block of code within a given security context.
The default resolution is to forbid everything--that is, Heimdallr security policy is whitelisting safe actions, not blacklisting unsafe ones. This is by design and is not going to change.
The field #id is whitelisted by default.
The DSL consists of three functions: #scope, #can and #cannot.
Instance Attribute Summary (collapse)
-
- (Object) allowed_fields
readonly
Returns the value of attribute allowed_fields.
-
- (Object) fixtures
readonly
Returns the value of attribute fixtures.
-
- (Object) validators
readonly
Returns the value of attribute validators.
DSL (collapse)
-
- (Object) can(actions, fields = @model_class.attribute_names)
Define allowed operations for action(s).
-
- (Object) cannot(actions, fields)
Revoke a permission on fields.
-
- (Object) scope(name, explicit_block, &implicit_block)
Define a scope.
Instance Method Summary (collapse)
-
- (Boolean) can?(action)
Check if any explicit restrictions were defined for action.
-
- (Array<ActiveModel::Validator>) create_validators(fields)
protected
Create validators for fields in ActiveModel::Validations-like way.
-
- (Object) evaluate(context)
Compute the restrictions for a given context.
-
- (Object) extract_fixtures(fields)
protected
Collects fixtures from the fields definition.
-
- (Evaluator) initialize(model_class, block)
constructor
Create a new Evaluator for the ActiveRecord-derived class model_class, and use block to infer restrictions for any security context passed.
-
- (Object) reflection
Return a Hash to be mixed in in reflect_on_security methods of Proxy::Collection and Proxy::Record.
-
- (Object) request_scope(name = :fetch, basic_scope = nil)
Request a scope.
Constructor Details
- (Evaluator) initialize(model_class, block)
Create a new Evaluator for the ActiveRecord-derived class model_class, and use block to infer restrictions for any security context passed.
17 18 19 20 21 22 23 24 |
# File 'lib/heimdallr/evaluator.rb', line 17 def initialize(model_class, block) @model_class, @block = model_class, block @scopes = {} @allowed_fields = {} @validators = {} @fixtures = {} end |
Instance Attribute Details
- (Object) allowed_fields (readonly)
Returns the value of attribute allowed_fields
13 14 15 |
# File 'lib/heimdallr/evaluator.rb', line 13 def allowed_fields @allowed_fields end |
- (Object) fixtures (readonly)
Returns the value of attribute fixtures
13 14 15 |
# File 'lib/heimdallr/evaluator.rb', line 13 def fixtures @fixtures end |
- (Object) validators (readonly)
Returns the value of attribute validators
13 14 15 |
# File 'lib/heimdallr/evaluator.rb', line 13 def validators @validators end |
Instance Method Details
- (Object) can(actions, fields = @model_class.attribute_names)
Define allowed operations for action(s).
The fields parameter accepts both Arrays and Hashes.
-
If an Array is passed, then all fields present in the array are whitelised.
-
If a Hash is passed, then all fields present as hash keys are whitelisted, and:
-
If a corresponding value is a Hash, it will be processed as a security validator. Security validators make records invalid when they are saved through a Proxy::Record.
-
If the corresponding value is any other object, it will be added as a security fixture. Fixtures are merged when objects are created through restricted scopes, and cause exceptions to be raised when a record is saved, even through the #save method.
-
76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 |
# File 'lib/heimdallr/evaluator.rb', line 76 def can(actions, fields=@model_class.attribute_names) Array(actions).map(&:to_sym).each do |action| case fields when Hash # a list of validations @allowed_fields[action] += fields.keys.map(&:to_sym) @validators[action] += create_validators(fields) fixtures = extract_fixtures(fields) @fixtures[action] = @fixtures[action].merge fixtures @allowed_fields[action] -= fixtures.keys else # an array or a field name @allowed_fields[action] += Array(fields).map(&:to_sym) end end end |
- (Boolean) can?(action)
Check if any explicit restrictions were defined for action. +can :create, []+ is an explicit restriction for action :create.
131 132 133 |
# File 'lib/heimdallr/evaluator.rb', line 131 def can?(action) @allowed_fields.include? action end |
- (Object) cannot(actions, fields)
Revoke validating restrictions.
Revoke a permission on fields.
98 99 100 101 102 103 |
# File 'lib/heimdallr/evaluator.rb', line 98 def cannot(actions, fields) Array(actions).map(&:to_sym).each do |action| @allowed_fields[action] -= fields.map(&:to_sym) @fixtures.delete_at *fields end end |
- (Array<ActiveModel::Validator>) create_validators(fields) (protected)
Create validators for fields in ActiveModel::Validations-like way.
180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 |
# File 'lib/heimdallr/evaluator.rb', line 180 def create_validators(fields) validators = [] fields.each do |attribute, validations| next unless validations.is_a? Hash validations.each do |key, | key = "#{key.to_s.camelize}Validator" begin validator = key.include?('::') ? key.constantize : ActiveModel::Validations.const_get(key) rescue NameError raise ArgumentError, "Unknown validator: '#{key}'" end validators << validator.new(().merge(:attributes => [ attribute ])) end end validators end |
- (Object) evaluate(context)
Compute the restrictions for a given context. Invokes a block passed to the initialize once.
147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 |
# File 'lib/heimdallr/evaluator.rb', line 147 def evaluate(context) if context != @last_context @scopes = {} @allowed_fields = Hash.new { [] } @validators = Hash.new { [] } @fixtures = Hash.new { {} } @allowed_fields[:view] += [ :id ] instance_exec context, &@block unless @scopes[:fetch] raise RuntimeError, "A :fetch scope must be defined" end @allowed_fields.each do |action, fields| fields.uniq! end [@scopes, @allowed_fields, @validators, @fixtures]. map(&:freeze) @last_context = context end self end |
- (Object) extract_fixtures(fields) (protected)
Collects fixtures from the fields definition.
203 204 205 206 207 208 209 210 211 212 213 |
# File 'lib/heimdallr/evaluator.rb', line 203 def extract_fixtures(fields) fixtures = {} fields.each do |attribute, | next if .is_a? Hash fixtures[attribute.to_sym] = end fixtures end |
- (Object) reflection
Return a Hash to be mixed in in reflect_on_security methods of Proxy::Collection and Proxy::Record.
137 138 139 140 141 |
# File 'lib/heimdallr/evaluator.rb', line 137 def reflection { operations: [ :view, :create, :update ].select { |op| can? op } } end |
- (Object) request_scope(name = :fetch, basic_scope = nil)
Request a scope.
115 116 117 118 119 120 121 122 123 124 125 |
# File 'lib/heimdallr/evaluator.rb', line 115 def request_scope(name=:fetch, basic_scope=nil) unless @scopes.has_key?(name) raise RuntimeError, "The #{name.inspect} scope does not exist" end if name == :fetch && basic_scope.nil? @model_class.instance_exec(&@scopes[:fetch]) else (basic_scope || request_scope(:fetch)).instance_exec(&@scopes[name]) end end |
- (Object) scope(name, block) - (Object) scope(name)
Define a scope. A special :fetch scope is applied to any other scope automatically.
48 49 50 |
# File 'lib/heimdallr/evaluator.rb', line 48 def scope(name, explicit_block, &implicit_block) @scopes[name] = explicit_block || implicit_block end |