The given role or permission should use guard `sanctum` instead of `api` when using teams mode in Laravel tests.
sts-ryan-holton opened this issue · 6 comments
Describe the bug
I'm using the teams feature of this package. In my application a "team" is a Company
and users are assigned to companies. There is one global role called super_admin
. My default auth guard is api since my Laravel 10 project is being used as an api to server a Nuxt front-end - all of this is working fine right now and I'd like to implement some tests.
When trying to create a reusable funtion to seed my roles and permissions, an error is thrown:
The given role or permission should use guard
sanctum
instead ofapi
.
Versions
- spatie/laravel-permission package version: 5.10.2
- illuminate/framework package: 10.19.0
PHP version: 8.1
Database version: MariaDB
Additional context
My TestCase
file is what all my tests extend, here's it's contents along with my createRoleWithPermissions
public function:
<?php
namespace Tests;
use Illuminate\Foundation\Testing\TestCase as BaseTestCase;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Spatie\Permission\Models\Permission;
use Spatie\Permission\Models\Role;
use Illuminate\Support\Facades\Log;
use Spatie\Permission\PermissionRegistrar;
use Database\Seeders\Production\Roles\GlobalRoleTableSeeder;
use Laravel\Sanctum\Sanctum;
use App\Models\Company;
use App\Models\User;
abstract class TestCase extends BaseTestCase
{
use CreatesApplication, RefreshDatabase;
/**
* Define a global user for each test
*/
public Company $company;
/**
* Define a company for each test that user can be attributed to
*/
public User $user;
/**
* Runs before each test
*/
protected function setUp(): void
{
parent::setUp();
// now re-register all the roles and permissions (clears cache and reloads relations)
$this->app->make(PermissionRegistrar::class)->registerPermissions();
// globally set common headers for HTTP tests
$this->withHeaders([
'Accept' => 'application/json'
]);
// create global role
$this->seed(GlobalRoleTableSeeder::class);
// create user each time
$this->user = User::factory()->create();
// create the company each time
$this->company = Company::factory()->create([
'user_id' => $this->user->id
]);
// assign user to company
$sessionCompanyId = getPermissionsTeamId();
setPermissionsTeamId($this->company->id);
$this->user->assignRole('super_admin');
setPermissionsTeamId($sessionCompanyId);
}
/**
* Login as a user
* docs: https://laravel.com/docs/10.x/sanctum#testing
*/
public function loginAs(User $user = null): void
{
Sanctum::actingAs(
$user,
['*']
);
}
/**
* Create roles with permissions
* inspiration: https://github.com/drbyte/spatie-permissions-demo/blob/master/tests/Feature/PostsTest.php#L36
*/
public function createRoleWithPermissions(int $companyId, array $roles): void
{
foreach ($roles as $key => $permissions) {
$role = Role::query();
$role = $role->where('name', $key);
if ($key != 'super_admin') {
$role = $role->where('company_id', $companyId);
}
$role = $role->first();
foreach ($permissions as $permission) {
$discoveredPermission = Permission::where('name', $permission)->first();
if ($discoveredPermission) {
$discoveredPermission->assignRole($role);
continue;
}
$permissionCreated = Permission::create([
'name' => $permission,
'guard_name' => 'sanctum',
]);
$permissionCreated->assignRole($role);
}
}
}
}
Note that the $this->seed(GlobalRoleTableSeeder::class);
simply seeds my global role that associated everywhere:
if (! Role::where('name', 'super_admin')->whereNull('company_id')->first()) {
Role::create([
'name' => 'super_admin',
'company_id' => null,
'guard_name' => config('auth.defaults.guard'),
]);
}
Then, in a simple test file...
<?php
namespace Tests\Feature\System\Company\Affiliates;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Foundation\Testing\WithFaker;
use Illuminate\Foundation\Testing\TestResponse;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\DB;
use Tests\TestCase;
use Spatie\Permission\Models\Permission;
use Spatie\Permission\Models\Role;
class AffiliateTest extends TestCase
{
/**
* Ensure that affiliates index returns successful response
*/
public function test_affiliates_index_returns_successful_response(): void
{
$companyId = $this->company->id;
setPermissionsTeamId(1);
$this->createRoleWithPermissions($companyId, [
'super_admin' => [
'affiliate_index'
]
]);
$this->loginAs($this->user);
$response = $this->get("/api/company/$companyId/affiliates");
$this->assertThat(
$response->getStatusCode(),
$this->logicalOr(
$this->equalTo(200),
$this->equalTo(404)
)
);
}
}
It's here where the errors occur. I need to apply the super admin's permission of affiliate_index
to the role so that it passes my authorize
method.
Why am I getting this error? I've tried forcing sanctum
with no luck.
Environment (please complete the following information, because it helps us investigate better):
- OS: Mac OS
- Version Latest
Make an example app: https://spatie.be/docs/laravel-permission/v5/basic-usage/example-app
Are you trying to pass authorization only with the super_admin
role?
In GlobalRoleTableSeeder
is 'guard_name' => config('auth.defaults.guard'),
where it's picking up api
?
In test_affiliates_index_returns_successful_response
should setPermissionsTeamId(1)
be setPermissionsTeamId($companyId)
?
I have a similar issue, its only actually happening when I run tests though. Also it's only when the gate / model policy returns false. I can actually check for the roles and if it's successful no issue. If it's false the issue is raised.
I think it's something to do with the type of response it wants to do.
For example if you run:
$this->authorize('view',$model); // if the return is false the test will fail.
Gate::authorize('view',$model); // this works the same
However
Gate::check('view',$model); // this will return a true or false.
It seems like when we have an AuthorizationException being thrown that is when the issue comes up. It's also pointing to my seeder though, which is weird.
If you already have a loaded user, after change team_id, you always must reload relations, look at the tests
laravel-permission/tests/TeamHasPermissionsTest.php
Lines 53 to 62 in 2cc5362
And i think
"/api/company/$companyId/affiliates"
is using api
auth, check thatThat part is fine and it works. When running tests though, it runs into the following issue and throws an error:
if (! $this->getGuardNames()->contains($roleOrPermission->guard_name)) { throw GuardDoesNotMatch::create($roleOrPermission->guard_name, $this->getGuardNames()); }
The permissions all have the 'web' guard. But the user we are logged in with has a 'sanctum' guard, this happens whether I use any of the following:
$this->actingAs($user2);
Sanctum::actingAs(
$user2,
['*'],
);
Sanctum::actingAs(
$user2,
['*'],
'web'
);
If you go to the same controller function via via web.php instead of api.php then it works fine. When going through api.php, we hit the "auth:sanctum" middleware and that causes an issue somewhere.