Skip to content
Merged
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
6 changes: 6 additions & 0 deletions config/permission.php
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,12 @@

'display_permission_in_exception' => false,

/*
* By default wildcard permission lookups are disabled.
*/

'enable_wildcard_permission' => false,

'cache' => [

/*
Expand Down
2 changes: 1 addition & 1 deletion docs/advanced-usage/extending.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ If you are creating your own User models and wish Authorization features to be a


## Extending Role and Permission Models
If you are extending or replacing the role/permission models, you will need to specify your new models in this package's `config/permissions.php` file.
If you are extending or replacing the role/permission models, you will need to specify your new models in this package's `config/permission.php` file.

First be sure that you've published the configuration file (see the Installation instructions), and edit it to update the `models.role` and `models.permission` values to point to your new models.

Expand Down
64 changes: 64 additions & 0 deletions docs/basic-usage/wildcard-permissions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
---
title: Wildcard permissions
weight: 3
---

Wildcard permissions can be enabled in the permission config file:

```php
// config/permission.php
'enable_wildcard_permission' => true,
```

When enabled, wildcard permissions offers you a flexible representation for a variety of permission schemes. The idea
behind wildcard permissions is inspired by the default permission implementation of
[Apache Shiro](https://shiro.apache.org/permissions.html).

A wildcard permission string is made of one or more parts separated by dots (.).

```php
$permission = 'posts.create.1';
```

The meaning of each part of the string depends on the application layer.

> You can use as many parts as you like. So you are not limited to the three-tiered structure, even though
this is the common use-case, representing {resource}.{action}.{target}.

### Using Wildcards

Each part can also contain wildcards (*). So let's say we assign the following permission to a user:

```php
$user->givePermissionTo('posts.*');
// is the same as
$user->givePermissionTo('posts');
```

Everyone who is assigned to this permission will be allowed every action on posts. It is not necessary to use a
wildcard on the last part of the string. This is automatically assumed.

```php
// will be true
$user->can('posts.create');
$user->can('posts.edit');
$user->can('posts.delete');
```

### Subparts

Besides the use of parts and wildcards, subparts can also be used. Subparts are divided with commas (,). This is a
powerful feature that lets you create complex permission schemes.

```php
// user can only do the actions create, update and view on both resources posts and users
$user->givePermissionTo('posts,users.create,update,view');

// user can do the actions create, update, view on any available resource
$user->givePermissionTo('*.create,update,view');

// user can do any action on posts with ids 1, 4 and 6
$user->givePermissionTo('posts.*.1,4,6');
```

> As said before, the meaning of each part is determined by the application layer! So, you are free to use each part as you like. And you can use as many parts and subparts as you want.
13 changes: 13 additions & 0 deletions src/Exceptions/WildcardPermissionInvalidArgument.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php

namespace Spatie\Permission\Exceptions;

use InvalidArgumentException;

class WildcardPermissionInvalidArgument extends InvalidArgumentException
{
public static function create()
{
return new static('Wildcard permission must be string, permission id or permission instance');
}
}
13 changes: 13 additions & 0 deletions src/Exceptions/WildcardPermissionNotProperlyFormatted.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php

namespace Spatie\Permission\Exceptions;

use InvalidArgumentException;

class WildcardPermissionNotProperlyFormatted extends InvalidArgumentException
{
public static function create(string $permission)
{
return new static("Wildcard permission `{$permission}` is not properly formatted.");
}
}
4 changes: 4 additions & 0 deletions src/Models/Role.php
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,10 @@ public static function findOrCreate(string $name, $guardName = null): RoleContra
*/
public function hasPermissionTo($permission): bool
{
if (config('permission.enable_wildcard_permission', false)) {
return $this->hasWildcardPermission($permission, $this->getDefaultGuardName());
}

$permissionClass = $this->getPermissionClass();

if (is_string($permission)) {
Expand Down
45 changes: 45 additions & 0 deletions src/Traits/HasPermissions.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,13 @@
use Spatie\Permission\Guard;
use Illuminate\Support\Collection;
use Illuminate\Database\Eloquent\Builder;
use Spatie\Permission\WildcardPermission;
use Spatie\Permission\PermissionRegistrar;
use Spatie\Permission\Contracts\Permission;
use Spatie\Permission\Exceptions\GuardDoesNotMatch;
use Illuminate\Database\Eloquent\Relations\MorphToMany;
use Spatie\Permission\Exceptions\PermissionDoesNotExist;
use Spatie\Permission\Exceptions\WildcardPermissionInvalidArgument;

trait HasPermissions
{
Expand Down Expand Up @@ -110,6 +112,10 @@ protected function convertToPermissionModels($permissions): array
*/
public function hasPermissionTo($permission, $guardName = null): bool
{
if (config('permission.enable_wildcard_permission', false)) {
return $this->hasWildcardPermission($permission, $guardName);
}

$permissionClass = $this->getPermissionClass();

if (is_string($permission)) {
Expand All @@ -133,6 +139,45 @@ public function hasPermissionTo($permission, $guardName = null): bool
return $this->hasDirectPermission($permission) || $this->hasPermissionViaRole($permission);
}

/**
* Validates a wildcard permission against all permissions of a user.
*
* @param string|int|\Spatie\Permission\Contracts\Permission $permission
* @param string|null $guardName
*
* @return bool
*/
protected function hasWildcardPermission($permission, $guardName = null): bool
{
$guardName = $guardName ?? $this->getDefaultGuardName();

if (is_int($permission)) {
$permission = $this->getPermissionClass()->findById($permission, $guardName);
}

if ($permission instanceof Permission) {
$permission = $permission->name;
}

if (! is_string($permission)) {
throw WildcardPermissionInvalidArgument::create();
}

foreach ($this->getAllPermissions() as $userPermission) {
if ($guardName !== $userPermission->guard_name) {
continue;
}

$userPermission = new WildcardPermission($userPermission->name);

if ($userPermission->implies($permission)) {
return true;
}
}

return false;
}

/**
* @deprecated since 2.35.0
* @alias of hasPermissionTo()
Expand Down
124 changes: 124 additions & 0 deletions src/WildcardPermission.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
<?php

namespace Spatie\Permission;

use Illuminate\Support\Collection;
use Spatie\Permission\Exceptions\WildcardPermissionNotProperlyFormatted;

class WildcardPermission
{
/** @var string */
const WILDCARD_TOKEN = '*';

/** @var string */
const PART_DELIMITER = '.';

/** @var string */
const SUBPART_DELIMITER = ',';

/** @var string */
protected $permission;

/** @var Collection */
protected $parts;

/**
* @param string $permission
*/
public function __construct(string $permission)
{
$this->permission = $permission;
$this->parts = collect();

$this->setParts();
}

/**
* @param string|WildcardPermission $permission
*
* @return bool
*/
public function implies($permission): bool
{
if (is_string($permission)) {
$permission = new self($permission);
}

$otherParts = $permission->getParts();

$i = 0;
foreach ($otherParts as $otherPart) {
if ($this->getParts()->count() - 1 < $i) {
return true;
}

if (! $this->parts->get($i)->contains(self::WILDCARD_TOKEN)
&& ! $this->containsAll($this->parts->get($i), $otherPart)) {
return false;
}

$i++;
}

for ($i; $i < $this->parts->count(); $i++) {
if (! $this->parts->get($i)->contains(self::WILDCARD_TOKEN)) {
return false;
}
}

return true;
}

/**
* @param Collection $part
* @param Collection $otherPart
*
* @return bool
*/
protected function containsAll(Collection $part, Collection $otherPart): bool
{
foreach ($otherPart->toArray() as $item) {
if (! $part->contains($item)) {
return false;
}
}

return true;
}

/**
* @return Collection
*/
public function getParts(): Collection
{
return $this->parts;
}

/**
* Sets the different parts and subparts from permission string.
*
* @return void
*/
protected function setParts(): void
{
if (empty($this->permission) || $this->permission == null) {
throw WildcardPermissionNotProperlyFormatted::create($this->permission);
}

$parts = collect(explode(self::PART_DELIMITER, $this->permission));

$parts->each(function ($item, $key) {
$subParts = collect(explode(self::SUBPART_DELIMITER, $item));

if ($subParts->isEmpty() || $subParts->contains('')) {
throw WildcardPermissionNotProperlyFormatted::create($this->permission);
}

$this->parts->add($subParts);
});

if ($this->parts->isEmpty()) {
throw WildcardPermissionNotProperlyFormatted::create($this->permission);
}
}
}
2 changes: 1 addition & 1 deletion tests/HasPermissionsTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -242,7 +242,7 @@ public function it_throws_an_exception_when_the_permission_does_not_exist_for_th
{
$this->expectException(PermissionDoesNotExist::class);

$this->testUser->hasPermissionTo('admin-permission');
$this->testUser->hasPermissionTo('does-not-exist');
}

/** @test */
Expand Down
Loading