Fix getCategoryExclusionById() excluding every category for role-only users

RolesAndPermissionsSeeder grants every 'view *' permission via
Role::givePermissionTo() only -- it never grants permissions directly to a
user with User::givePermissionTo(). That is true for every seeded role,
including Admin.

User::getCategoryExclusionById() computed the allowed permission set as:

    $userAllowed = $user->getDirectPermissions()->pluck('name')->toArray();
    $roleAllowed = $user->getAllPermissions()->pluck('name')->toArray();
    $allowed = array_intersect($roleAllowed, $userAllowed);

getAllPermissions() already includes permissions granted via the user's
role(s), so intersecting it with getDirectPermissions() (permissions
assigned directly to the user, bypassing roles) means $allowed is empty
for any user whose permissions come only from their role. Since every
seeded role works this way, this silently excluded every category root
for every user on a fresh install, and any subsequent Newznab/Torznab API
search or browse request returned zero results with no visible error.

Fix: use getAllPermissions() directly, since it already reflects both
role-granted and directly-granted permissions.

Added a regression test (test_role_only_permissions_are_not_excluded)
that mirrors the real seeder setup -- role-only permissions, nothing
granted directly to the user -- to make sure this doesn't regress.
This commit is contained in:
joemeyer76
2026-07-03 18:18:46 -04:00
parent 09ad515969
commit e62724f2cd
2 changed files with 32 additions and 3 deletions
+9 -3
View File
@@ -1647,9 +1647,15 @@ final class User extends Authenticatable implements CanResetPasswordContract, Ha
{
$user = static::findOrFail($userId);
$userAllowed = $user->getDirectPermissions()->pluck('name')->toArray();
$roleAllowed = $user->getAllPermissions()->pluck('name')->toArray();
$allowed = array_intersect($roleAllowed, $userAllowed);
// getAllPermissions() already merges permissions granted directly to the
// user with permissions granted via their role(s). Intersecting it with
// getDirectPermissions() (as this used to do) meant that any user whose
// permissions come only from their role -- which is how every seeded
// role in RolesAndPermissionsSeeder grants them, including Admin -- ended
// up with an empty $allowed array, and therefore every category root
// excluded. That silently zeroed out browse/API results for all users
// on a fresh install.
$allowed = $user->getAllPermissions()->pluck('name')->toArray();
$categoryPermissions = [
'view console' => 1000,
@@ -266,6 +266,29 @@ final class UserExcludedCategoryTest extends TestCase
$this->assertContains(2070, $exclusions);
}
public function test_role_only_permissions_are_not_excluded(): void
{
// Regression test: RolesAndPermissionsSeeder grants every view permission
// via the role only (Role::givePermissionTo), never directly to the user
// (User::givePermissionTo). getCategoryExclusionById() must honor
// role-granted permissions, not just permissions assigned directly to
// the user, or every category ends up excluded on a fresh install.
$roleOnlyUser = $this->createTestUser($this->userRole->id);
$roleOnlyUser->assignRole($this->userRole);
// Deliberately do NOT call $roleOnlyUser->givePermissionTo(...) here.
$exclusions = User::getCategoryExclusionById($roleOnlyUser->id);
// The role has every 'view *' permission except 'view other', so only
// the 'Other' root category (id 1) should be excluded -- nothing from
// the seeded Movies (2000) or TV (5000) root categories.
$this->assertNotContains(2030, $exclusions);
$this->assertNotContains(2040, $exclusions);
$this->assertNotContains(2070, $exclusions);
$this->assertNotContains(5030, $exclusions);
$this->assertNotContains(5040, $exclusions);
}
public function test_clearing_exclusions_works(): void
{
// First add exclusions