Skip to content
Open
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
23 changes: 22 additions & 1 deletion src/Auth/Eloquent/User.php
Original file line number Diff line number Diff line change
Expand Up @@ -232,7 +232,15 @@ public function permissions()

public function hasPermission($permission)
{
return $this->permissions()->contains($permission);
$permissions = $this->permissions();

if ($permissions->contains($permission)) {
return true;
}

return $permissions->contains(function ($userPermission) use ($permission) {
return $this->matchesWildcard($userPermission, $permission);
});
}

public function makeSuper()
Expand Down Expand Up @@ -411,4 +419,17 @@ public function passkeys(): Collection
->map(fn ($model) => app(Passkey::class)->setModel($model))
->keyBy(fn ($passkey) => $passkey->id());
}

protected function matchesWildcard(string $wildcardPermission, string $requestedPermission): bool
{
if (! str_contains($wildcardPermission, '*')) {
return false;
}

$pattern = preg_quote($wildcardPermission, '/');
$pattern = str_replace('\*', '.*', $pattern);
$pattern = '/^'.$pattern.'$/';

return (bool) preg_match($pattern, $requestedPermission);
}
}
21 changes: 20 additions & 1 deletion src/Auth/File/Role.php
Original file line number Diff line number Diff line change
Expand Up @@ -98,14 +98,33 @@ public function removePermission($permission)

public function hasPermission(string $permission): bool
{
return $this->permissions->contains($permission);
if ($this->permissions->contains($permission)) {
return true;
}

return $this->permissions->contains(function ($rolePermission) use ($permission) {
return $this->matchesWildcard($rolePermission, $permission);
});
}

public function isSuper(): bool
{
return $this->hasPermission('super');
}

protected function matchesWildcard(string $wildcardPermission, string $requestedPermission): bool
{
if (! str_contains($wildcardPermission, '*')) {
return false;
}

$pattern = preg_quote($wildcardPermission, '/');
$pattern = str_replace('\*', '.*', $pattern);
$pattern = '/^'.$pattern.'$/';

return (bool) preg_match($pattern, $requestedPermission);
}

public function save()
{
// TODO: Move this logic into \Statamic\Auth\Role.php to be consistent with \Statamic\Auth\UserGroup?
Expand Down
23 changes: 22 additions & 1 deletion src/Auth/File/User.php
Original file line number Diff line number Diff line change
Expand Up @@ -292,7 +292,15 @@ public function permissions()

public function hasPermission($permission)
{
return $this->permissions()->contains($permission);
$permissions = $this->permissions();

if ($permissions->contains($permission)) {
return true;
}

return $permissions->contains(function ($userPermission) use ($permission) {
return $this->matchesWildcard($userPermission, $permission);
});
}

public function makeSuper()
Expand Down Expand Up @@ -381,6 +389,19 @@ public function getCurrentDirtyStateAttributes(): array
], $this->data()->toArray());
}

protected function matchesWildcard(string $wildcardPermission, string $requestedPermission): bool
{
if (! str_contains($wildcardPermission, '*')) {
return false;
}

$pattern = preg_quote($wildcardPermission, '/');
$pattern = str_replace('\*', '.*', $pattern);
$pattern = '/^'.$pattern.'$/';

return (bool) preg_match($pattern, $requestedPermission);
}

public function passkeys(): Collection
{
return $this->passkeys;
Expand Down
25 changes: 23 additions & 2 deletions src/Auth/UserGroup.php
Original file line number Diff line number Diff line change
Expand Up @@ -133,9 +133,17 @@ public function hasRole($role): bool

public function hasPermission($permission)
{
return $this->roles->reduce(function ($carry, $role) {
$permissions = $this->roles->reduce(function ($carry, $role) {
return $carry->merge($role->permissions());
}, collect())->contains($permission);
}, collect());

if ($permissions->contains($permission)) {
return true;
}

return $permissions->contains(function ($groupPermission) use ($permission) {
return $this->matchesWildcard($groupPermission, $permission);
});
}

public function isSuper(): bool
Expand Down Expand Up @@ -201,4 +209,17 @@ public function blueprint()
{
return Facades\UserGroup::blueprint();
}

protected function matchesWildcard(string $wildcardPermission, string $requestedPermission): bool
{
if (! str_contains($wildcardPermission, '*')) {
return false;
}

$pattern = preg_quote($wildcardPermission, '/');
$pattern = str_replace('\*', '.*', $pattern);
$pattern = '/^'.$pattern.'$/';

return (bool) preg_match($pattern, $requestedPermission);
}
}
81 changes: 81 additions & 0 deletions tests/Auth/PermissibleContractTests.php
Original file line number Diff line number Diff line change
Expand Up @@ -363,4 +363,85 @@ public function it_sets_all_the_groups()
'c' => 'c',
], $user->groups()->map->handle()->all());
}

#[Test]
public function it_checks_wildcard_permission_with_asterisk_at_beginning()
{
$role = RoleAPI::make('test')->addPermission('* blog entries');
RoleAPI::shouldReceive('find')->with('test')->andReturn($role);
RoleAPI::shouldReceive('all')->andReturn(collect([$role]));

$user = $this->createPermissible()->assignRole($role);
$user->save();

$this->assertTrue($user->hasPermission('view blog entries'));
$this->assertTrue($user->hasPermission('edit blog entries'));
$this->assertTrue($user->hasPermission('delete blog entries'));
$this->assertFalse($user->hasPermission('view news entries'));
$this->assertFalse($user->hasPermission('view blog posts'));
}

#[Test]
public function it_checks_wildcard_permission_with_asterisk_in_middle()
{
$role = RoleAPI::make('test')->addPermission('view * entries');
RoleAPI::shouldReceive('find')->with('test')->andReturn($role);
RoleAPI::shouldReceive('all')->andReturn(collect([$role]));

$user = $this->createPermissible()->assignRole($role);
$user->save();

$this->assertTrue($user->hasPermission('view blog entries'));
$this->assertTrue($user->hasPermission('view news entries'));
$this->assertTrue($user->hasPermission('view products entries'));
$this->assertFalse($user->hasPermission('edit blog entries'));
$this->assertFalse($user->hasPermission('view blog posts'));
}

#[Test]
public function it_checks_wildcard_permission_through_user_group()
{
$role = RoleAPI::make('test')->addPermission('view * entries');
$userGroup = (new UserGroup)->handle('testgroup')->assignRole($role);

RoleAPI::shouldReceive('find')->with('test')->andReturn($role);
RoleAPI::shouldReceive('all')->andReturn(collect([$role]));
UserGroupAPI::shouldReceive('find')->with('testgroup')->andReturn($userGroup);
UserGroupAPI::shouldReceive('all')->andReturn(collect([$userGroup]));

$user = $this->createPermissible()->addToGroup($userGroup);
$user->save();

$this->assertTrue($user->hasPermission('view blog entries'));
$this->assertTrue($user->hasPermission('view news entries'));
$this->assertTrue($user->hasPermission('view products entries'));
$this->assertFalse($user->hasPermission('edit blog entries'));
$this->assertFalse($user->hasPermission('view blog posts'));
}

#[Test]
public function it_checks_multiple_wildcard_permissions_from_different_sources()
{
$directRole = RoleAPI::make('direct')->addPermission('view * entries');
$groupRole = RoleAPI::make('grouprole')->addPermission('* blog entries');
$userGroup = (new UserGroup)->handle('testgroup')->assignRole($groupRole);

RoleAPI::shouldReceive('find')->with('direct')->andReturn($directRole);
RoleAPI::shouldReceive('find')->with('grouprole')->andReturn($groupRole);
RoleAPI::shouldReceive('all')->andReturn(collect([$directRole, $groupRole]));
UserGroupAPI::shouldReceive('find')->with('testgroup')->andReturn($userGroup);
UserGroupAPI::shouldReceive('all')->andReturn(collect([$userGroup]));

$user = $this->createPermissible()
->assignRole($directRole)
->addToGroup($userGroup);
$user->save();

$this->assertTrue($user->hasPermission('view blog entries'));
$this->assertTrue($user->hasPermission('view news entries'));
$this->assertTrue($user->hasPermission('edit blog entries'));
$this->assertTrue($user->hasPermission('delete blog entries'));
$this->assertFalse($user->hasPermission('delete news entries'));
$this->assertFalse($user->hasPermission('view blog posts'));
}
}
39 changes: 39 additions & 0 deletions tests/Auth/RoleTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -138,4 +138,43 @@ public function it_is_arrayable()
->each(fn ($value, $key) => $this->assertEquals($value, $role->{$key}))
->each(fn ($value, $key) => $this->assertEquals($value, $role[$key]));
}

#[Test]
public function it_checks_wildcard_permission_with_asterisk_at_beginning()
{
$role = (new Role)->addPermission('* blog entries');

$this->assertTrue($role->hasPermission('view blog entries'));
$this->assertTrue($role->hasPermission('edit blog entries'));
$this->assertTrue($role->hasPermission('delete blog entries'));
$this->assertFalse($role->hasPermission('view news entries'));
$this->assertFalse($role->hasPermission('view blog posts'));
}

#[Test]
public function it_checks_wildcard_permission_with_asterisk_in_middle()
{
$role = (new Role)->addPermission('view * entries');

$this->assertTrue($role->hasPermission('view blog entries'));
$this->assertTrue($role->hasPermission('view news entries'));
$this->assertTrue($role->hasPermission('view products entries'));
$this->assertFalse($role->hasPermission('edit blog entries'));
$this->assertFalse($role->hasPermission('view blog posts'));
}

#[Test]
public function it_checks_multiple_wildcard_permissions()
{
$role = (new Role)
->addPermission('view * entries')
->addPermission('* blog entries');

$this->assertTrue($role->hasPermission('view blog entries'));
$this->assertTrue($role->hasPermission('view news entries'));
$this->assertTrue($role->hasPermission('edit blog entries'));
$this->assertTrue($role->hasPermission('delete blog entries'));
$this->assertFalse($role->hasPermission('delete news entries'));
$this->assertFalse($role->hasPermission('view blog posts'));
}
}
81 changes: 81 additions & 0 deletions tests/Auth/UserGroupTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -392,4 +392,85 @@ public function it_clones_internal_collections()
$this->assertEquals('A', $group->getSupplement('bar'));
$this->assertEquals('B', $clone->getSupplement('bar'));
}

#[Test]
public function it_checks_wildcard_permission_with_asterisk_at_beginning()
{
$role = new class extends Role
{
public function permissions($permissions = null)
{
return collect(['* blog entries']);
}
};

$group = UserGroup::make()->assignRole($role);

$this->assertTrue($group->hasPermission('view blog entries'));
$this->assertTrue($group->hasPermission('edit blog entries'));
$this->assertTrue($group->hasPermission('delete blog entries'));
$this->assertFalse($group->hasPermission('view news entries'));
$this->assertFalse($group->hasPermission('view blog posts'));
}

#[Test]
public function it_checks_wildcard_permission_with_asterisk_in_middle()
{
$role = new class extends Role
{
public function permissions($permissions = null)
{
return collect(['view * entries']);
}
};

$group = UserGroup::make()->assignRole($role);

$this->assertTrue($group->hasPermission('view blog entries'));
$this->assertTrue($group->hasPermission('view news entries'));
$this->assertTrue($group->hasPermission('view products entries'));
$this->assertFalse($group->hasPermission('edit blog entries'));
$this->assertFalse($group->hasPermission('view blog posts'));
}

#[Test]
public function it_checks_multiple_roles_with_wildcard_permissions()
{
$roleOne = new class extends Role
{
public function handle(?string $handle = null)
{
return 'role_one';
}

public function permissions($permissions = null)
{
return collect(['view * entries']);
}
};

$roleTwo = new class extends Role
{
public function handle(?string $handle = null)
{
return 'role_two';
}

public function permissions($permissions = null)
{
return collect(['* blog entries']);
}
};

$group = UserGroup::make()
->assignRole($roleOne)
->assignRole($roleTwo);

$this->assertTrue($group->hasPermission('view blog entries'));
$this->assertTrue($group->hasPermission('view news entries'));
$this->assertTrue($group->hasPermission('edit blog entries'));
$this->assertTrue($group->hasPermission('delete blog entries'));
$this->assertFalse($group->hasPermission('delete news entries'));
$this->assertFalse($group->hasPermission('view blog posts'));
}
}
Loading