Class: Ability
- Inherits:
-
Object
- Object
- Ability
- Defined in:
- app/models/ability.rb
Class Method Summary collapse
- .allowed?(user, ability, subject = :global, **opts) ⇒ Boolean
-
.before_check(policy, ability, user, subject, opts) ⇒ Object
Hook call right before ability check.
- .feature_flags_readable_by_user(feature_flags, user = nil, filters: {}) ⇒ Object
-
.forgetting(pattern, &block) ⇒ Object
This method is something of a band-aid over the problem.
-
.issues_readable_by_user(issues, user = nil, filters: {}) ⇒ Object
(also: work_items_readable_by_user)
Returns an Array of Issues that can be read by the given user.
-
.merge_requests_readable_by_user(merge_requests, user = nil, filters: {}) ⇒ Object
Returns an Array of MergeRequests that can be read by the given user.
-
.policy_for(user, subject = :global, cache: true) ⇒ Object
We cache in the request store by default.
-
.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.
-
.users_that_can_read_internal_notes(users, note_parent) ⇒ Object
A list of users that can read confidential notes in a project.
-
.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.
-
.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.
Class Method Details
.allowed?(user, ability, subject = :global, **opts) ⇒ 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 |