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)


73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
# File 'app/models/ability.rb', line 73

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

  policy = policy_for(user, subject, **opts.slice(:cache))

  before_check(policy, ability.to_sym, user, subject, opts)

  result = case opts[:scope]
           when :user
             DeclarativePolicy.user_scope { policy.allowed?(ability) }
           when :subject
             DeclarativePolicy.subject_scope { policy.allowed?(ability) }
           else
             policy.allowed?(ability)
           end

  identity = ::Gitlab::Auth::Identity.fabricate(user)

  if identity.present? && identity.composite?
    result && allowed?(identity.scoped_user, ability, subject, **opts)
  else
    result
  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

.before_check(policy, ability, user, subject, opts) ⇒ Object

Hook call right before ability check.



108
109
110
# File 'app/models/ability.rb', line 108

def before_check(policy, ability, user, subject, opts)
  # See Support::AbilityCheck and Support::PermissionsCheck.
end

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



65
66
67
68
69
70
71
# File 'app/models/ability.rb', line 65

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



141
142
143
144
145
146
147
148
149
150
# File 'app/models/ability.rb', line 141

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 Also known as: work_items_readable_by_user

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


42
43
44
45
46
47
48
# File 'app/models/ability.rb', line 42

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


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

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, cache: true) ⇒ Object

We cache in the request store by default. This can lead to unexpected results if abilities are re-checked after objects are modified and the check depends on the modified attributes. In such cases, you should pass ‘cache: false` for the second check to ensure all rules get re-evaluated.



116
117
118
119
120
# File 'app/models/ability.rb', line 116

def policy_for(user, subject = :global, cache: true)
  policy_cache = cache ? ::Gitlab::SafeRequestStore.storage : {}

  DeclarativePolicy.policy_for(user, subject, cache: policy_cache)
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_internal_notes(users, note_parent) ⇒ Object

A list of users that can read confidential notes in a project



30
31
32
33
34
# File 'app/models/ability.rb', line 30

def users_that_can_read_internal_notes(users, note_parent)
  DeclarativePolicy.subject_scope do
    users.select { |u| allowed?(u, :read_internal_note, note_parent) }
  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