Optimize: custom ability verification

What does this MR do and why?

Contributes to #482942 (closed)

Problem

We use Authz::CustomAbility.allowed? to check custom abilities of the user. But for each ability we trigger two database requests to fetch the same project and namespace. That leads to N+1 problem.

Solution

  1. Restructure Authz::CustomAbility to support caching.

The code below won't trigger unnecessary database queries for each allowed? call.

ability = Authz::CustomAbility.new(user, project)
ability.allowed?(:ability_1)
ability.allowed?(:ability_2)
  1. Add caching level to the policy code

Authz::CustomAbility record will be memoized and have access to permitted abilities to optimize the number of DB queries.

References

Please include cross links to any resources that are relevant to this MR This will give reviewers and future readers helpful context to give an efficient review of the changes introduced.

MR acceptance checklist

Please evaluate this MR against the MR acceptance checklist. It helps you analyze changes to reduce risks in quality, performance, reliability, security, and maintainability.

Screenshots or screen recordings

graph TB
  ProjectPolicy
  Authz::CustomAbility
  Authz::Project
  Preloaders::UserMemberRolesInProjectsPreloader
  Preloaders::ProjectRootAncestorPreloader

  ProjectPolicy ---> Authz::CustomAbility
  Authz::CustomAbility --> Authz::Project
  Authz::Project --> Preloaders::UserMemberRolesInProjectsPreloader
  Preloaders::UserMemberRolesInProjectsPreloader --> Preloaders::ProjectRootAncestorPreloader
  1. In ProjectPolicy we define a condition for each custom ability.
  2. Each Authz::CustomAbility requests a list of permitted abilities Authz::Project#permitted
  3. Preloaders::ProjectRootAncestorPreloader is called for each Authz::Project#permitted and triggers two DB queries for each ability checked.

For some pages it leads to 32 additional requests to projects and namespaces tables.

Edited by Vasilii Iakliushin

Merge request reports

Loading