Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 21 additions & 4 deletions src/Models/Permission.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,19 @@ public function __construct(array $attributes = [])
$this->table = config('permission.table_names.permissions') ?: parent::getTable();
}

/**
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please remove all these validation methods from all files in this PR.

* Boot the model and add validation on model events.
*/
protected static function boot()
{
parent::boot();

static::saving(function ($model) {
$model->validateNameAttribute();
$model->validateGuardNameAttribute();
});
}

/**
* @return PermissionContract|Permission
*
Expand All @@ -57,11 +70,13 @@ public static function create(array $attributes = [])
*/
public function roles(): BelongsToMany
{
$registrar = app(PermissionRegistrar::class);

return $this->belongsToMany(
config('permission.models.role'),
config('permission.table_names.role_has_permissions'),
app(PermissionRegistrar::class)->pivotPermission,
app(PermissionRegistrar::class)->pivotRole
$registrar->pivotPermission,
$registrar->pivotRole
);
}

Expand All @@ -70,11 +85,13 @@ public function roles(): BelongsToMany
*/
public function users(): BelongsToMany
{
$registrar = app(PermissionRegistrar::class);

return $this->morphedByMany(
getModelForGuard($this->attributes['guard_name'] ?? config('auth.defaults.guard')),
getModelForGuard($this->guard_name ?? Guard::getDefaultName(static::class)),
'model',
config('permission.table_names.model_has_permissions'),
app(PermissionRegistrar::class)->pivotPermission,
$registrar->pivotPermission,
config('permission.column_names.model_morph_key')
);
}
Expand Down
46 changes: 37 additions & 9 deletions src/Models/Role.php
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,19 @@ public function __construct(array $attributes = [])
$this->table = config('permission.table_names.roles') ?: parent::getTable();
}

/**
* Boot the model and add validation on model events.
*/
protected static function boot()
{
parent::boot();

static::saving(function ($model) {
$model->validateNameAttribute();
$model->validateGuardNameAttribute();
});
}

/**
* @return RoleContract|Role
*
Expand All @@ -44,16 +57,19 @@ public static function create(array $attributes = [])
{
$attributes['guard_name'] ??= Guard::getDefaultName(static::class);

$registrar = app(PermissionRegistrar::class);
$params = ['name' => $attributes['name'], 'guard_name' => $attributes['guard_name']];
if (app(PermissionRegistrar::class)->teams) {
$teamsKey = app(PermissionRegistrar::class)->teamsKey;

if ($registrar->teams) {
$teamsKey = $registrar->teamsKey;

if (array_key_exists($teamsKey, $attributes)) {
$params[$teamsKey] = $attributes[$teamsKey];
} else {
$attributes[$teamsKey] = getPermissionsTeamId();
}
}

if (static::findByParam($params)) {
throw RoleAlreadyExists::create($attributes['name'], $attributes['guard_name']);
}
Expand All @@ -66,11 +82,13 @@ public static function create(array $attributes = [])
*/
public function permissions(): BelongsToMany
{
$registrar = app(PermissionRegistrar::class);

return $this->belongsToMany(
config('permission.models.permission'),
config('permission.table_names.role_has_permissions'),
app(PermissionRegistrar::class)->pivotRole,
app(PermissionRegistrar::class)->pivotPermission
$registrar->pivotRole,
$registrar->pivotPermission
);
}

Expand All @@ -79,11 +97,13 @@ public function permissions(): BelongsToMany
*/
public function users(): BelongsToMany
{
$registrar = app(PermissionRegistrar::class);

return $this->morphedByMany(
getModelForGuard($this->attributes['guard_name'] ?? config('auth.defaults.guard')),
getModelForGuard($this->guard_name ?? Guard::getDefaultName(static::class)),
'model',
config('permission.table_names.model_has_roles'),
app(PermissionRegistrar::class)->pivotRole,
$registrar->pivotRole,
config('permission.column_names.model_morph_key')
);
}
Expand Down Expand Up @@ -138,7 +158,14 @@ public static function findOrCreate(string $name, ?string $guardName = null): Ro
$role = static::findByParam(['name' => $name, 'guard_name' => $guardName]);

if (! $role) {
return static::query()->create(['name' => $name, 'guard_name' => $guardName] + (app(PermissionRegistrar::class)->teams ? [app(PermissionRegistrar::class)->teamsKey => getPermissionsTeamId()] : []));
$registrar = app(PermissionRegistrar::class);
$attributes = ['name' => $name, 'guard_name' => $guardName];

if ($registrar->teams) {
$attributes[$registrar->teamsKey] = getPermissionsTeamId();
}

return static::query()->create($attributes);
}

return $role;
Expand All @@ -152,9 +179,10 @@ public static function findOrCreate(string $name, ?string $guardName = null): Ro
protected static function findByParam(array $params = []): ?RoleContract
{
$query = static::query();
$registrar = app(PermissionRegistrar::class);

if (app(PermissionRegistrar::class)->teams) {
$teamsKey = app(PermissionRegistrar::class)->teamsKey;
if ($registrar->teams) {
$teamsKey = $registrar->teamsKey;

$query->where(fn ($q) => $q->whereNull($teamsKey)
->orWhere($teamsKey, $params[$teamsKey] ?? getPermissionsTeamId())
Expand Down
83 changes: 83 additions & 0 deletions src/Traits/HasPermissions.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Support\Arr;
use Illuminate\Support\Collection;
use InvalidArgumentException;
use Spatie\Permission\Contracts\Permission;
use Spatie\Permission\Contracts\Role;
use Spatie\Permission\Contracts\Wildcard;
Expand Down Expand Up @@ -590,4 +591,86 @@ public function hasAnyDirectPermission(...$permissions): bool

return false;
}

/**
* Validate the name attribute.
*
* @throws InvalidArgumentException
*/
protected function validateNameAttribute(): void
{
if (! isset($this->attributes['name'])) {
return;
}

$name = $this->attributes['name'];

// Check if name is a string
if (! is_string($name)) {
throw new InvalidArgumentException($this->getModelType().' name must be a string.');
}

// Trim and check for empty name
$name = trim($name);
if (empty($name)) {
throw new InvalidArgumentException($this->getModelType().' name cannot be empty.');
}

// Check name length (prevent excessively long names)
if (strlen($name) > 255) {
throw new InvalidArgumentException($this->getModelType().' name cannot exceed 255 characters.');
}

// Sanitize name - remove control characters and null bytes
$sanitized = preg_replace('/[\x00-\x1F\x7F]/u', '', $name);

if ($sanitized !== $name) {
throw new InvalidArgumentException($this->getModelType().' name contains invalid characters.');
}

// Store the trimmed name
$this->attributes['name'] = $name;
}

/**
* Validate the guard_name attribute.
*
* @throws InvalidArgumentException
*/
protected function validateGuardNameAttribute(): void
{
if (! isset($this->attributes['guard_name'])) {
return;
}

$guardName = $this->attributes['guard_name'];

if (! is_string($guardName)) {
throw new InvalidArgumentException('Guard name must be a string.');
}

$guardName = trim($guardName);
if (empty($guardName)) {
throw new InvalidArgumentException('Guard name cannot be empty.');
}

if (strlen($guardName) > 255) {
throw new InvalidArgumentException('Guard name cannot exceed 255 characters.');
}

// Validate guard name format (alphanumeric, dash, underscore only)
if (! preg_match('/^[a-zA-Z0-9_-]+$/', $guardName)) {
throw new InvalidArgumentException('Guard name must contain only alphanumeric characters, dashes, and underscores.');
}

$this->attributes['guard_name'] = $guardName;
}

/**
* Get the model type name for error messages.
*/
protected function getModelType(): string
{
return class_basename($this);
}
}