Scope remaining abuse report reads and writes to one organization
What does this MR do and why?
Follow-ups found while scoping AbuseReportsFinder. The first one is a bug, not a cleanup.
Cross-organization write (the important one)
Admin::AbuseReports::ModerateUserService#close_similar_open_reports calls:
abuse_report.similar_open_reports_for_user.update_all(status: 'closed')similar_open_reports_for_user went through user.abuse_reports. A report's organization follows
its reporter, not the reported user, so the same user can be reported in several organizations —
which means an admin moderating a user silently closed that user's open reports in every other
organization as well.
past_closed_reports_for_user had the matching read leak: Admin::AbuseReportDetailsEntity exposes
both methods, so the admin details page showed another organization's report metadata.
Both are now scoped to the report's own organization_id, which is available on the record and
needs no request context.
Reverting this fix fails the new spec in five separate moderation paths — ban, block, delete, trust and close-only — so the exposure was not limited to one action.
Sidebar pill count
Sidebars::Admin::Menus::AbuseReportsMenu#pill_count counted AbuseReport.open across every
organization, so it disagreed with the list it links to once that list became organization-scoped.
lib/sidebars is not excluded from Gitlab/AvoidCurrentOrganization, so the organization is
carried on Sidebars::Context instead of read from Current. The Explore panel already passes
current_organization this way. Both places that build an admin panel context are updated —
SidebarsHelper#super_sidebar_nav_panel and the gitlab:nav:dump_structure Rake task — because
Sidebars::Context only defines readers for the keywords it is given, so missing one would raise
NoMethodError.
GraphQL abuseReport query
Resolvers::AbuseReportResolver looked reports up with an unscoped find_by_id, returning a report
from any organization. app/graphql is excluded from the cop, so Current.organization is read
directly here rather than threaded through the resolver.
Database
No migration and no schema changes. Three existing reads gain a WHERE organization_id = ?
predicate, and one update_all is narrowed by the same predicate. abuse_reports is
table_size: small and organization_id is already indexed.
1. AbuseReport#past_closed_reports_for_user
SELECT abuse_reports.*
FROM abuse_reports
WHERE abuse_reports.user_id = :user_id
AND abuse_reports.organization_id = :organization_id
AND abuse_reports.status = 2
AND abuse_reports.id != :report_id;2. AbuseReport#similar_open_reports_for_user
SELECT abuse_reports.*
FROM abuse_reports
WHERE abuse_reports.user_id = :user_id
AND abuse_reports.organization_id = :organization_id
AND abuse_reports.status = 1
AND abuse_reports.category = :category
AND abuse_reports.id != :report_id;3. update_all in ModerateUserService#close_similar_open_reports
UPDATE abuse_reports
SET status = 2
WHERE abuse_reports.user_id = :user_id
AND abuse_reports.organization_id = :organization_id
AND abuse_reports.status = 1
AND abuse_reports.category = :category
AND abuse_reports.id != :report_id;This is the query the cross-organization fix narrows: before this MR the organization_id predicate
was absent, so moderating a user closed that user's open reports in every other organization.
4. Sidebars::Admin::Menus::AbuseReportsMenu#pill_count
SELECT COUNT(*)
FROM abuse_reports
WHERE abuse_reports.organization_id = :organization_id
AND abuse_reports.status = 1;5. Resolvers::AbuseReportResolver
SELECT abuse_reports.*
FROM abuse_reports
WHERE abuse_reports.organization_id = :organization_id
AND abuse_reports.id = :report_id
LIMIT 1;References
Closes gitlab-com/gl-infra/tenant-scale/cells-infrastructure/team#780 (closed)
Depends on the parent MR for the AbuseReport.in_organization scope.
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.