Open-Source Wikis

/

GitLab

/

Systems

/

Authorization

gitlab-org/gitlab

Authorization

How GitLab decides what a user can do.

Purpose

Express ability rules ("can this user push to this protected branch?") declaratively, with caching, EE extension, and easy reuse from controllers, services, GraphQL, and policies.

The declarative_policy DSL

GitLab uses declarative_policy, a DSL designed in this org. A policy file defines:

  • Conditions — boolean predicates on the subject and current user.
  • Rules — combinations of conditions that enable or prevent abilities.

Example (app/policies/project_policy.rb):

class ProjectPolicy < ::BasePolicy
  condition(:owner) { @user&.id == @subject.creator_id }
  condition(:reporter, scope: :subject) { team_access?(:reporter) }
  condition(:archived, score: 0, scope: :subject) { @subject.archived? }

  rule { reporter | owner }.enable :read_project
  rule { archived }.prevent :push_code
end

Conditions can be scoped (:user, :subject, :global), have a score (cheap conditions tested first), and be cached for the request.

Source layout

app/policies/
├── base_policy.rb            # Root policy class
├── application_policy.rb
├── ability.rb                # `can?(user, :ability, subject)` shortcut
├── project_policy.rb         # The largest policy in the codebase
├── group_policy.rb
├── issue_policy.rb
├── merge_request_policy.rb
├── ci/
│   └── ...                   # CI-related policies
├── concerns/                 # Shared rules and helpers
└── ...                       # ~33 policy directories
ee/app/policies/              # EE-only conditions, prepended onto FOSS classes

Calling abilities

In a controller or service:

return unless can?(current_user, :read_project, project)
authorize!(:push_code, project)   # raises Gitlab::Access::AccessDeniedError

In GraphQL:

field :description, GraphQL::Types::String, null: true,
  authorize: :read_project

In a worker:

return unless current_user.can?(:read_project, project)

EE additions

EE adds conditions like:

  • :auditor — read-only access for auditors.
  • :adminmode_active — required for admin operations under "admin mode".
  • :has_paid_plan — guards EE-only features.
  • :scim_provisioned / :saml_provisioned — SSO state.
  • :beyond_identity_verified.

These are merged into the base policies via prepend_mod_with('ProjectPolicy') etc.

Custom roles

ee/app/services/members/custom_role/ introduces fine-grained custom permissions tied to MemberRole. The policy layer reads MemberRole.permitted_abilities to decide.

Branch- and tag-level permissions

app/services/protected_branches/ and app/services/protected_tags/ evaluate per-ref access. EE adds:

  • Code Owners (ee/app/services/code_owners/).
  • Required approvals tied to MR approval rules.
  • Protected environments.

Performance

Policy lookups are heavily cached:

  • Per-policy cache via DeclarativePolicy::Runner.
  • Per-request cache via RequestStore.
  • Subject-level cache via condition(:foo, scope: :subject).

When a hot path slows down, the perf bar shows policy evaluation time.

Testing

Policies have their own spec convention under spec/policies/:

describe ProjectPolicy do
  let_it_be(:user) { create(:user) }
  let_it_be(:project) { create(:project) }

  subject(:policy) { described_class.new(user, project) }

  it { is_expected.to be_disallowed(:read_project) }
end

Test helpers be_allowed / be_disallowed come from spec/support/policy_matchers.rb.

Where to make changes

  • New ability: declare in the relevant policy with a condition and rule.
  • New EE-only ability: add to ee/app/policies/ and prepend onto the FOSS class.
  • New custom-role permission: see ee/app/services/members/custom_role/.
  • New cross-project access pattern: see lib/gitlab/cross_project_access.rb.

Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.

Authorization – GitLab wiki | Factory