Skip to content

removeRole ignores team when using custom Pivot class (with teams enabled) #2867

@SlyDave

Description

@SlyDave

Description

If you setup your roles() relationship to use a custom Pivot model and you have teams enabled, then the removeRole() method fails to include the team scope because InteractsWithPivotTable::detach() doesn't run a single query to detach the pivot records, rather it uses the function InteractsWithPivotTable::detachUsingCustomClass() which runs InteractsWithPivotTable::getCurrentlyAttachedPivotsForIds() and then iterates over the result and deletes each model.

For whatever reason the scoping applied when teams is enabled doesn't get applied when InteractsWithPivotTable::detachUsingCustomClass() is used.

Steps To Reproduce

works with teams:

    use HasRoles {
        roles as traitRoles;
    }

    public function roles(): BelongsToMany
    {
        // We're adding timestamps so we know when a role was added and/or updated.
        // And we always want the vessel_id even if we disable laravel-permissions teams support
        return $this->traitRoles()
            ->withPivot('vessel_id')
            ->withTimestamps();
            //->using(UserRole::class);
    }

does not work with teams

    use HasRoles {
        roles as traitRoles;
    }

    public function roles(): BelongsToMany
    {
        // We're adding timestamps so we know when a role was added and/or updated.
        // And we always want the vessel_id even if we disable laravel-permissions teams support
        return $this->traitRoles()
            ->withPivot('vessel_id')
            ->withTimestamps();
            ->using(UserRole::class);
    }

Test case:

<?php

use App\Models\Role;
use App\Models\User;
use App\Models\Vessel;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use Tests\TestCase;

uses(TestCase::class);
uses(DatabaseTransactions::class);

test('Can sync or remove roles without detach on different teams', function () {

    app(\Spatie\Permission\PermissionRegistrar::class)->teams = true;

    $vessel1 = Vessel::factory()->create();
    $vessel2 = Vessel::factory()->create();

    Role::create(['name' => 'testRole', 'guard_name' => 'api']);
    Role::create(['name' => 'testRole2', 'guard_name' => 'api']);
    Role::create(['name' => 'testRole3', 'guard_name' => 'api']);

    $testUser = User::factory()->create();

    // For Vessel 1 attached testRole and testRole2
    setPermissionsTeamId($vessel1);
    $testUser->unsetRelation('roles');
    $testUser->syncRoles('testRole', 'testRole2');
    Log::debug('Vessel 1 Attached Roles', $testUser->roles->pluck('name')->toArray());

    // For Vessel 2 attached testRole and testRole3
    setPermissionsTeamId($vessel2);
    $testUser->unsetRelation('roles');
    $testUser->syncRoles('testRole', 'testRole3');
    Log::debug('Vessel 2 Attached Roles', $testUser->roles->pluck('name')->toArray());

    // Test Vessel 1 only has testRole and testRole2, and that testRole3 didn't get added or testRole2 removed in the syncRoles on Vessel 2
    setPermissionsTeamId($vessel1);
    $testUser->unsetRelation('roles');
    $this->assertEquals(
        collect(['testRole', 'testRole2']),
        $testUser->getRoleNames()->sort()->values()
    );

    // For Vessel 1 remove testRole and confirm the user only has testRole2 attached
    $testUser->removeRole('testRole');
    Log::debug('Vessel 1 Attached Roles', $testUser->roles->pluck('name')->toArray());
    $this->assertEquals(
        collect(['testRole2']),
        $testUser->getRoleNames()->sort()->values()
    );

    // For Vessel 2, confirm it still has testRole and testRole3, i.e. testRole wasn't incorrectly moved when it was removed from Vessel 1
    setPermissionsTeamId($vessel2);
    $testUser->unsetRelation('roles');

    Log::debug('Vessel 2 Attached Roles', $testUser->roles->pluck('name')->toArray());
    $this->assertEquals(
        collect(['testRole', 'testRole3']),
        $testUser->getRoleNames()->sort()->values()
    );
});

Example Application

No response

Version of spatie/laravel-permission package:

6.21.0

Version of laravel/framework package:

12.21.0

PHP version:

8.2

Database engine and version:

No response

OS: Windows/Mac/Linux version:

No response

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions