Class: DeclarativePolicy::Base
- Inherits:
-
Object
- Object
- DeclarativePolicy::Base
- Defined in:
- lib/declarative_policy/base.rb
Direct Known Subclasses
Defined Under Namespace
Classes: AbilityMap, Options
Instance Attribute Summary collapse
-
#subject ⇒ Object
readonly
A policy object contains a specific user and subject on which to compute abilities.
-
#user ⇒ Object
readonly
A policy object contains a specific user and subject on which to compute abilities.
Class Method Summary collapse
-
.ability_map ⇒ Object
The ‘own_ability_map` vs `ability_map` distinction is used so that the data structure is properly inherited - with subclasses recursively merging their parent class.
-
.condition(condition_name, opts = {}, &value) ⇒ Object
Declares a condition.
-
.conditions ⇒ Object
an inheritable map of conditions, by name.
-
.configuration_for(ability) ⇒ Object
all the [rule, action] pairs that apply to a particular ability.
-
.delegate(name = nil, &delegation_block) ⇒ Object
declaration methods ###.
-
.delegations ⇒ Object
an inheritable map of delegations, indexed by name (which may be autogenerated).
-
.desc(description) ⇒ Object
Declare a description for the following condition.
-
.enable_when(abilities, rule) ⇒ Object
These next three methods are mainly called from PolicyDsl, and are responsible for “inverting” the relationship between an ability and a rule.
-
.global_actions ⇒ Object
a list of global actions, generated by ‘prevent_all`.
-
.last_options ⇒ Object
A hash in which to store calls to ‘desc` and `with_scope`, etc.
-
.overrides(*names) ⇒ Object
Declare that the given abilities should not be read from delegates.
- .own_ability_map ⇒ Object
- .own_conditions ⇒ Object
- .own_delegations ⇒ Object
- .own_global_actions ⇒ Object
-
.prevent_all_when(rule) ⇒ Object
we store global prevents (from ‘prevent_all`) separately, so that they can be combined into every decision made.
- .prevent_when(abilities, rule) ⇒ Object
-
.rule(&block) ⇒ Object
Declares a rule, constructed using RuleDsl, and returns a PolicyDsl which is used for registering the rule with this class.
- .with_options(opts = {}) ⇒ Object
-
.with_scope(scope) ⇒ Object
Declare a scope for the following condition.
-
.with_score(score) ⇒ Object
Declare a score for the following condition.
Instance Method Summary collapse
-
#allowed?(*abilities) ⇒ Boolean
This is the main entry point for permission checks.
-
#banned? ⇒ Boolean
used in specs - returns true if there is no possible way for any action to be allowed, determined only by the global :prevent_all rules.
-
#cache(key) ⇒ Object
Helpers for caching.
- #cached?(key) ⇒ Boolean
-
#can?(ability, new_subject = :_self) ⇒ Boolean
helper for checking abilities on this and other subjects for the current user.
-
#condition(name) ⇒ Object
returns a ManifestCondition capable of computing itself.
-
#debug(ability, *args) ⇒ Object
computes the given ability and prints a helpful debugging output showing which.
-
#delegated_policies ⇒ Object
A list of other policies that we’ve delegated to (see ‘Base.delegate`).
-
#disallowed?(*abilities) ⇒ Boolean
The inverse of #allowed?, used mainly in specs.
- #identify_subject ⇒ Object
- #identify_user ⇒ Object
-
#initialize(user, subject, opts = {}) ⇒ Base
constructor
A new instance of Base.
- #inspect ⇒ Object
- #policy_for(other_subject) ⇒ Object
- #repr ⇒ Object
-
#runner(ability) ⇒ Object
returns a Runner for the given ability, capable of computing whether the ability is allowed.
- #runners ⇒ Object
Constructor Details
#initialize(user, subject, opts = {}) ⇒ Base
Returns a new instance of Base.
241 242 243 244 245 |
# File 'lib/declarative_policy/base.rb', line 241 def initialize(user, subject, opts = {}) @user = user @subject = subject @cache = opts[:cache] || {} end |
Instance Attribute Details
#subject ⇒ Object (readonly)
A policy object contains a specific user and subject on which to compute abilities. For this reason it’s sometimes called “context” within the framework.
It also stores a reference to the cache, so it can be used to cache computations by e.g. ManifestCondition.
239 240 241 |
# File 'lib/declarative_policy/base.rb', line 239 def subject @subject end |
#user ⇒ Object (readonly)
A policy object contains a specific user and subject on which to compute abilities. For this reason it’s sometimes called “context” within the framework.
It also stores a reference to the cache, so it can be used to cache computations by e.g. ManifestCondition.
239 240 241 |
# File 'lib/declarative_policy/base.rb', line 239 def user @user end |
Class Method Details
.ability_map ⇒ Object
The ‘own_ability_map` vs `ability_map` distinction is used so that the data structure is properly inherited - with subclasses recursively merging their parent class.
This pattern is also used for conditions, global_actions, and delegations.
60 61 62 63 64 65 66 |
# File 'lib/declarative_policy/base.rb', line 60 def ability_map if self == Base own_ability_map else superclass.ability_map.merge(own_ability_map) end end |
.condition(condition_name, opts = {}, &value) ⇒ Object
Declares a condition. It gets stored in ‘own_conditions`, and generates a query method based on the condition’s name.
193 194 195 196 197 198 199 200 201 |
# File 'lib/declarative_policy/base.rb', line 193 def condition(condition_name, opts = {}, &value) condition_name = condition_name.to_sym condition = Condition.new(condition_name, (opts), &value) own_conditions[condition_name] = condition define_method(:"#{condition_name}?") { condition(condition_name).pass? } end |
.conditions ⇒ Object
an inheritable map of conditions, by name
73 74 75 76 77 78 79 |
# File 'lib/declarative_policy/base.rb', line 73 def conditions if self == Base own_conditions else superclass.conditions.merge(own_conditions) end end |
.configuration_for(ability) ⇒ Object
all the [rule, action] pairs that apply to a particular ability. we combine the specific ones looked up in ability_map with the global ones.
117 118 119 |
# File 'lib/declarative_policy/base.rb', line 117 def configuration_for(ability) ability_map.actions(ability) + global_actions end |
.delegate(name = nil, &delegation_block) ⇒ Object
declaration methods ###
123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 |
# File 'lib/declarative_policy/base.rb', line 123 def delegate(name = nil, &delegation_block) if name.nil? @delegate_name_counter ||= 0 @delegate_name_counter += 1 name = :"anonymous_#{@delegate_name_counter}" end name = name.to_sym # rubocop: disable GitlabSecurity/PublicSend delegation_block = proc { @subject.__send__(name) } if delegation_block.nil? # rubocop: enable GitlabSecurity/PublicSend own_delegations[name] = delegation_block end |
.delegations ⇒ Object
an inheritable map of delegations, indexed by name (which may be autogenerated)
102 103 104 105 106 107 108 |
# File 'lib/declarative_policy/base.rb', line 102 def delegations if self == Base own_delegations else superclass.delegations.merge(own_delegations) end end |
.desc(description) ⇒ Object
Declare a description for the following condition. Currently unused, but opens the potential for explaining to users why they were or were not able to do something.
177 178 179 |
# File 'lib/declarative_policy/base.rb', line 177 def desc(description) description: description end |
.enable_when(abilities, rule) ⇒ Object
These next three methods are mainly called from PolicyDsl, and are responsible for “inverting” the relationship between an ability and a rule. We store in ‘ability_map` a map of abilities to rules that affect them, together with a symbol indicating :prevent or :enable.
208 209 210 |
# File 'lib/declarative_policy/base.rb', line 208 def enable_when(abilities, rule) abilities.each { |a| own_ability_map.enable(a, rule) } end |
.global_actions ⇒ Object
a list of global actions, generated by ‘prevent_all`. these aren’t stored in ‘ability_map` because they aren’t indexed by a particular ability.
88 89 90 91 92 93 94 |
# File 'lib/declarative_policy/base.rb', line 88 def global_actions if self == Base own_global_actions else superclass.global_actions + own_global_actions end end |
.last_options ⇒ Object
A hash in which to store calls to ‘desc` and `with_scope`, etc.
166 167 168 |
# File 'lib/declarative_policy/base.rb', line 166 def @last_options ||= Options.new end |
.overrides(*names) ⇒ Object
Declare that the given abilities should not be read from delegates.
This is useful if you have an ability that you want to define differently in a policy than in a delegated policy, but still want to delegate all other abilities.
example:
delegate { @subect.parent }
overrides :drive_car, :watch_tv
151 152 153 154 |
# File 'lib/declarative_policy/base.rb', line 151 def overrides(*names) @overrides ||= [].to_set @overrides.merge(names) end |
.own_ability_map ⇒ Object
68 69 70 |
# File 'lib/declarative_policy/base.rb', line 68 def own_ability_map @own_ability_map ||= AbilityMap.new end |
.own_conditions ⇒ Object
81 82 83 |
# File 'lib/declarative_policy/base.rb', line 81 def own_conditions @own_conditions ||= {} end |
.own_delegations ⇒ Object
110 111 112 |
# File 'lib/declarative_policy/base.rb', line 110 def own_delegations @own_delegations ||= {} end |
.own_global_actions ⇒ Object
96 97 98 |
# File 'lib/declarative_policy/base.rb', line 96 def own_global_actions @own_global_actions ||= [] end |
.prevent_all_when(rule) ⇒ Object
we store global prevents (from ‘prevent_all`) separately, so that they can be combined into every decision made.
218 219 220 |
# File 'lib/declarative_policy/base.rb', line 218 def prevent_all_when(rule) own_global_actions << [:prevent, rule] end |
.prevent_when(abilities, rule) ⇒ Object
212 213 214 |
# File 'lib/declarative_policy/base.rb', line 212 def prevent_when(abilities, rule) abilities.each { |a| own_ability_map.prevent(a, rule) } end |
.rule(&block) ⇒ Object
Declares a rule, constructed using RuleDsl, and returns a PolicyDsl which is used for registering the rule with this class. PolicyDsl will call back into Base.enable_when, Base.prevent_when, and Base.prevent_all_when.
160 161 162 163 |
# File 'lib/declarative_policy/base.rb', line 160 def rule(&block) rule = RuleDsl.new(self).instance_eval(&block) PolicyDsl.new(self, rule) end |
.with_options(opts = {}) ⇒ Object
170 171 172 |
# File 'lib/declarative_policy/base.rb', line 170 def (opts = {}) .to_h.merge!(opts.to_h) end |
.with_scope(scope) ⇒ Object
Declare a scope for the following condition.
182 183 184 |
# File 'lib/declarative_policy/base.rb', line 182 def with_scope(scope) scope: scope end |
.with_score(score) ⇒ Object
Declare a score for the following condition.
187 188 189 |
# File 'lib/declarative_policy/base.rb', line 187 def with_score(score) score: score end |
Instance Method Details
#allowed?(*abilities) ⇒ Boolean
This is the main entry point for permission checks. It constructs or looks up a Runner for the given ability and asks it if it passes.
257 258 259 |
# File 'lib/declarative_policy/base.rb', line 257 def allowed?(*abilities) abilities.all? { |a| runner(a).pass? } end |
#banned? ⇒ Boolean
used in specs - returns true if there is no possible way for any action to be allowed, determined only by the global :prevent_all rules.
354 355 356 357 |
# File 'lib/declarative_policy/base.rb', line 354 def banned? global_steps = self.class.global_actions.map { |(action, rule)| Step.new(self, rule, action) } !Runner.new(global_steps).pass? end |
#cache(key) ⇒ Object
Helpers for caching. Used by ManifestCondition in performing condition computation.
NOTE we can’t use ||= here because the value might be the boolean ‘false`
329 330 331 332 333 |
# File 'lib/declarative_policy/base.rb', line 329 def cache(key) return @cache[key] if cached?(key) @cache[key] = yield end |
#cached?(key) ⇒ Boolean
335 336 337 |
# File 'lib/declarative_policy/base.rb', line 335 def cached?(key) !@cache[key].nil? end |
#can?(ability, new_subject = :_self) ⇒ Boolean
helper for checking abilities on this and other subjects for the current user.
249 250 251 252 253 |
# File 'lib/declarative_policy/base.rb', line 249 def can?(ability, new_subject = :_self) return allowed?(ability) if new_subject == :_self policy_for(new_subject).allowed?(ability) end |
#condition(name) ⇒ Object
returns a ManifestCondition capable of computing itself. The computation will use our own @cache.
341 342 343 344 345 346 347 348 349 350 |
# File 'lib/declarative_policy/base.rb', line 341 def condition(name) name = name.to_sym @_conditions ||= {} @_conditions[name] ||= begin raise "invalid condition #{name}" unless self.class.conditions.key?(name) ManifestCondition.new(self.class.conditions[name], self) end end |
#debug(ability, *args) ⇒ Object
computes the given ability and prints a helpful debugging output showing which
268 269 270 |
# File 'lib/declarative_policy/base.rb', line 268 def debug(ability, *args) runner(ability).debug(*args) end |
#delegated_policies ⇒ Object
A list of other policies that we’ve delegated to (see ‘Base.delegate`)
360 361 362 363 364 365 366 367 368 369 |
# File 'lib/declarative_policy/base.rb', line 360 def delegated_policies @delegated_policies ||= self.class.delegations.transform_values do |block| new_subject = instance_eval(&block) # never delegate to nil, as that would immediately prevent_all next if new_subject.nil? policy_for(new_subject) end end |
#disallowed?(*abilities) ⇒ Boolean
The inverse of #allowed?, used mainly in specs.
262 263 264 |
# File 'lib/declarative_policy/base.rb', line 262 def disallowed?(*abilities) abilities.all? { |a| !runner(a).pass? } end |
#identify_subject ⇒ Object
290 291 292 293 294 295 296 |
# File 'lib/declarative_policy/base.rb', line 290 def identify_subject if @subject.respond_to?(:id) "#{@subject.class.name}/#{@subject.id}" else @subject.inspect end end |
#identify_user ⇒ Object
282 283 284 285 286 287 288 |
# File 'lib/declarative_policy/base.rb', line 282 def identify_user return '<anonymous>' unless @user @user.to_reference rescue NoMethodError "<#{@user.class}: #{@user.object_id}>" end |
#inspect ⇒ Object
298 299 300 |
# File 'lib/declarative_policy/base.rb', line 298 def inspect "#<#{self.class.name} #{repr}>" end |
#policy_for(other_subject) ⇒ Object
371 372 373 |
# File 'lib/declarative_policy/base.rb', line 371 def policy_for(other_subject) DeclarativePolicy.policy_for(@user, other_subject, cache: @cache) end |
#repr ⇒ Object
278 279 280 |
# File 'lib/declarative_policy/base.rb', line 278 def repr "(#{identify_user} : #{identify_subject})" end |
#runner(ability) ⇒ Object
returns a Runner for the given ability, capable of computing whether the ability is allowed. Runners are cached on the policy (which itself is cached on @cache), and caches its result. This is how we perform caching at the ability level.
306 307 308 309 310 311 312 313 314 315 316 317 318 |
# File 'lib/declarative_policy/base.rb', line 306 def runner(ability) ability = ability.to_sym runners[ability] ||= begin own_runner = Runner.new(own_steps(ability)) if self.class.overrides.include?(ability) own_runner else delegated_runners = delegated_policies.values.compact.map { |p| p.runner(ability) } delegated_runners.reduce(own_runner, &:merge_runner) end end end |
#runners ⇒ Object
320 321 322 |
# File 'lib/declarative_policy/base.rb', line 320 def runners @runners ||= {} end |