Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'lib/declarative_policy/runner.rb')
-rw-r--r--lib/declarative_policy/runner.rb196
1 files changed, 0 insertions, 196 deletions
diff --git a/lib/declarative_policy/runner.rb b/lib/declarative_policy/runner.rb
deleted file mode 100644
index 59588b4d84e..00000000000
--- a/lib/declarative_policy/runner.rb
+++ /dev/null
@@ -1,196 +0,0 @@
-# frozen_string_literal: true
-
-module DeclarativePolicy
- class Runner
- class State
- def initialize
- @enabled = false
- @prevented = false
- end
-
- def enable!
- @enabled = true
- end
-
- def enabled?
- @enabled
- end
-
- def prevent!
- @prevented = true
- end
-
- def prevented?
- @prevented
- end
-
- def pass?
- !prevented? && enabled?
- end
- end
-
- # a Runner contains a list of Steps to be run.
- attr_reader :steps
- def initialize(steps)
- @steps = steps
- @state = nil
- end
-
- # We make sure only to run any given Runner once,
- # and just continue to use the resulting @state
- # that's left behind.
- def cached?
- !!@state
- end
-
- # used by Rule::Ability. See #steps_by_score
- def score
- return 0 if cached?
-
- steps.map(&:score).inject(0, :+)
- end
-
- def merge_runner(other)
- Runner.new(@steps + other.steps)
- end
-
- # The main entry point, called for making an ability decision.
- # See #run and DeclarativePolicy::Base#can?
- def pass?
- run unless cached?
-
- @state.pass?
- end
-
- # see DeclarativePolicy::Base#debug
- def debug(out = $stderr)
- run(out)
- end
-
- private
-
- def flatten_steps!
- @steps = @steps.flat_map { |s| s.flattened(@steps) }
- end
-
- # This method implements the semantic of "one enable and no prevents".
- # It relies on #steps_by_score for the main loop, and updates @state
- # with the result of the step.
- def run(debug = nil)
- @state = State.new
-
- steps_by_score do |step, score|
- break if !debug && @state.prevented?
-
- passed = nil
- case step.action
- when :enable then
- # we only check :enable actions if they have a chance of
- # changing the outcome - if no other rule has enabled or
- # prevented.
- unless @state.enabled? || @state.prevented?
- passed = step.pass?
- @state.enable! if passed
- end
-
- debug << inspect_step(step, score, passed) if debug
- when :prevent then
- # we only check :prevent actions if the state hasn't already
- # been prevented.
- unless @state.prevented?
- passed = step.pass?
- @state.prevent! if passed
- end
-
- debug << inspect_step(step, score, passed) if debug
- else raise "invalid action #{step.action.inspect}"
- end
- end
-
- @state
- end
-
- # This is the core spot where all those `#score` methods matter.
- # It is critical for performance to run steps in the correct order,
- # so that we don't compute expensive conditions (potentially n times
- # if we're called on, say, a large list of users).
- #
- # In order to determine the cheapest step to run next, we rely on
- # Step#score, which returns a numerical rating of how expensive
- # it would be to calculate - the lower the better. It would be
- # easy enough to statically sort by these scores, but we can do
- # a little better - the scores are cache-aware (conditions that
- # are already in the cache have score 0), which means that running
- # a step can actually change the scores of other steps.
- #
- # So! The way we sort here involves re-scoring at every step. This
- # is by necessity quadratic, but most of the time the number of steps
- # will be low. But just in case, if the number of steps exceeds 50,
- # we print a warning and fall back to a static sort.
- #
- # For each step, we yield the step object along with the computed score
- # for debugging purposes.
- def steps_by_score
- flatten_steps!
-
- if @steps.size > 50
- warn "DeclarativePolicy: large number of steps (#{steps.size}), falling back to static sort"
-
- @steps.map { |s| [s.score, s] }.sort_by { |(score, _)| score }.each do |(score, step)|
- yield step, score
- end
-
- return
- end
-
- remaining_steps = Set.new(@steps)
- remaining_enablers, remaining_preventers = remaining_steps.partition(&:enable?).map { |s| Set.new(s) }
-
- loop do
- if @state.enabled?
- # Once we set this, we never need to unset it, because a single
- # prevent will stop this from being enabled
- remaining_steps = remaining_preventers
- else
- # if the permission hasn't yet been enabled and we only have
- # prevent steps left, we short-circuit the state here
- @state.prevent! if remaining_enablers.empty?
- end
-
- return if remaining_steps.empty?
-
- lowest_score = Float::INFINITY
- next_step = nil
-
- remaining_steps.each do |step|
- score = step.score
-
- if score < lowest_score
- next_step = step
- lowest_score = score
- end
-
- break if lowest_score == 0
- end
-
- [remaining_steps, remaining_enablers, remaining_preventers].each do |set|
- set.delete(next_step)
- end
-
- yield next_step, lowest_score
- end
- end
-
- # Formatter for debugging output.
- def inspect_step(step, original_score, passed)
- symbol =
- case passed
- when true then '+'
- when false then '-'
- when nil then ' '
- end
-
- "#{symbol} [#{original_score.to_i}] #{step.repr}\n"
- end
- end
-end