Skip to content

Commit

Permalink
Allow syncing abilities
Browse files Browse the repository at this point in the history
  • Loading branch information
JosephSilber committed Aug 31, 2017
1 parent 3d15ce1 commit cc73893
Show file tree
Hide file tree
Showing 4 changed files with 185 additions and 42 deletions.
8 changes: 4 additions & 4 deletions src/Bouncer.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,14 @@

use Silber\Bouncer\Seed\Seeder;
use Silber\Bouncer\Database\Models;
use Silber\Bouncer\Conductors\SyncsRoles;
use Silber\Bouncer\Conductors\ChecksRoles;
use Silber\Bouncer\Conductors\AssignsRoles;
use Silber\Bouncer\Conductors\RemovesRoles;
use Silber\Bouncer\Conductors\GivesAbilities;
use Silber\Bouncer\Conductors\RemovesAbilities;
use Silber\Bouncer\Conductors\ForbidsAbilities;
use Silber\Bouncer\Conductors\UnforbidsAbilities;
use Silber\Bouncer\Conductors\SyncsRolesAndAbilities;
use Silber\Bouncer\Contracts\Clipboard as ClipboardContract;
use Silber\Bouncer\Contracts\CachedClipboard as CachedClipboardContract;

Expand Down Expand Up @@ -163,14 +163,14 @@ public function retract($role)
}

/**
* Start a chain, to sync roles for the given authority.
* Start a chain, to sync roles/abilities for the given authority.
*
* @param \Illuminate\Database\Eloquent\Model $authority
* @return \Silber\Bouncer\Conductors\SyncsRoles
* @return \Silber\Bouncer\Conductors\SyncsRolesAndAbilities
*/
public function sync(Model $authority)
{
return new SyncsRoles($authority);
return new SyncsRolesAndAbilities($authority);
}

/**
Expand Down
37 changes: 0 additions & 37 deletions src/Conductors/SyncsRoles.php

This file was deleted.

136 changes: 136 additions & 0 deletions src/Conductors/SyncsRolesAndAbilities.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
<?php

namespace Silber\Bouncer\Conductors;

use Silber\Bouncer\Helpers;
use Silber\Bouncer\Database\Models;

use Illuminate\Support\Arr;
use Illuminate\Support\Collection;
use Illuminate\Database\Eloquent\Model;

class SyncsRolesAndAbilities
{
/**
* The authority for whom to sync roles/abilities.
*
* @var \Illuminate\Database\Eloquent\Model
*/
protected $authority;

/**
* Constructor.
*
* @param \Illuminate\Database\Eloquent\Model $authority
*/
public function __construct(Model $authority)
{
$this->authority = $authority;
}

/**
* Sync the provided roles to the authority.
*
* @param iterable $roles
* @return void
*/
public function roles($roles)
{
$this->authority->roles()->sync(Models::role()->getRoleKeys($roles), true);
}

/**
* Sync the provided abilities to the authority.
*
* @param iterable $abilities
* @return void
*/
public function abilities($abilities)
{
$this->syncAbilities($abilities);
}

/**
* Sync the provided forbidden abilities to the authority.
*
* @param iterable $abilities
* @return void
*/
public function forbiddenAbilities($abilities)
{
$this->syncAbilities($abilities, ['forbidden' => true]);
}

/**
* Sync the given abilities for the authority.
*
* @param iterable $abilities
* @param array $options
* @return void
*/
protected function syncAbilities($abilities, $options = ['forbidden' => false])
{
$keyName = Models::ability()->getKeyName();

$abilityKeys = $this->getAbilityIds($abilities, $keyName);

$this->authority->abilities()
->wherePivot('forbidden', $options['forbidden'])
->detach();

if ($options['forbidden']) {
(new ForbidsAbilities($this->authority))->to($abilityKeys);
} else {
(new GivesAbilities($this->authority))->to($abilityKeys);
}
}

/**
* Get the IDs of the given abilities, creating new ones for the missing names.
*
* @param iterable $abilities
* @param string $keyName
* @return array
*/
protected function getAbilityIds($abilities, $keyName)
{
$abilities = Helpers::groupModelsAndIdentifiersByType($abilities);

$abilities['strings'] = $this->findAbilityKeysOrCreate(
$abilities['strings'], $keyName
);

$abilities['models'] = Arr::pluck($abilities['models'], $keyName);

return Arr::collapse($abilities);
}

/**
* Find the IDs of the given ability names, creating the ones that are missing.
*
* @param iterable $names
* @param string $keyName
* @return array
*/
protected function findAbilityKeysOrCreate($names, $keyName)
{
if (empty($names)) {
return [];
}

$model = Models::ability();

$existing = $model->simpleAbility()
->whereIn('name', $names)
->get([$keyName, 'name'])
->pluck('name', $keyName);

$creating = (new Collection($names))->diff($existing);

$created = $creating->map(function ($name) use ($model) {
return $model->create(compact('name'))->getKey();
});

return $created->merge($existing->keys())->all();
}
}
46 changes: 45 additions & 1 deletion tests/SyncTest.php
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
<?php

use Silber\Bouncer\Database\Role;
use Silber\Bouncer\Database\Ability;

class SyncTest extends BaseTestCase
{
public function test_syncing_user_roles_by_id()
public function test_syncing_roles()
{
$bouncer = $this->bouncer($user = User::create())->dontCache();

Expand All @@ -23,6 +24,49 @@ public function test_syncing_user_roles_by_id()
$this->assertTrue($bouncer->is($user)->notAn($admin));
}

public function test_syncing_abilities()
{
$bouncer = $this->bouncer($user = User::create())->dontCache();

$editSite = Ability::create(['name' => 'edit-site']);
$banUsers = Ability::create(['name' => 'ban-users']);
$accessDashboard = Ability::create(['name' => 'access-dashboard']);

$bouncer->allow($user)->to([$editSite, $banUsers]);

$this->assertTrue($bouncer->allows('edit-site'));
$this->assertTrue($bouncer->allows('ban-users'));
$this->assertTrue($bouncer->denies('access-dashboard'));

$bouncer->sync($user)->abilities([$banUsers->id, 'access-dashboard']);

$this->assertTrue($bouncer->denies('edit-site'));
$this->assertTrue($bouncer->allows('ban-users'));
$this->assertTrue($bouncer->allows('access-dashboard'));
}

public function test_syncing_forbidden_abilities()
{
$bouncer = $this->bouncer($user = User::create())->dontCache();

$editSite = Ability::create(['name' => 'edit-site']);
$banUsers = Ability::create(['name' => 'ban-users']);
$accessDashboard = Ability::create(['name' => 'access-dashboard']);

$bouncer->allow($user)->everything();
$bouncer->forbid($user)->to([$editSite, $banUsers->id]);

$this->assertTrue($bouncer->denies('edit-site'));
$this->assertTrue($bouncer->denies('ban-users'));
$this->assertTrue($bouncer->allows('access-dashboard'));

$bouncer->sync($user)->forbiddenAbilities([$banUsers->id, 'access-dashboard']);

$this->assertTrue($bouncer->allows('edit-site'));
$this->assertTrue($bouncer->denies('ban-users'));
$this->assertTrue($bouncer->denies('access-dashboard'));
}

/**
* Create a new role with the given name.
*
Expand Down

0 comments on commit cc73893

Please sign in to comment.