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 'doc/development/permissions.md')
-rw-r--r--doc/development/permissions.md125
1 files changed, 123 insertions, 2 deletions
diff --git a/doc/development/permissions.md b/doc/development/permissions.md
index bf7f99de1ab..3abadc98501 100644
--- a/doc/development/permissions.md
+++ b/doc/development/permissions.md
@@ -4,7 +4,7 @@ group: Authentication and Authorization
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments
---
-# Implementing permissions
+# Permission development guidelines
There are multiple types of permissions across GitLab, and when implementing
anything that deals with permissions, all of them should be considered.
@@ -60,7 +60,7 @@ Additionally, the following project features can have different visibility level
- Pipelines
- Analytics
- Requirements
-- Security & Compliance
+- Security and Compliance
- Wiki
- Snippets
- Pages
@@ -198,3 +198,124 @@ Say, for example, we extract the whole endpoint into a service. The `can?` check
- If the finder doesn't accept `current_user`, and therefore doesn't check permissions, then probably no.
- If the finder accepts `current_user`, and doesn't check permissions, then it would be a good idea to double check other usages of the finder, and we might consider adding authorization.
- If the finder accepts `current_user`, and already checks permissions, then either we need to add our case, or the existing checks are appropriate.
+
+### Refactoring permissions
+
+#### Finding existing permissions checks
+
+As mentioned [above](#where-should-permissions-be-checked), permissions are
+often checked in multiple locations for a single endpoint or web request. As a
+result, finding the list of authorization checks that are run for a given endpoint
+can be challenging.
+
+To assist with this, you can locally set `GITLAB_DEBUG_POLICIES=true`.
+
+This outputs information about which abilities are checked in the requests
+made in any specs that you run. The output also includes the line of code where the
+authorization check was made. Caller information is especially helpful in cases
+where there is metaprogramming used because those cases are difficult to find by
+grepping for ability name strings.
+
+Example:
+
+```shell
+# example spec run
+
+GITLAB_DEBUG_POLICIES=true bundle exec rspec spec/controllers/groups_controller_spec.rb:162
+
+# permissions debug output when spec is run; if multiple policy checks are run they will all be in the debug output.
+
+POLICY CHECK DEBUG -> policy: GlobalPolicy, ability: create_group, called_from: ["/gitlab/app/controllers/application_controller.rb:245:in `can?'", "/gitlab/app/controllers/groups_controller.rb:255:in `authorize_create_group!'"]
+```
+
+This flag is meant to help learn more about authorization checks while
+refactoring and should not remain enabled for any specs on the default branch.
+
+#### Understanding logic for individual abilities
+
+References to an ability may appear in a `DeclarativePolicy` class many times
+and depend on conditions and rules which reference other abilities. As a result,
+it can be challenging to know exactly which conditions apply to a particular
+ability.
+
+`DeclarativePolicy` provides a `ability_map` for each Policy class, which
+pulls all Rules for an ability into an array.
+
+Example:
+
+```ruby
+> GroupPolicy.ability_map.map.select { |k,v| k == :read_group_member }
+=> {:read_group_member=>[[:enable, #<Rule can?(:read_group)>], [:prevent, #<Rule ~can_read_group_member>]]}
+
+> GroupPolicy.ability_map.map.select { |k,v| k == :read_group }
+=> {:read_group=>
+ [[:enable, #<Rule public_group>],
+ [:enable, #<Rule logged_in_viewable>],
+ [:enable, #<Rule guest>],
+ [:enable, #<Rule admin>],
+ [:enable, #<Rule has_projects>],
+ [:enable, #<Rule read_package_registry_deploy_token>],
+ [:enable, #<Rule write_package_registry_deploy_token>],
+ [:prevent, #<Rule all?(~public_group, ~admin, user_banned_from_group)>],
+ [:enable, #<Rule auditor>],
+ [:prevent, #<Rule needs_new_sso_session>],
+ [:prevent, #<Rule all?(ip_enforcement_prevents_access, ~owner, ~auditor)>]]}
+```
+
+`DeclarativePolicy` also provides a `debug` method that can be used to
+understand the logic tree for a specific object and actor. The output is similar
+to the list of rules from `ability_map`. But, `DeclarativePolicy` stops
+evaluating rules once one `prevent`s an ability, so it is possible that
+not all conditions are called.
+
+Example:
+
+```ruby
+policy = GroupPolicy.new(User.last, Group.last)
+policy.debug(:read_group)
+
+- [0] enable when public_group ((@custom_guest_user1 : Group/139))
+- [0] enable when logged_in_viewable ((@custom_guest_user1 : Group/139))
+- [0] enable when admin ((@custom_guest_user1 : Group/139))
+- [0] enable when auditor ((@custom_guest_user1 : Group/139))
+- [14] prevent when all?(~public_group, ~admin, user_banned_from_group) ((@custom_guest_user1 : Group/139))
+- [14] prevent when needs_new_sso_session ((@custom_guest_user1 : Group/139))
+- [16] enable when guest ((@custom_guest_user1 : Group/139))
+- [16] enable when has_projects ((@custom_guest_user1 : Group/139))
+- [16] enable when read_package_registry_deploy_token ((@custom_guest_user1 : Group/139))
+- [16] enable when write_package_registry_deploy_token ((@custom_guest_user1 : Group/139))
+ [21] prevent when all?(ip_enforcement_prevents_access, ~owner, ~auditor) ((@custom_guest_user1 : Group/139))
+
+=> #<DeclarativePolicy::Runner::State:0x000000015c665050
+ @called_conditions=
+ #<Set: {
+ "/dp/condition/GroupPolicy/public_group/Group:139",
+ "/dp/condition/GroupPolicy/logged_in_viewable/User:83,Group:139",
+ "/dp/condition/BasePolicy/admin/User:83",
+ "/dp/condition/BasePolicy/auditor/User:83",
+ "/dp/condition/GroupPolicy/user_banned_from_group/User:83,Group:139",
+ "/dp/condition/GroupPolicy/needs_new_sso_session/User:83,Group:139",
+ "/dp/condition/GroupPolicy/guest/User:83,Group:139",
+ "/dp/condition/GroupPolicy/has_projects/User:83,Group:139",
+ "/dp/condition/GroupPolicy/read_package_registry_deploy_token/User:83,Group:139",
+ "/dp/condition/GroupPolicy/write_package_registry_deploy_token/User:83,Group:139"}>,
+ @enabled=false,
+ @prevented=true>
+```
+
+#### Testing that individual policies are equivalent
+
+You can use the `'equivalent project policy abilities'` shared example to ensure
+that 2 project policy abilities are equivalent for all project visibility levels
+and access levels.
+
+Example:
+
+```ruby
+ context 'when refactoring read_pipeline_schedule and read_pipeline' do
+ let(:old_policy) { :read_pipeline_schedule }
+ let(:new_policy) { :read_pipeline }
+
+ it_behaves_like 'equivalent policies'
+ end
+```