Class: Ability

Inherits:
Object
  • Object
show all
Defined in:
app/models/ability.rb

Class Method Summary collapse

Class Method Details

.allowed?(user, ability, subject = :global, opts = {}) ⇒ Boolean

Returns:

  • (Boolean)

65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
# File 'app/models/ability.rb', line 65

def allowed?(user, ability, subject = :global, opts = {})
  if subject.is_a?(Hash)
    opts = subject
    subject = :global
  end

  policy = policy_for(user, subject)

  case opts[:scope]
  when :user
    DeclarativePolicy.user_scope { policy.allowed?(ability) }
  when :subject
    DeclarativePolicy.subject_scope { policy.allowed?(ability) }
  else
    policy.allowed?(ability)
  end
ensure
  # TODO: replace with runner invalidation:
  # See: https://gitlab.com/gitlab-org/declarative-policy/-/merge_requests/24
  # See: https://gitlab.com/gitlab-org/declarative-policy/-/merge_requests/25
  forget_runner_result(policy.runner(ability)) if policy && ability_forgetting?
end

.feature_flags_readable_by_user(feature_flags, user = nil, filters: {}) ⇒ Object


57
58
59
60
61
62
63
# File 'app/models/ability.rb', line 57

def feature_flags_readable_by_user(feature_flags, user = nil, filters: {})
  feature_flags = apply_filters_if_needed(feature_flags, user, filters)

  DeclarativePolicy.user_scope do
    feature_flags.select { |flag| allowed?(user, :read_feature_flag, flag) }
  end
end

.forgetting(pattern, &block) ⇒ Object

This method is something of a band-aid over the problem. The problem is that some conditions may not be re-entrant, if facts change. (`BasePolicy#admin?` is a known offender, due to the effects of `admin_mode`)

To deal with this we need to clear two elements of state: the offending conditions (selected by 'pattern') and the cached ability checks (cached on the `policy#runner(ability)`).

Clearing the conditions (see `forget_all_but`) is fairly robust, provided the pattern is not under-selective. Clearing the runners is harder, since there is not good way to know which abilities any given condition may affect. The approach taken here (see `forget_runner_result`) is to discard all runner results generated during a `forgetting` block. This may be under-selective if a runner prior to this block cached a state value that might now be invalid.

TODO: add some kind of reverse-dependency mapping in DeclarativePolicy See: gitlab.com/gitlab-org/declarative-policy/-/issues/14


111
112
113
114
115
116
117
118
119
120
# File 'app/models/ability.rb', line 111

def forgetting(pattern, &block)
  was_forgetting = ability_forgetting?
  ::Gitlab::SafeRequestStore[:ability_forgetting] = true
  keys_before = ::Gitlab::SafeRequestStore.storage.keys

  yield
ensure
  ::Gitlab::SafeRequestStore[:ability_forgetting] = was_forgetting
  forget_all_but(keys_before, matching: pattern)
end

.issues_readable_by_user(issues, user = nil, filters: {}) ⇒ Object

Returns an Array of Issues that can be read by the given user.

issues - The issues to reduce down to those readable by the user. user - The User for which to check the issues filters - A hash of abilities and filters to apply if the user lacks this

ability

35
36
37
38
39
40
41
# File 'app/models/ability.rb', line 35

def issues_readable_by_user(issues, user = nil, filters: {})
  issues = apply_filters_if_needed(issues, user, filters)

  DeclarativePolicy.user_scope do
    issues.select { |issue| issue.visible_to_user?(user) }
  end
end

.merge_requests_readable_by_user(merge_requests, user = nil, filters: {}) ⇒ Object

Returns an Array of MergeRequests that can be read by the given user.

merge_requests - MRs out of which to collect MRs readable by the user. user - The User for which to check the merge_requests filters - A hash of abilities and filters to apply if the user lacks this

ability

49
50
51
52
53
54
55
# File 'app/models/ability.rb', line 49

def merge_requests_readable_by_user(merge_requests, user = nil, filters: {})
  merge_requests = apply_filters_if_needed(merge_requests, user, filters)

  DeclarativePolicy.user_scope do
    merge_requests.select { |mr| allowed?(user, :read_merge_request, mr) }
  end
end

.policy_for(user, subject = :global) ⇒ Object


88
89
90
# File 'app/models/ability.rb', line 88

def policy_for(user, subject = :global)
  DeclarativePolicy.policy_for(user, subject, cache: ::Gitlab::SafeRequestStore.storage)
end

.users_that_can_read_group(users, group) ⇒ Object

Given a list of users and a group this method returns the users that can read the given group.


15
16
17
18
19
# File 'app/models/ability.rb', line 15

def users_that_can_read_group(users, group)
  DeclarativePolicy.subject_scope do
    users.select { |u| allowed?(u, :read_group, group) }
  end
end

.users_that_can_read_personal_snippet(users, snippet) ⇒ Object

Given a list of users and a snippet this method returns the users that can read the given snippet.


23
24
25
26
27
# File 'app/models/ability.rb', line 23

def users_that_can_read_personal_snippet(users, snippet)
  DeclarativePolicy.subject_scope do
    users.select { |u| allowed?(u, :read_snippet, snippet) }
  end
end

.users_that_can_read_project(users, project) ⇒ Object

Given a list of users and a project this method returns the users that can read the given project.


7
8
9
10
11
# File 'app/models/ability.rb', line 7

def users_that_can_read_project(users, project)
  DeclarativePolicy.subject_scope do
    users.select { |u| allowed?(u, :read_project, project) }
  end
end