diff options
Diffstat (limited to 'doc/development/permissions.md')
-rw-r--r-- | doc/development/permissions.md | 315 |
1 files changed, 4 insertions, 311 deletions
diff --git a/doc/development/permissions.md b/doc/development/permissions.md index 3abadc98501..aa58447b818 100644 --- a/doc/development/permissions.md +++ b/doc/development/permissions.md @@ -7,315 +7,8 @@ info: To determine the technical writer assigned to the Stage/Group associated w # 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. +anything that deals with permissions, all of them should be considered. For more information, see: -## Instance - -### User types - -Each user can be one of the following types: - -- Regular. -- External - access to groups and projects only if direct member. -- [Internal users](internal_users.md) - system created. -- [Auditor](https://gitlab.com/gitlab-org/gitlab/-/blob/master/ee/app/policies/ee/base_policy.rb#L9): - - No access to projects or groups settings menu. - - No access to Admin Area. - - Read-only access to everything else. -- [Administrator](https://gitlab.com/gitlab-org/gitlab/-/blob/master/app/policies/base_policy.rb#L6) - read-write access. - -See the [permissions page](../user/permissions.md) for details on how each user type is used. - -## Groups and Projects - -### General permissions - -Groups and projects can have the following visibility levels: - -- public (`20`) - an entity is visible to everyone -- internal (`10`) - an entity is visible to authenticated users -- private (`0`) - an entity is visible only to the approved members of the entity - -By default, subgroups can **not** have higher visibility levels. -For example, if you create a new private group, it cannot include a public subgroup. - -The visibility level of a group can be changed only if all subgroups and -sub-projects have the same or lower visibility level. For example, a group can be set -to internal only if all subgroups and projects are internal or private. - -WARNING: -If you migrate an existing group to a lower visibility level, that action does not migrate subgroups -in the same way. This is a [known issue](https://gitlab.com/gitlab-org/gitlab/-/issues/22406). - -Visibility levels can be found in the `Gitlab::VisibilityLevel` module. - -### Feature specific permissions - -Additionally, the following project features can have different visibility levels: - -- Issues -- Repository - - Merge request - - Forks - - Pipelines -- Analytics -- Requirements -- Security and Compliance -- Wiki -- Snippets -- Pages -- Operations -- Metrics Dashboard - -These features can be set to "Everyone with Access" or "Only Project Members". -They make sense only for public or internal projects because private projects -can be accessed only by project members by default. - -### Members - -Users can be members of multiple groups and projects. The following access -levels are available (defined in the -[`Gitlab::Access`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/access.rb) -module): - -- No access (`0`) -- [Minimal access](../user/permissions.md#users-with-minimal-access) (`5`) -- Guest (`10`) -- Reporter (`20`) -- Developer (`30`) -- Maintainer (`40`) -- Owner (`50`) - -If a user is the member of both a project and the project parent groups, the -highest permission is the applied access level for the project. - -If a user is the member of a project, but not the parent groups, they -can still view the groups and their entities (like epics). - -Project membership (where the group membership is already taken into account) -is stored in the `project_authorizations` table. - -NOTE: -In [GitLab 14.9](https://gitlab.com/gitlab-org/gitlab/-/issues/351211) and later, projects in personal namespaces have a maximum role of Owner. -Because of a [known issue](https://gitlab.com/gitlab-org/gitlab/-/issues/219299) in GitLab 14.8 and earlier, projects in personal namespaces have a maximum role of Maintainer. - -### Confidential issues - -[Confidential issues](../user/project/issues/confidential_issues.md) can be accessed -only by project members who are at least -reporters (they can't be accessed by guests). Additionally they can be accessed -by their authors and assignees. - -### Licensed features - -Some features can be accessed only if the user has the correct license plan. - -## Permission dependencies - -Feature policies can be quite complex and consist of multiple rules. -Quite often, one permission can be based on another. - -Designing good permissions means reusing existing permissions as much as possible -and making access to features granular. - -In the case of a complex resource, it should be broken into smaller pieces of information -and each piece should be granted a different permission. - -A good example in this case is the _Merge Request widget_ and the _Security reports_. -Depending on the visibility level of the _Pipelines_, the _Security reports_ are either visible -in the widget or not. So, the _Merge Request widget_, the _Pipelines_, and the _Security reports_, -have separate permissions. Moreover, the permissions for the _Merge Request widget_ -and the _Pipelines_ are dependencies of the _Security reports_. - -### Permission dependencies of Secure features - -Secure features have complex permissions since these features are integrated -into different features like Merge Requests and CI flow. - - Here is a list of some permission dependencies. - -| Activity level | Resource | Locations |Permission dependency| -|----------------|----------|-----------|-----| -| View | License information | Dependency list, License Compliance | Can view repository | -| View | Dependency information | Dependency list, License Compliance | Can view repository | -| View | Vulnerabilities information | Dependency list | Can view security findings | -| View | Black/Whitelisted licenses for the project | License Compliance, merge request | Can view repository | -| View | Security findings | merge request, CI job page, Pipeline security tab | Can read the project and CI jobs | -| View | Vulnerability feedback | merge request | Can read security findings | -| View | Dependency List page | Project | Can access Dependency information | -| View | License Compliance page | Project | Can access License information| - -## Where should permissions be checked? - -We should typically apply defense-in-depth (implementing multiple checks at -various layers) starting with low-level layers, such as finders and services, -followed by high-level layers, such as GraphQL, public REST API, and controllers. - -See [Guidelines for reusing abstractions](reusing_abstractions.md). - -Protecting the same resources at many points means that if one layer of defense is compromised -or missing, customer data is still protected by the additional layers. - -See the permissions section in the [Secure Coding Guidelines](secure_coding_guidelines.md#permissions). - -### Considerations - -Services or finders are appropriate locations because: - -- Multiple endpoints share services or finders so downstream logic is more likely to be re-used. -- Sometimes authorization logic must be incorporated in DB queries to filter records. -- Permission checks at the display layer should be avoided except to provide better UX - and not as a security check. For example, showing and hiding non-data elements like buttons. - -The downsides to defense-in-depth are: - -- `DeclarativePolicy` rules are relatively performant, but conditions may perform database calls. -- Higher maintenance costs. - -### Exceptions - -Developers can choose to do authorization in only a single area after weighing -the risks and drawbacks for their specific case. - -Prefer domain logic (services or finders) as the source of truth when making exceptions. - -Logic, like backend worker logic, might not need authorization based on the current user. -If the service or finder's constructor does not expect `current_user`, then it typically won't -check permissions. - -### Tips - -If a class accepts `current_user`, then it may be responsible for authorization. - -### Example: Adding a new API endpoint - -By default, we authorize at the endpoint. Checking an existing ability may make sense; if not, then we probably need to add one. - -As an aside, most endpoints can be cleanly categorized as a CRUD (create, read, update, destroy) action on a resource. The services and abilities follow suit, which is why many are named like `Projects::CreateService` or `:read_project`. - -Say, for example, we extract the whole endpoint into a service. The `can?` check will now be in the service. Say the service reuses an existing finder, which we are modifying for our purposes. Should we make the finder check an ability? - -- 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 -``` +- [Predefined roles system](permissions/predefined_roles.md): a general overview about predefined roles, user types, feature specific permissions or permissions dependencies. +- [Authorizations](permissions/authorizations.md): guidance on where to check permissions. +- [Custom roles](permissions/custom_roles.md): guidance on how to work on custom role, how to introduce a new ability for custom roles, how to refactor permissions. |