Apply namespace bans to AI Catalog item visibility

What does this MR do and why?

Group and project policies deny a banned member everything through prevent_all, but Ai::Catalog::ItemPolicy and the two visibility scopes on Ai::Catalog::Item derived membership on their own, so a ban did not carry over to private and restricted catalog items.

This MR makes item access follow the container policies. ItemPolicy now also requires read_project (private items) and read_group on the top-level group (restricted items). The list scopes add a NOT EXISTS on namespace_bans to their membership subquery, which is a no-op for users with no bans and for admins.

Note for reviewers: requiring read_project and read_group means item reads now also respect other container-level denials such as SSO enforcement and IP restrictions.

References

https://gitlab.com/gitlab-org/gitlab/-/work_items/612769

MR acceptance checklist

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

Database Review

Explore level listing of AI Catalog Items

Query plan - https://console.postgres.ai/gitlab/projects/gitlab-production-main/sessions/56736/commands/161116

Click to expand
SELECT "ai_catalog_items".*
FROM "ai_catalog_items"
WHERE "ai_catalog_items"."deleted_at" IS NULL
  AND "ai_catalog_items"."organization_id" = 1
  AND "ai_catalog_items"."item_type" = 2
  AND (
    "ai_catalog_items"."visibility" = 2
    OR EXISTS (
      SELECT 1
      FROM "project_authorizations"
      WHERE "project_authorizations"."project_id" = "ai_catalog_items"."project_id"
        AND "project_authorizations"."user_id" = 26121709
        AND "project_authorizations"."access_level" >= 10
        AND NOT (
          EXISTS (
            SELECT 1
            FROM "projects"
            INNER JOIN "namespaces"
              ON "namespaces"."id" = "projects"."project_namespace_id"
              AND "namespaces"."type" = 'Project'
            WHERE "projects"."id" = "ai_catalog_items"."project_id"
              AND (namespaces.traversal_ids[1] IN (9970))
          )
        )
    )
  )
  AND "ai_catalog_items"."visibility" != 1
ORDER BY
  "ai_catalog_items"."last_30_day_usage_count" DESC,
  "ai_catalog_items"."id" DESC
LIMIT 21

Related to #612769

Edited by Jaydip Pansuriya

Merge request reports

Loading
Loading