Разрешения пользователей Spatie - принадлежат многим компаниям, отзывы о решениях - PullRequest
0 голосов
/ 07 ноября 2019

У меня есть ситуация, когда пользователь может принадлежать ко многим командам / компаниям, и в рамках этой команды / компании они могут иметь разные роли и разрешения в зависимости от того, в какую учетную запись они вошли. Я предложил следующее решение и хотел бы получить обратную связь!

Примечание. В настоящее время я использую только таблицу model_has_roles с разрешениями Spatie и всегда использую $user->can('Permission') для проверки разрешений.

  1. Модель нашей компании имеет следующие отношения и метод
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);
    }
}

Теперь мы можем сделать что-то вроде этого:

  1. Создать роль и разрешение
/** @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();
}

Так что все это работает, мои основные проблемы заключаются в следующем:

  1. Мы перезаписываем метод can. Могут быть другие методы авторизации / функции шлюза, которые не перехвачены.

  2. У нас есть 2 набора model_permissions. Пользователь Auth и пользователь компании. Я думаю, что мне нужно встроить некоторые проверки, чтобы гарантировать, что только правильные типы пользователей могут быть назначены на роли. На этом этапе всем пользователям-администраторам будут предоставлены разрешения, назначенные их авторизованному пользователю, в то время как все пользователи, владеющие компанией, должны иметь разрешения только для модели пользователя компании

...