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
endConditions 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 classesCalling abilities
In a controller or service:
return unless can?(current_user, :read_project, project)
authorize!(:push_code, project) # raises Gitlab::Access::AccessDeniedErrorIn GraphQL:
field :description, GraphQL::Types::String, null: true,
authorize: :read_projectIn 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) }
endTest 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
conditionandrule. - 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.
Related
- Authentication — who you are.
- Patterns and conventions — how policies fit with services.
Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.