fix: Improved support for group authorization for Album delete & edit#4317
fix: Improved support for group authorization for Album delete & edit#4317
Conversation
📝 WalkthroughWalkthroughPermission checks in AlbumPolicy.php now evaluate both direct user grants and group-inherited permissions. Single-album deletion validation was relaxed to require at least one matching permission. Bulk operations use distinct album ID selection for accurate counting across multiple permission rows. Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Poem
🚥 Pre-merge checks | ✅ 1✅ Passed checks (1 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
app/Policies/AlbumPolicy.php (1)
382-394:⚠️ Potential issue | 🔴 Critical
distinct()->count()does not produceCOUNT(DISTINCT base_album_id)— group-inherited grants will be wrongly denied.The intent (per the PR/AI summary) of adding
->select(APC::BASE_ALBUM_ID)and->distinct()is to count unique albums when a user matches via both a directUSER_IDgrant and aUSER_GROUP_IDgrant. However, Laravel'sBuilder::aggregate()clones the query without thecolumnsproperty and invokes the aggregate with['*'], socompileAggregateproducesSELECT COUNT(*) …— theelseif ($query->distinct && $column !== '*')branch is skipped because$column === '*'. The->select(...)column is dropped anddistinct()has no effect on the aggregate.Concrete impact: if a user has both a direct permission row and a group-inherited permission row for the same album,
count()returns 2 per album, and=== $num_albumsfails → access incorrectly denied. This is exactly the scenario the PR is meant to enable.Fix by passing the column to
count()(or todistinct()), which hits the correct branch incompileAggregate:🐛 Proposed fix for canEditById (lines 382–394)
if ( AccessPermission::query() - ->select(APC::BASE_ALBUM_ID) ->whereIn(APC::BASE_ALBUM_ID, $album_ids) ->where(fn ($query) => $query->where(APC::USER_ID, '=', $user->id) ->orWhereIn(APC::USER_GROUP_ID, $user->user_groups->pluck('id')) ) ->where(APC::GRANTS_EDIT, '=', true) - ->distinct() - ->count() === $num_albums + ->distinct() + ->count(APC::BASE_ALBUM_ID) === $num_albums ) { return true; }The same issue applies to
canDeleteByIdat lines 437–449 — please mirror the fix there (usingAPC::GRANTS_DELETE/count(APC::BASE_ALBUM_ID)).Run this script to confirm the Laravel version in use and the exact
aggregate/compileAggregatebehavior in this vendor tree:#!/bin/bash # Confirm Laravel framework version cat composer.lock 2>/dev/null | python3 -c " import json, sys d = json.load(sys.stdin) for p in d.get('packages', []): if p['name'] in ('laravel/framework', 'illuminate/database'): print(p['name'], p['version']) " # Inspect Query\Builder::aggregate to confirm it clones-without columns fd -t f 'Builder.php' vendor/laravel/framework/src/Illuminate/Database/Query 2>/dev/null \ | xargs -I{} awk '/function aggregate\(/,/^ \}/' {} # Inspect compileAggregate in the base Grammar fd -t f 'Grammar.php' vendor/laravel/framework/src/Illuminate/Database/Query/Grammars 2>/dev/null \ | xargs -I{} awk '/function compileAggregate\(/,/^ \}/' {}
🧹 Nitpick comments (1)
app/Policies/AlbumPolicy.php (1)
285-295: Preferexists()overcount() >= 1for an existence check.Behaviorally the change from
=== 1to>= 1is correct (theUSER_ID/USER_GROUP_IDOR can yield multiple rows per album). But since you only care whether any matching row exists,->exists()is clearer and lets the DB short-circuit (typically aSELECT EXISTS(...)/LIMIT 1instead of counting all matches).♻️ Proposed refactor
if ( AccessPermission::query() ->where(APC::BASE_ALBUM_ID, '=', $abstract_album->parent_id) ->where(fn ($query) => $query->where(APC::USER_ID, '=', $user->id) ->orWhereIn(APC::USER_GROUP_ID, $user->user_groups->pluck('id')) ) ->where(APC::GRANTS_DELETE, '=', true) - ->count() >= 1 + ->exists() ) { return true; }
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 191dfcd5-0e8e-48a2-a52f-8af30a34df0c
📒 Files selected for processing (1)
app/Policies/AlbumPolicy.php
Codecov Report✅ All modified and coverable lines are covered by tests. 🚀 New features to boost your workflow:
|
Summary by CodeRabbit