У меня есть ситуация, когда пользователь может принадлежать ко многим командам / компаниям, и в рамках этой команды / компании они могут иметь разные роли и разрешения в зависимости от того, в какую учетную запись они вошли. Я предложил следующее решение и хотел бы получить обратную связь!
Примечание. В настоящее время я использую только таблицу model_has_roles
с разрешениями Spatie и всегда использую $user->can('Permission')
для проверки разрешений.
- Модель нашей компании имеет следующие отношения и метод
class Company extends Model
{
public function owner(): HasOne
{
return $this->hasOne(User::class, 'id', 'user_id');
}
public function users(): BelongsToMany
{
return $this->belongsToMany(
User::class, 'company_users', 'company_id', 'user_id'
)->using(CompanyUser::class);
}
public function addTeamMember(User $user)
{
$this->users()->detach($user);
$this->users()->attach($user);
}
}
Мы модифицируем модель пивота, чтобы иметь черту Spatie
HasRoles
. Это позволяет нам назначать роль для
CompanyUser
в отличие от Auth User. Вам также необходимо указать квоты защиты по умолчанию или разрешения Spatie.
class CompanyUser extends Pivot
{
use HasRoles;
protected $guard_name = 'web';
}
В пользовательской модели я создал черту
HasCompanies
. Это обеспечивает отношения и предоставляет метод для назначения ролей новому пользователю компании. Кроме того, он перезаписывает метод gate
can()
.
Пользователь может принадлежать нескольким компаниям, но одновременно может иметь только одну активную компанию (то есть ту, которую они просматривают). Мы определяем это с помощью столбца current_company_id
.
Также важно убедиться, что ID сводной таблицы переброшен (что не будет стандартным), поскольку теперь это то, что мы используем в Spatie model_has_roles table
.
trait HasCompanies
{
public function companies(): HasMany
{
return $this->hasMany(Company::class);
}
public function currentCompany(): HasOne
{
return $this->hasOne(Company::class, 'id', 'current_company_id');
}
public function teams(): BelongsToMany
{
return $this->belongsToMany(
Company::class, 'company_users', 'user_id', 'company_id'
)->using(CompanyUser::class)->withPivot('id');
}
public function switchCompanies(Company $company): void
{
$this->current_company_id = $company->id;
$this->save();
}
private function companyWithPivot(Company $company)
{
return $this->teams()->where('companies.id', $company->id)->first();
}
public function assignRolesForCompany(Company $company, ...$roles)
{
if($company = $this->companyWithPivot($company)){
/** @var CompanyUser $companyUser */
$companyUser = $company->pivot;
$companyUser->assignRole($roles);
return;
}
throw new Exception('Roles could not be assigned to company user');
}
public function hasRoleForCurrentCompany(string $roles, Company $company = null, string $guard = null): bool
{
if(! $company){
if(! $company = $this->currentCompany){
throw new Exception('Cannot check role for current company because it has not been set');
}
}
if($company = $this->companyWithPivot($company)){
/** @var CompanyUser $companyUser */
$companyUser = $company->pivot;
return $companyUser->hasRole($roles, $guard);
}
return false;
}
public function can($ability, $arguments = []): bool
{
if(isset($this->current_company_id)){
/** @var CompanyUser $companyUser */
$companyUser = $this->teams()->where('companies.id', $this->current_company_id)->first()->pivot;
if($companyUser->hasPermissionTo($ability)){
return true;
}
// Still run through the gate as this will check for gate bypass
return app(Gate::class)->forUser($this)->check('N/A', []);
}
return app(Gate::class)->forUser($this)->check($ability, $arguments);
}
}
Теперь мы можем сделать что-то вроде этого:
- Создать роль и разрешение
/** @var Role $ownerRoll */
$ownerRoll = Role::create(['name' => 'Owner']);
/** @var Permission $permission */
$permission = Permission::create([
'name' => 'Create Company',
'guard_name' => 'web',
]);
$ownerRoll->givePermissionTo($permission);
Создайте новую компанию с владельцем-владельцем, а затем переключите эту компанию на активную компанию этого владельца.
public function store(CompanyStoreRequest $request)
{
DB::transaction(function () use($request) {
/** @var User $owner */
$owner = User::findOrFail($request->user_id);
/** @var Company $company */
$company = $owner->companies()->create($request->validated());
$company->addTeamMember($owner);
$owner->assignRolesForCompany($company, 'Owner');
$owner->switchCompanies($company);
});
return redirect()->back();
}
Так что все это работает, мои основные проблемы заключаются в следующем:
Мы перезаписываем метод can. Могут быть другие методы авторизации / функции шлюза, которые не перехвачены.
У нас есть 2 набора model_permissions. Пользователь Auth и пользователь компании. Я думаю, что мне нужно встроить некоторые проверки, чтобы гарантировать, что только правильные типы пользователей могут быть назначены на роли. На этом этапе всем пользователям-администраторам будут предоставлены разрешения, назначенные их авторизованному пользователю, в то время как все пользователи, владеющие компанией, должны иметь разрешения только для модели пользователя компании