From b2bc6186eec2196029905b3882bb7c59cb634a90 Mon Sep 17 00:00:00 2001 From: simonbarbier Date: Wed, 29 Apr 2015 15:52:55 +0200 Subject: [PATCH 01/11] remove resourceId casting --- src/Permissions/PermissionFactory.php | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/Permissions/PermissionFactory.php b/src/Permissions/PermissionFactory.php index d44eea0..81b82fc 100644 --- a/src/Permissions/PermissionFactory.php +++ b/src/Permissions/PermissionFactory.php @@ -33,8 +33,7 @@ public static function createFromArray(array $permission) { $type = $permission['type']; - // Make sure the id is typecast to an integer. - $id = ! is_null($permission['resource_id']) ? (int) $permission['resource_id'] : null; + $id = ! is_null($permission['resource_id']) ? $permission['resource_id'] : null; if ($type === Privilege::TYPE) { return new Privilege( @@ -60,8 +59,7 @@ public static function createFromArray(array $permission) */ public static function createFromObject($permission) { - // Make sure the id is typecast to an integer. - $id = ! is_null($permission->resource_id) ? (int) $permission->resource_id : null; + $id = ! is_null($permission->resource_id) ? $permission->resource_id : null; if ($permission->type === Privilege::TYPE) { return new Privilege( From b39a479f0f10ea5fca7b719a0cc6dc269eb87a4d Mon Sep 17 00:00:00 2001 From: simonbarbier Date: Wed, 29 Apr 2015 16:28:01 +0200 Subject: [PATCH 02/11] adapt documentation etc. according to the uncasting of resourceId --- readme.md | 14 +++++++------- src/Lock.php | 12 ++++++------ src/LockAware.php | 10 +++++----- src/Permissions/AbstractPermission.php | 2 +- src/Permissions/Permission.php | 2 +- src/Resources/Resource.php | 2 +- src/Resources/SimpleResource.php | 6 +++--- 7 files changed, 24 insertions(+), 24 deletions(-) diff --git a/readme.md b/readme.md index 1dd60d9..365af5d 100644 --- a/readme.md +++ b/readme.md @@ -521,7 +521,7 @@ Checks to see if the current caller has permission to do something. can( string|array $action, string|\BeatSwitch\Lock\Resources\Resource $resource = null, - int $resourceId = null + string $resourceId = null ) ``` @@ -533,7 +533,7 @@ Checks to see if it's forbidden for the current caller to do something. cannot( string|array $action, string|\BeatSwitch\Lock\Resources\Resource $resource = null, - int $resourceId = null + string $resourceId = null ) ``` @@ -545,7 +545,7 @@ Sets a `Privilege` permission on a caller to allow it to do something. Removes a allow( string|array $action, string|\BeatSwitch\Lock\Resources\Resource $resource = null, - int $resourceId = null, + string $resourceId = null, \BeatSwitch\Lock\Permissions\Condition[] $conditions = [] ) ``` @@ -558,7 +558,7 @@ Sets a `Restriction` permission on a caller to prevent it from doing something. deny( string|array $action, string|\BeatSwitch\Lock\Resources\Resource $resource = null, - int $resourceId = null, + string $resourceId = null, \BeatSwitch\Lock\Permissions\Condition[] $conditions = [] ) ``` @@ -571,7 +571,7 @@ Toggles the value for the given permission. toggle( string|array $action, string|\BeatSwitch\Lock\Resources\Resource $resource = null, - int $resourceId = null + string $resourceId = null ) ``` @@ -674,7 +674,7 @@ We'll assume we have a `CallerPermission` model class with at least the followin - `type` (varchar, 10) - `action` (varchar, 100) - `resource_type` (varchar, 100, nullable) -- `resource_id` (int, 11, nullable) +- `resource_id` (varchar, 36, nullable) And we have a `RolePermission` model with the following database columns: @@ -682,7 +682,7 @@ And we have a `RolePermission` model with the following database columns: - `type` (varchar, 10) - `action` (varchar, 100) - `resource_type` (varchar, 100, nullable) -- `resource_id` (int, 11, nullable) +- `resource_id` (varchar, 36, nullable) Let's check out a full implementation of the driver below. Notice that for the `getCallerPermissions` method we're using the `PermissionFactory` class to easily map the data and create `Permission` objects from them. The `PermissionFactory`'s `createFromData` method will accept both arrays and objects. diff --git a/src/Lock.php b/src/Lock.php index 1e70683..28cc29e 100644 --- a/src/Lock.php +++ b/src/Lock.php @@ -19,7 +19,7 @@ abstract class Lock * * @param string|array $action * @param string|\BeatSwitch\Lock\Resources\Resource $resource - * @param int $resourceId + * @param string $resourceId * @return bool */ public function can($action, $resource = null, $resourceId = null) @@ -48,7 +48,7 @@ public function can($action, $resource = null, $resourceId = null) * * @param string|array $action * @param string|\BeatSwitch\Lock\Resources\Resource $resource - * @param int $resourceId + * @param string $resourceId * @return bool */ public function cannot($action, $resource = null, $resourceId = null) @@ -61,7 +61,7 @@ public function cannot($action, $resource = null, $resourceId = null) * * @param string|array $action * @param string|\BeatSwitch\Lock\Resources\Resource $resource - * @param int $resourceId + * @param string $resourceId * @param \BeatSwitch\Lock\Permissions\Condition|\BeatSwitch\Lock\Permissions\Condition[]|\Closure $conditions */ public function allow($action, $resource = null, $resourceId = null, $conditions = []) @@ -94,7 +94,7 @@ public function allow($action, $resource = null, $resourceId = null, $conditions * * @param string|array $action * @param string|\BeatSwitch\Lock\Resources\Resource $resource - * @param int $resourceId + * @param string $resourceId * @param \BeatSwitch\Lock\Permissions\Condition|\BeatSwitch\Lock\Permissions\Condition[]|\Closure $conditions */ public function deny($action, $resource = null, $resourceId = null, $conditions = []) @@ -126,7 +126,7 @@ public function deny($action, $resource = null, $resourceId = null, $conditions * * @param string|array $action * @param string|\BeatSwitch\Lock\Resources\Resource $resource - * @param int $resourceId + * @param string $resourceId */ public function toggle($action, $resource = null, $resourceId = null) { @@ -285,7 +285,7 @@ protected function getAliasesForAction($action) * Create a resource value object if a non resource object is passed * * @param string|\BeatSwitch\Lock\Resources\Resource|null $resource - * @param int|null $resourceId + * @param string|null $resourceId * @return \BeatSwitch\Lock\Resources\Resource */ protected function convertResourceToObject($resource, $resourceId = null) diff --git a/src/LockAware.php b/src/LockAware.php index b9befc3..53ab118 100644 --- a/src/LockAware.php +++ b/src/LockAware.php @@ -20,7 +20,7 @@ trait LockAware * * @param string|array $action * @param string|\BeatSwitch\Lock\Resources\Resource $resource - * @param int $resourceId + * @param string $resourceId * @return bool */ public function can($action, $resource = null, $resourceId = null) @@ -35,7 +35,7 @@ public function can($action, $resource = null, $resourceId = null) * * @param string|array $action * @param string|\BeatSwitch\Lock\Resources\Resource $resource - * @param int $resourceId + * @param string $resourceId * @return bool */ public function cannot($action, $resource = null, $resourceId = null) @@ -50,7 +50,7 @@ public function cannot($action, $resource = null, $resourceId = null) * * @param string|array $action * @param string|\BeatSwitch\Lock\Resources\Resource $resource - * @param int $resourceId + * @param string $resourceId * @param \BeatSwitch\Lock\Permissions\Condition|\BeatSwitch\Lock\Permissions\Condition[]|\Closure $conditions */ public function allow($action, $resource = null, $resourceId = null, $conditions = []) @@ -65,7 +65,7 @@ public function allow($action, $resource = null, $resourceId = null, $conditions * * @param string|array $action * @param string|\BeatSwitch\Lock\Resources\Resource $resource - * @param int $resourceId + * @param string $resourceId * @param \BeatSwitch\Lock\Permissions\Condition|\BeatSwitch\Lock\Permissions\Condition[]|\Closure $conditions */ public function deny($action, $resource = null, $resourceId = null, $conditions = []) @@ -80,7 +80,7 @@ public function deny($action, $resource = null, $resourceId = null, $conditions * * @param string|array $action * @param string|\BeatSwitch\Lock\Resources\Resource $resource - * @param int $resourceId + * @param string $resourceId */ public function toggle($action, $resource = null, $resourceId = null) { diff --git a/src/Permissions/AbstractPermission.php b/src/Permissions/AbstractPermission.php index fc4d584..7215d62 100644 --- a/src/Permissions/AbstractPermission.php +++ b/src/Permissions/AbstractPermission.php @@ -177,7 +177,7 @@ public function getResourceType() /** * The resource's identifier * - * @return int|null + * @return string|null */ public function getResourceId() { diff --git a/src/Permissions/Permission.php b/src/Permissions/Permission.php index c46d019..33e2b20 100644 --- a/src/Permissions/Permission.php +++ b/src/Permissions/Permission.php @@ -58,7 +58,7 @@ public function getResourceType(); /** * The resource's identifier * - * @return int|null + * @return string|null */ public function getResourceId(); } diff --git a/src/Resources/Resource.php b/src/Resources/Resource.php index 8d87414..c1cbb92 100644 --- a/src/Resources/Resource.php +++ b/src/Resources/Resource.php @@ -16,7 +16,7 @@ public function getResourceType(); /** * The main identifier for the resource * - * @return int|null + * @return string|null */ public function getResourceId(); } diff --git a/src/Resources/SimpleResource.php b/src/Resources/SimpleResource.php index ac42515..d67ed75 100644 --- a/src/Resources/SimpleResource.php +++ b/src/Resources/SimpleResource.php @@ -9,13 +9,13 @@ final class SimpleResource implements Resource private $type; /** - * @var int|null + * @var string|null */ private $id; /** * @param string $type - * @param int|null $id + * @param string|null $id */ public function __construct($type, $id = null) { @@ -36,7 +36,7 @@ public function getResourceType() /** * The main identifier for the resource * - * @return int|null + * @return string|null */ public function getResourceId() { From acac4885d417246d44ccc83f82d99a73c1f3e4cc Mon Sep 17 00:00:00 2001 From: simonbarbier Date: Thu, 30 Apr 2015 10:32:33 +0200 Subject: [PATCH 03/11] adapt unit test to use non-casted resource id --- src/Tests/PersistentDriverTestCase.php | 79 ++++++++++++++++---------- 1 file changed, 49 insertions(+), 30 deletions(-) diff --git a/src/Tests/PersistentDriverTestCase.php b/src/Tests/PersistentDriverTestCase.php index e4341fd..7694ddb 100644 --- a/src/Tests/PersistentDriverTestCase.php +++ b/src/Tests/PersistentDriverTestCase.php @@ -113,7 +113,7 @@ final function it_fails_with_an_invalid_resource_type() final function it_succeeds_with_a_valid_action_on_a_resource_object() { $lock = $this->getCallerLock(); - $event = new SimpleResource('events', 1); + $event = new SimpleResource('events', '1'); $lock->allow('read', $event); @@ -123,7 +123,7 @@ final function it_succeeds_with_a_valid_action_on_a_resource_object() /** @test */ final function it_fails_with_an_invalid_action_on_a_resource_object() { - $this->assertFalse($this->getCallerLock()->can('edit', new SimpleResource('events', 1))); + $this->assertFalse($this->getCallerLock()->can('edit', new SimpleResource('events', '1'))); } /** @test */ @@ -157,7 +157,7 @@ function it_fails_with_a_denied_action_for_a_resource_type() { $lock = $this->getCallerLock(); - $lock->allow('update', new SimpleResource('events', 1)); + $lock->allow('update', new SimpleResource('events', '1')); // We can't update every event, just the one with an ID of 1. $this->assertFalse($lock->can('update', 'events')); @@ -167,7 +167,7 @@ function it_fails_with_a_denied_action_for_a_resource_type() final function it_succeeds_when_overriding_a_denied_action_on_a_resource() { $lock = $this->getCallerLock(); - $stub = new SimpleResource('events', 1); + $stub = new SimpleResource('events', '1'); $lock->deny('update'); $lock->allow('update', $stub); @@ -180,9 +180,9 @@ final function it_fails_with_an_incorrect_resource_object() { $lock = $this->getCallerLock(); - $lock->allow('update', new SimpleResource('events', 1)); + $lock->allow('update', new SimpleResource('events', '1')); - $this->assertFalse($lock->can('update', new SimpleResource('events', 2))); + $this->assertFalse($lock->can('update', new SimpleResource('events', '2'))); } /** @test */ @@ -246,7 +246,7 @@ final function it_can_check_actions_from_aliases() $this->assertFalse($lock->can('manage')); $this->assertTrue($lock->can('manage', 'accounts')); - $this->assertTrue($lock->can('manage', 'accounts', 1)); + $this->assertTrue($lock->can('manage', 'accounts', '1')); $this->assertFalse($lock->can('manage', 'events')); $this->assertTrue($lock->can('read', 'accounts')); $this->assertTrue($lock->can(['read', 'update'], 'accounts')); @@ -313,33 +313,52 @@ final function it_can_make_a_role_lock_aware() /** @test */ final function it_can_return_allowed_resource_ids() { - $this->getCallerLock()->allow('update', 'users', 1); - $this->getCallerLock()->allow('update', 'users', 2); - $this->getCallerLock()->allow('update', 'events', 4); - $this->getCallerLock()->allow(['update', 'delete'], 'users', 3); - $this->getCallerLock()->allow(['update', 'delete'], 'users', 5); - $this->getCallerLock()->allow('delete', 'users', 2); - $this->getCallerLock()->deny('update', 'users', 2); - - $this->assertEquals([1, 3, 5], $this->getCallerLock()->allowed('update', 'users')); - $this->assertEquals([3, 5, 2], $this->getCallerLock()->allowed('delete', 'users')); - $this->assertEquals([3, 5], $this->getCallerLock()->allowed(['update', 'delete'], 'users')); + $this->getCallerLock()->allow('update', 'users', '1'); + $this->getCallerLock()->allow('update', 'users', '2'); + $this->getCallerLock()->allow('update', 'events', '4'); + $this->getCallerLock()->allow(['update', 'delete'], 'users', '3'); + $this->getCallerLock()->allow(['update', 'delete'], 'users', '5'); + $this->getCallerLock()->allow('delete', 'users', '2'); + $this->getCallerLock()->deny('update', 'users', '2'); + + $expected = ['1', '3', '5']; + $result = $this->getCallerLock()->allowed('update', 'users'); + sort($expected, SORT_STRING); + sort($result, SORT_STRING); + $this->assertEquals($expected,$result); + + $expected = ['3', '5', '2']; + $result = $this->getCallerLock()->allowed('delete', 'users'); + sort($expected, SORT_STRING); + sort($result, SORT_STRING); + $this->assertEquals($expected,$result); + + $expected = ['3', '5']; + $result = $this->getCallerLock()->allowed(['update', 'delete'], 'users'); + sort($expected, SORT_STRING); + sort($result, SORT_STRING); + $this->assertEquals($expected,$result); } /** @test */ final function it_can_return_denied_resource_ids() { - $this->getCallerLock()->allow('update', 'users', 1); - $this->getCallerLock()->allow('update', 'users', 2); - $this->getCallerLock()->allow('update', 'events', 4); - $this->getCallerLock()->allow(['update', 'delete'], 'users', 3); - $this->getCallerLock()->allow(['update', 'delete'], 'users', 5); - $this->getCallerLock()->deny('delete', 'users', 1); - $this->getCallerLock()->allow('delete', 'users', 2); - $this->getCallerLock()->deny('update', 'users', 2); - - $this->assertEquals([2], $this->getCallerLock()->denied('update', 'users')); - $this->assertEquals([1], $this->getCallerLock()->denied('delete', 'users')); - $this->assertEquals([1, 2], $this->getCallerLock()->denied(['update', 'delete'], 'users')); + $this->getCallerLock()->allow('update', 'users', '1'); + $this->getCallerLock()->allow('update', 'users', '2'); + $this->getCallerLock()->allow('update', 'events', '4'); + $this->getCallerLock()->allow(['update', 'delete'], 'users', '3'); + $this->getCallerLock()->allow(['update', 'delete'], 'users', '5'); + $this->getCallerLock()->deny('delete', 'users', '1'); + $this->getCallerLock()->allow('delete', 'users', '2'); + $this->getCallerLock()->deny('update', 'users', '2'); + + $this->assertEquals(['2'], $this->getCallerLock()->denied('update', 'users')); + $this->assertEquals(['1'], $this->getCallerLock()->denied('delete', 'users')); + + $expected = ['1', '2']; + $result = $this->getCallerLock()->denied(['update', 'delete'], 'users'); + sort($expected, SORT_STRING); + sort($result, SORT_STRING); + $this->assertEquals($expected,$result); } } From 3d89d3788e8e92fb3717a638dc6c355f8e779c91 Mon Sep 17 00:00:00 2001 From: simonbarbier Date: Wed, 6 May 2015 15:53:30 +0200 Subject: [PATCH 04/11] test own repo --- src/Tests/PersistentDriverTestCase.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Tests/PersistentDriverTestCase.php b/src/Tests/PersistentDriverTestCase.php index 7694ddb..11801eb 100644 --- a/src/Tests/PersistentDriverTestCase.php +++ b/src/Tests/PersistentDriverTestCase.php @@ -360,5 +360,6 @@ final function it_can_return_denied_resource_ids() sort($expected, SORT_STRING); sort($result, SORT_STRING); $this->assertEquals($expected,$result); + //TEST 123 } } From 884b27f968a8f63a36ce9730a8e01422532b4947 Mon Sep 17 00:00:00 2001 From: simonbarbier Date: Fri, 8 May 2015 10:24:22 +0200 Subject: [PATCH 05/11] add new functionnalities : clear, allowed with inheritance and can(not)Explicitly --- src/Lock.php | 233 ++++++- src/Tests/PersistentDriverTestCase.php | 888 ++++++++++++++++++++++++- 2 files changed, 1097 insertions(+), 24 deletions(-) diff --git a/src/Lock.php b/src/Lock.php index 28cc29e..515a787 100644 --- a/src/Lock.php +++ b/src/Lock.php @@ -43,6 +43,34 @@ public function can($action, $resource = null, $resourceId = null) return true; } + public function canExplicitly($action, $resource = null, $resourceId = null) { + $resource = $this->convertResourceToObject($resource, $resourceId); + $permissions = $this->getPermissions(); + + $hasPrivilege = false; + + foreach ($permissions as $permission) { + if($permission instanceof Privilege && $permission->getAction() == $action && $resource->getResourceType() == $permission->getResourceType() && $resource->getResourceId() == $permission->getResourceId()) { + $hasPrivilege = true; + } + } + return $hasPrivilege; + } + + public function cannotExplicitly($action, $resource = null, $resourceId = null) { + $resource = $this->convertResourceToObject($resource, $resourceId); + $permissions = $this->getPermissions(); + + $hasRestriction = false; + + foreach ($permissions as $permission) { + if($permission instanceof Restriction && $permission->getAction() == $action && $resource->getResourceType() == $permission->getResourceType() && $resource->getResourceId() == $permission->getResourceId()) { + $hasRestriction = true; + } + } + return $hasRestriction; + } + /** * Determine if an action isn't allowed * @@ -121,6 +149,29 @@ public function deny($action, $resource = null, $resourceId = null, $conditions } } + + /** + * Clear the subject permission/restriction to do something + * + * @param string|array $action + * @param string|\BeatSwitch\Lock\Resources\Resource $resource + * @param string $resourceId + * @param \BeatSwitch\Lock\Permissions\Condition|\BeatSwitch\Lock\Permissions\Condition[]|\Closure $conditions + */ + public function clear($action, $resource = null, $resourceId = null, $conditions = []) + { + $actions = (array) $action; + $resource = $this->convertResourceToObject($resource, $resourceId); + $permissions = $this->getPermissions(); + foreach ($actions as $action) { + $privilege = new Privilege($action, $resource); + $this->removePermission($privilege); + $restriction = new Restriction($action, $resource); + $this->removePermission($restriction); + } + } + + /** * Change the value for a permission * @@ -144,20 +195,92 @@ public function toggle($action, $resource = null, $resourceId = null) * @param string|\BeatSwitch\Lock\Resources\Resource $resourceType * @return array */ - public function allowed($action, $resourceType) + public function allowed($actions, $resourceType) { + + // rules : + // (1) a permission on caller level is always winner against a permission on role level + // (2) a privilege is winner against a restriction between two different roles inherited by a caller + // (1) > (2) + + // example : + // Peter inherits Editor role & Publisher role (R2 = Resource Id 2) + // Peter is allowed to R2 and Editor is denied to R2 => Peter can R2 + // Peter is denied to R2 and Editor is allowed to R2 => Peter cannot R2 + // Peter has not explicit permission on R2, but Editor is allowed to R2 and Publisher is denied to R2 => Peter can R2 + + /* !!! only works with one level of heritence of role on caller */ + + // if actions is more than one action, it will return resource id with Privilege for ALL action + $resourceType = $resourceType instanceof Resource ? $resourceType->getResourceType() : $resourceType; + $actions = (array) $actions; + $allowed = []; + $allowedOnCallerLevel = []; + $deniedOnCallerLevel = []; + $allowedOnRoleLevel = []; + + foreach($actions as $action) { + + $allowed[$action] = []; + $allowedOnCallerLevel[$action] = []; + $deniedOnCallerLevel[$action] = []; + $allowedOnRoleLevel[$action] = []; + + // browse permission of a role / caller + foreach($this->getPermissions() as $permission){ + if($permission->getResourceType() != $resourceType || $permission->getAction() != $action) { + continue; + } - // Get all the ids from privileges which match the given resource type. - $ids = array_unique(array_map(function (Permission $permission) { - return $permission->getResourceId(); - }, array_filter($this->getPermissions(), function (Permission $permission) use ($resourceType) { - return $permission instanceof Privilege && $permission->getResourceType() === $resourceType; - }))); + if($permission instanceof Restriction) { + if(!in_array($permission->getResourceId(), $deniedOnCallerLevel[$action])) { + $deniedOnCallerLevel[$action][] = $permission->getResourceId(); + } + } elseif($permission instanceof Privilege) { + if(!in_array($permission->getResourceId(), $allowedOnCallerLevel[$action])) { + $allowedOnCallerLevel[$action][] = $permission->getResourceId(); + } + } else { + throw new \Exception("Unrecognize permission", 1); + } + } + + // browse the permission of roles inherited by the caller + if(method_exists($this, 'getLockInstancesForCallerRoles')) { + foreach ($this->getLockInstancesForCallerRoles() as $role) { + foreach ($role->getPermissions() as $permission) { + if($permission->getResourceType() != $resourceType || $permission->getAction() != $action) { + continue; + } + + if($permission instanceof Privilege) { + if(!in_array($permission->getResourceId(), $allowedOnRoleLevel[$action])) { + $allowedOnRoleLevel[$action][] = $permission->getResourceId(); + } + } + } + } + + // we keep only the Privilege on role that are not a Restriction by the children(caller level) + $allowedOnRoleLevel[$action] = array_diff($allowedOnRoleLevel[$action], $deniedOnCallerLevel[$action]); + } + + // we combine Privilege on role level with Privilege on caller level + $allowed[$action] = array_unique(array_merge($allowedOnRoleLevel[$action], $allowedOnCallerLevel[$action])); + } - return array_values(array_filter($ids, function ($id) use ($action, $resourceType) { - return $this->can($action, $resourceType, $id); - })); + if(empty($actions)) { + // no action => nothing to return + return []; + } elseif(count($actions) == 1) { + // only one action = we return the allowed ressource id + return $allowed[$actions[0]]; + } else { + // we return ressource id present in all actions + $intersect = call_user_func_array('array_intersect', $allowed); + return array_values($intersect); + } } /** @@ -167,20 +290,90 @@ public function allowed($action, $resourceType) * @param string|\BeatSwitch\Lock\Resources\Resource $resourceType * @return array */ - public function denied($action, $resourceType) + public function denied($actions, $resourceType) { + + // if actions is more than one action, it will return resource id with Privilege for AT LEAST ONE action + $resourceType = $resourceType instanceof Resource ? $resourceType->getResourceType() : $resourceType; + $actions = (array) $actions; + $denied = []; + $allowedOnCallerLevel = []; + $deniedOnCallerLevel = []; + $allowedOnRoleLevel = []; + $deniedOnRoleLevel = []; + + foreach($actions as $action) { + $denied[$action] = []; + $allowedOnCallerLevel[$action] = []; + $deniedOnCallerLevel[$action] = []; + $deniedOnRoleLevel[$action] = []; + + // browse permission of a role / caller + foreach($this->getPermissions() as $permission){ + if($permission->getResourceType() != $resourceType || $permission->getAction() != $action) { + continue; + } - // Get all the ids from restrictions which match the given resource type. - $ids = array_unique(array_map(function (Permission $permission) { - return $permission->getResourceId(); - }, array_filter($this->getPermissions(), function (Permission $permission) use ($resourceType) { - return $permission instanceof Restriction && $permission->getResourceType() === $resourceType; - }))); + if($permission instanceof Restriction) { + if(!in_array($permission->getResourceId(), $deniedOnCallerLevel[$action])) { + $deniedOnCallerLevel[$action][] = $permission->getResourceId(); + } + } elseif($permission instanceof Privilege) { + if(!in_array($permission->getResourceId(), $allowedOnCallerLevel[$action])) { + $allowedOnCallerLevel[$action][] = $permission->getResourceId(); + } + } else { + throw new \Exception("Unrecognize permission", 1); + } + } - return array_values(array_filter($ids, function ($id) use ($action, $resourceType) { - return $this->cannot($action, $resourceType, $id); - })); + // browse the permission of roles inherited by the caller + if(method_exists($this, 'getLockInstancesForCallerRoles')) { + foreach ($this->getLockInstancesForCallerRoles() as $role) { + foreach ($role->getPermissions() as $permission) { + if($permission->getResourceType() != $resourceType || $permission->getAction() != $action) { + continue; + } + + if($permission instanceof Privilege) { + if(!in_array($permission->getResourceId(), $allowedOnRoleLevel[$action])) { + $allowedOnRoleLevel[$action][] = $permission->getResourceId(); + } + + // remove any Restriction set on another role (Rule 2) + if(in_array($permission->getResourceId(), $deniedOnRoleLevel[$action])) { + $deniedOnRoleLevel[$action] = array_diff($deniedOnRoleLevel[$action],[$permission->getResourceId()]); + } + } + + if($permission instanceof Restriction) { + if(!in_array($permission->getResourceId(), $deniedOnRoleLevel[$action]) && !in_array($permission->getResourceId(), $allowedOnRoleLevel[$action])) { + $deniedOnRoleLevel[$action][] = $permission->getResourceId(); + } + } + } + } + + // we keep only the Restriction on role that are not a Allowed by the children(caller level) + $deniedOnRoleLevel[$action] = array_diff($deniedOnRoleLevel[$action], $allowedOnCallerLevel[$action]); + } + + // we combine Restriction on role level with Restriction on caller level + $denied[$action] = array_unique(array_merge($deniedOnRoleLevel[$action], $deniedOnCallerLevel[$action])); + } + + if(empty($actions)) { + // no action => nothing to return + return []; + } elseif(count($actions) == 1) { + // only one action = we return the denied ressource id + return $denied[$actions[0]]; + } else { + // we return ressource id present in actions (not specially in all actions) + $intersect = call_user_func_array('array_merge', $denied); + return array_values($intersect); + } } /** diff --git a/src/Tests/PersistentDriverTestCase.php b/src/Tests/PersistentDriverTestCase.php index 11801eb..8d2ea7b 100644 --- a/src/Tests/PersistentDriverTestCase.php +++ b/src/Tests/PersistentDriverTestCase.php @@ -39,7 +39,7 @@ function setUp() $this->manager = new Manager($this->driver); // Init the caller. - $this->caller = new SimpleCaller('users', 1, ['editor']); + $this->caller = new SimpleCaller('users', 1, ['editor', 'publisher']); $this->caller->setLock($this->getCallerLock()); } @@ -310,7 +310,10 @@ final function it_can_make_a_role_lock_aware() $this->assertTrue($role->can('create', 'users')); } - /** @test */ + /** + * @test + * @group failing + */ final function it_can_return_allowed_resource_ids() { $this->getCallerLock()->allow('update', 'users', '1'); @@ -332,12 +335,13 @@ final function it_can_return_allowed_resource_ids() sort($expected, SORT_STRING); sort($result, SORT_STRING); $this->assertEquals($expected,$result); - + $expected = ['3', '5']; $result = $this->getCallerLock()->allowed(['update', 'delete'], 'users'); sort($expected, SORT_STRING); sort($result, SORT_STRING); $this->assertEquals($expected,$result); + } /** @test */ @@ -360,6 +364,882 @@ final function it_can_return_denied_resource_ids() sort($expected, SORT_STRING); sort($result, SORT_STRING); $this->assertEquals($expected,$result); - //TEST 123 + + $this->getCallerLock()->deny('update', 'users', '6'); + $expected = ['1', '2', '6']; + $result = $this->getCallerLock()->denied(['update', 'delete'], 'users'); + sort($expected, SORT_STRING); + sort($result, SORT_STRING); + $this->assertEquals($expected,$result); + } + + /** @test */ + final function it_can_clear_permissions(){ + // test one (permission set on caller) + $this->getCallerLock()->allow('make', '1'); + // check if it was correctly set + $this->assertTrue($this->getCallerLock()->can('make', '1')); + // clear + $this->getCallerLock()->clear('make', '1'); + $this->assertFalse($this->getCallerLock()->can('make', '1')); + + + // test two (permission set on role herited by caller) + $this->getRoleLock('editor')->allow('make', '2'); + // check if it was correctly set + $this->assertTrue($this->getCallerLock()->can('make', '2')); + //clear + $this->getRoleLock('editor')->clear('make', '2'); + $this->assertFalse($this->getCallerLock()->can('make', '2')); + + + // test three check if can clear deny + $this->getRoleLock('editor')->allow('make'); // allow 'make' on ALL + $this->getCallerLock()->deny('make', '3'); + // check if it was correctly set + $this->assertFalse($this->getCallerLock()->can('make', '3')); + //clear + $this->getCallerLock()->clear('make', '3'); + $this->assertTrue($this->getCallerLock()->can('make', '3')); + } + + /** @test */ + final function it_can_return_allowed_resource_ids_with_inheritence(){ + $this->getRoleLock('editor')->allow('make', 'events', '1'); + $this->getRoleLock('editor')->allow('make', 'events', '2'); + $this->getCallerLock()->allow('make', 'events', '3'); + + $this->assertTrue($this->getCallerLock()->can('make', 'events', '1')); + $this->assertTrue($this->getCallerLock()->can('make', 'events', '2')); + $this->assertTrue($this->getCallerLock()->can('make', 'events', '3')); + $expected = ['1', '2', '3']; + $result = $this->getCallerLock()->allowed('make', 'events'); + sort($expected, SORT_STRING); + sort($result, SORT_STRING); + $this->assertEquals($expected,$result); + + $this->assertTrue($this->getRoleLock('editor')->can('make', 'events', '1')); + $this->assertTrue($this->getRoleLock('editor')->can('make', 'events', '2')); + $this->assertTrue($this->getRoleLock('editor')->cannot('make', 'events', '3')); + $expected = ['1', '2']; + $result = $this->getRoleLock('editor')->allowed('make', 'events'); + sort($expected, SORT_STRING); + sort($result, SORT_STRING); + $this->assertEquals($expected,$result); + + + + $this->getRoleLock('editor')->allow('make', 'events', '4'); + $this->getCallerLock()->deny('make', 'events', '4'); + + $this->assertTrue($this->getCallerLock()->can('make', 'events', '1')); + $this->assertTrue($this->getCallerLock()->can('make', 'events', '2')); + $this->assertTrue($this->getCallerLock()->can('make', 'events', '3')); + $this->assertTrue($this->getCallerLock()->cannot('make', 'events', '4')); + $expected = ['1', '2', '3']; + $result = $this->getCallerLock()->allowed('make', 'events'); + sort($expected, SORT_STRING); + sort($result, SORT_STRING); + $this->assertEquals($expected,$result); + + $this->assertTrue($this->getRoleLock('editor')->can('make', 'events', '1')); + $this->assertTrue($this->getRoleLock('editor')->can('make', 'events', '2')); + $this->assertTrue($this->getRoleLock('editor')->cannot('make', 'events', '3')); + $this->assertTrue($this->getRoleLock('editor')->can('make', 'events', '4')); + $expected = ['1', '2', '4']; + $result = $this->getRoleLock('editor')->allowed('make', 'events'); + sort($expected, SORT_STRING); + sort($result, SORT_STRING); + $this->assertEquals($expected,$result); + + + + $this->getRoleLock('editor')->deny('make', 'events', '5'); + $this->getCallerLock()->allow('make', 'events', '5'); + + $this->assertTrue($this->getCallerLock()->can('make', 'events', '1')); + $this->assertTrue($this->getCallerLock()->can('make', 'events', '2')); + $this->assertTrue($this->getCallerLock()->can('make', 'events', '3')); + $this->assertTrue($this->getCallerLock()->cannot('make', 'events', '4')); + $this->assertTrue($this->getCallerLock()->can('make', 'events', '5')); + $expected = ['1', '2', '3', '5']; + $result = $this->getCallerLock()->allowed('make', 'events'); + sort($expected, SORT_STRING); + sort($result, SORT_STRING); + $this->assertEquals($expected,$result); + + $this->assertTrue($this->getRoleLock('editor')->can('make', 'events', '1')); + $this->assertTrue($this->getRoleLock('editor')->can('make', 'events', '2')); + $this->assertTrue($this->getRoleLock('editor')->cannot('make', 'events', '3')); + $this->assertTrue($this->getRoleLock('editor')->can('make', 'events', '4')); + $this->assertTrue($this->getRoleLock('editor')->cannot('make', 'events', '5')); + $expected = ['1', '2', '4']; + $result = $this->getRoleLock('editor')->allowed('make', 'events'); + sort($expected, SORT_STRING); + sort($result, SORT_STRING); + $this->assertEquals($expected,$result); + + + $this->getRoleLock('editor')->deny('make', 'events', '6'); + $this->getCallerLock()->deny('make', 'events', '6'); + + $this->assertTrue($this->getCallerLock()->can('make', 'events', '1')); + $this->assertTrue($this->getCallerLock()->can('make', 'events', '2')); + $this->assertTrue($this->getCallerLock()->can('make', 'events', '3')); + $this->assertTrue($this->getCallerLock()->cannot('make', 'events', '4')); + $this->assertTrue($this->getCallerLock()->can('make', 'events', '5')); + $this->assertTrue($this->getCallerLock()->cannot('make', 'events', '6')); + $expected = ['1', '2', '3', '5']; + $result = $this->getCallerLock()->allowed('make', 'events'); + sort($expected, SORT_STRING); + sort($result, SORT_STRING); + $this->assertEquals($expected,$result); + + $this->assertTrue($this->getRoleLock('editor')->can('make', 'events', '1')); + $this->assertTrue($this->getRoleLock('editor')->can('make', 'events', '2')); + $this->assertTrue($this->getRoleLock('editor')->cannot('make', 'events', '3')); + $this->assertTrue($this->getRoleLock('editor')->can('make', 'events', '4')); + $this->assertTrue($this->getRoleLock('editor')->cannot('make', 'events', '5')); + $this->assertTrue($this->getRoleLock('editor')->cannot('make', 'events', '6')); + $expected = ['1', '2', '4']; + $result = $this->getRoleLock('editor')->allowed('make', 'events'); + sort($expected, SORT_STRING); + sort($result, SORT_STRING); + $this->assertEquals($expected,$result); + + + $this->getRoleLock('editor')->allow('make', 'events', '7'); + $this->getCallerLock()->allow('make', 'events', '7'); + + $this->assertTrue($this->getCallerLock()->can('make', 'events', '1')); + $this->assertTrue($this->getCallerLock()->can('make', 'events', '2')); + $this->assertTrue($this->getCallerLock()->can('make', 'events', '3')); + $this->assertTrue($this->getCallerLock()->cannot('make', 'events', '4')); + $this->assertTrue($this->getCallerLock()->can('make', 'events', '5')); + $this->assertTrue($this->getCallerLock()->cannot('make', 'events', '6')); + $this->assertTrue($this->getCallerLock()->can('make', 'events', '7')); + $expected = ['1', '2', '3', '5', '7']; + $result = $this->getCallerLock()->allowed('make', 'events'); + sort($expected, SORT_STRING); + sort($result, SORT_STRING); + $this->assertEquals($expected,$result); + + $this->assertTrue($this->getRoleLock('editor')->can('make', 'events', '1')); + $this->assertTrue($this->getRoleLock('editor')->can('make', 'events', '2')); + $this->assertTrue($this->getRoleLock('editor')->cannot('make', 'events', '3')); + $this->assertTrue($this->getRoleLock('editor')->can('make', 'events', '4')); + $this->assertTrue($this->getRoleLock('editor')->cannot('make', 'events', '5')); + $this->assertTrue($this->getRoleLock('editor')->cannot('make', 'events', '6')); + $this->assertTrue($this->getRoleLock('editor')->can('make', 'events', '7')); + $expected = ['1', '2', '4', '7']; + $result = $this->getRoleLock('editor')->allowed('make', 'events'); + sort($expected, SORT_STRING); + sort($result, SORT_STRING); + $this->assertEquals($expected,$result); + + + $this->getRoleLock('publisher')->allow('make', 'events', '8'); + + + $this->assertTrue($this->getCallerLock()->can('make', 'events', '1')); + $this->assertTrue($this->getCallerLock()->can('make', 'events', '2')); + $this->assertTrue($this->getCallerLock()->can('make', 'events', '3')); + $this->assertTrue($this->getCallerLock()->cannot('make', 'events', '4')); + $this->assertTrue($this->getCallerLock()->can('make', 'events', '5')); + $this->assertTrue($this->getCallerLock()->cannot('make', 'events', '6')); + $this->assertTrue($this->getCallerLock()->can('make', 'events', '7')); + $this->assertTrue($this->getCallerLock()->can('make', 'events', '8')); + $expected = ['1', '2', '3', '5', '7', '8']; + $result = $this->getCallerLock()->allowed('make', 'events'); + sort($expected, SORT_STRING); + sort($result, SORT_STRING); + $this->assertEquals($expected,$result); + + $this->assertTrue($this->getRoleLock('editor')->can('make', 'events', '1')); + $this->assertTrue($this->getRoleLock('editor')->can('make', 'events', '2')); + $this->assertTrue($this->getRoleLock('editor')->cannot('make', 'events', '3')); + $this->assertTrue($this->getRoleLock('editor')->can('make', 'events', '4')); + $this->assertTrue($this->getRoleLock('editor')->cannot('make', 'events', '5')); + $this->assertTrue($this->getRoleLock('editor')->cannot('make', 'events', '6')); + $this->assertTrue($this->getRoleLock('editor')->can('make', 'events', '7')); + $this->assertTrue($this->getRoleLock('editor')->cannot('make', 'events', '8')); + $expected = ['1', '2', '4', '7']; + $result = $this->getRoleLock('editor')->allowed('make', 'events'); + sort($expected, SORT_STRING); + sort($result, SORT_STRING); + $this->assertEquals($expected,$result); + + $this->assertTrue($this->getRoleLock('publisher')->cannot('make', 'events', '1')); + $this->assertTrue($this->getRoleLock('publisher')->cannot('make', 'events', '2')); + $this->assertTrue($this->getRoleLock('publisher')->cannot('make', 'events', '3')); + $this->assertTrue($this->getRoleLock('publisher')->cannot('make', 'events', '4')); + $this->assertTrue($this->getRoleLock('publisher')->cannot('make', 'events', '5')); + $this->assertTrue($this->getRoleLock('publisher')->cannot('make', 'events', '6')); + $this->assertTrue($this->getRoleLock('publisher')->cannot('make', 'events', '7')); + $this->assertTrue($this->getRoleLock('publisher')->can('make', 'events', '8')); + $expected = ['8']; + $result = $this->getRoleLock('publisher')->allowed('make', 'events'); + sort($expected, SORT_STRING); + sort($result, SORT_STRING); + $this->assertEquals($expected,$result); + + + $this->getRoleLock('editor')->allow('make', 'events', '9'); + $this->getRoleLock('publisher')->deny('make', 'events', '9'); + + + $this->assertTrue($this->getCallerLock()->can('make', 'events', '1')); + $this->assertTrue($this->getCallerLock()->can('make', 'events', '2')); + $this->assertTrue($this->getCallerLock()->can('make', 'events', '3')); + $this->assertTrue($this->getCallerLock()->cannot('make', 'events', '4')); + $this->assertTrue($this->getCallerLock()->can('make', 'events', '5')); + $this->assertTrue($this->getCallerLock()->cannot('make', 'events', '6')); + $this->assertTrue($this->getCallerLock()->can('make', 'events', '7')); + $this->assertTrue($this->getCallerLock()->can('make', 'events', '8')); + $this->assertTrue($this->getCallerLock()->can('make', 'events', '9')); + $expected = ['1', '2', '3', '5', '7', '8', '9']; + $result = $this->getCallerLock()->allowed('make', 'events'); + sort($expected, SORT_STRING); + sort($result, SORT_STRING); + $this->assertEquals($expected,$result); + + $this->assertTrue($this->getRoleLock('editor')->can('make', 'events', '1')); + $this->assertTrue($this->getRoleLock('editor')->can('make', 'events', '2')); + $this->assertTrue($this->getRoleLock('editor')->cannot('make', 'events', '3')); + $this->assertTrue($this->getRoleLock('editor')->can('make', 'events', '4')); + $this->assertTrue($this->getRoleLock('editor')->cannot('make', 'events', '5')); + $this->assertTrue($this->getRoleLock('editor')->cannot('make', 'events', '6')); + $this->assertTrue($this->getRoleLock('editor')->can('make', 'events', '7')); + $this->assertTrue($this->getRoleLock('editor')->cannot('make', 'events', '8')); + $this->assertTrue($this->getRoleLock('editor')->can('make', 'events', '9')); + $expected = ['1', '2', '4', '7', '9']; + $result = $this->getRoleLock('editor')->allowed('make', 'events'); + sort($expected, SORT_STRING); + sort($result, SORT_STRING); + $this->assertEquals($expected,$result); + + $this->assertTrue($this->getRoleLock('publisher')->cannot('make', 'events', '1')); + $this->assertTrue($this->getRoleLock('publisher')->cannot('make', 'events', '2')); + $this->assertTrue($this->getRoleLock('publisher')->cannot('make', 'events', '3')); + $this->assertTrue($this->getRoleLock('publisher')->cannot('make', 'events', '4')); + $this->assertTrue($this->getRoleLock('publisher')->cannot('make', 'events', '5')); + $this->assertTrue($this->getRoleLock('publisher')->cannot('make', 'events', '6')); + $this->assertTrue($this->getRoleLock('publisher')->cannot('make', 'events', '7')); + $this->assertTrue($this->getRoleLock('publisher')->can('make', 'events', '8')); + $this->assertTrue($this->getRoleLock('publisher')->cannot('make', 'events', '9')); + $expected = ['8']; + $result = $this->getRoleLock('publisher')->allowed('make', 'events'); + sort($expected, SORT_STRING); + sort($result, SORT_STRING); + $this->assertEquals($expected,$result); + + + $this->getRoleLock('editor')->deny('make', 'events', '10'); + $this->getRoleLock('publisher')->allow('make', 'events', '10'); + + + $this->assertTrue($this->getCallerLock()->can('make', 'events', '1')); + $this->assertTrue($this->getCallerLock()->can('make', 'events', '2')); + $this->assertTrue($this->getCallerLock()->can('make', 'events', '3')); + $this->assertTrue($this->getCallerLock()->cannot('make', 'events', '4')); + $this->assertTrue($this->getCallerLock()->can('make', 'events', '5')); + $this->assertTrue($this->getCallerLock()->cannot('make', 'events', '6')); + $this->assertTrue($this->getCallerLock()->can('make', 'events', '7')); + $this->assertTrue($this->getCallerLock()->can('make', 'events', '8')); + $this->assertTrue($this->getCallerLock()->can('make', 'events', '9')); + $this->assertTrue($this->getCallerLock()->can('make', 'events', '10')); + $expected = ['1', '2', '3', '5', '7', '8', '9', '10']; + $result = $this->getCallerLock()->allowed('make', 'events'); + sort($expected, SORT_STRING); + sort($result, SORT_STRING); + $this->assertEquals($expected,$result); + + $this->assertTrue($this->getRoleLock('editor')->can('make', 'events', '1')); + $this->assertTrue($this->getRoleLock('editor')->can('make', 'events', '2')); + $this->assertTrue($this->getRoleLock('editor')->cannot('make', 'events', '3')); + $this->assertTrue($this->getRoleLock('editor')->can('make', 'events', '4')); + $this->assertTrue($this->getRoleLock('editor')->cannot('make', 'events', '5')); + $this->assertTrue($this->getRoleLock('editor')->cannot('make', 'events', '6')); + $this->assertTrue($this->getRoleLock('editor')->can('make', 'events', '7')); + $this->assertTrue($this->getRoleLock('editor')->cannot('make', 'events', '8')); + $this->assertTrue($this->getRoleLock('editor')->can('make', 'events', '9')); + $this->assertTrue($this->getRoleLock('editor')->cannot('make', 'events', '10')); + $expected = ['1', '2', '4', '7', '9']; + $result = $this->getRoleLock('editor')->allowed('make', 'events'); + sort($expected, SORT_STRING); + sort($result, SORT_STRING); + $this->assertEquals($expected,$result); + + $this->assertTrue($this->getRoleLock('publisher')->cannot('make', 'events', '1')); + $this->assertTrue($this->getRoleLock('publisher')->cannot('make', 'events', '2')); + $this->assertTrue($this->getRoleLock('publisher')->cannot('make', 'events', '3')); + $this->assertTrue($this->getRoleLock('publisher')->cannot('make', 'events', '4')); + $this->assertTrue($this->getRoleLock('publisher')->cannot('make', 'events', '5')); + $this->assertTrue($this->getRoleLock('publisher')->cannot('make', 'events', '6')); + $this->assertTrue($this->getRoleLock('publisher')->cannot('make', 'events', '7')); + $this->assertTrue($this->getRoleLock('publisher')->can('make', 'events', '8')); + $this->assertTrue($this->getRoleLock('publisher')->cannot('make', 'events', '9')); + $this->assertTrue($this->getRoleLock('publisher')->can('make', 'events', '10')); + $expected = ['8', '10']; + $result = $this->getRoleLock('publisher')->allowed('make', 'events'); + sort($expected, SORT_STRING); + sort($result, SORT_STRING); + $this->assertEquals($expected,$result); + } + + /** + * @test + */ + final function it_can_return_denied_resource_ids_with_inheritance() { + $this->getRoleLock('editor')->allow('make', 'events', '1'); + $this->getRoleLock('editor')->deny('make', 'events', '2'); + $this->getCallerLock()->allow('make', 'events', '3'); + $this->getCallerLock()->deny('make', 'events', '4'); + + $this->assertTrue($this->getCallerLock()->can('make', 'events', '1')); + $this->assertTrue($this->getCallerLock()->cannot('make', 'events', '2')); + $this->assertTrue($this->getCallerLock()->can('make', 'events', '3')); + $this->assertTrue($this->getCallerLock()->cannot('make', 'events', '4')); + $expected = ['2', '4']; + $result = $this->getCallerLock()->denied('make', 'events'); + sort($expected, SORT_STRING); + sort($result, SORT_STRING); + $this->assertEquals($expected,$result); + + $this->assertTrue($this->getRoleLock('editor')->can('make', 'events', '1')); + $this->assertTrue($this->getRoleLock('editor')->cannot('make', 'events', '2')); + $this->assertTrue($this->getRoleLock('editor')->cannot('make', 'events', '3')); // Editor as no Permission on that (no Privilege neither Restriction) => won't be listed in denied + $this->assertTrue($this->getRoleLock('editor')->cannot('make', 'events', '4')); // Editor as no Permission on that (no Privilege neither Restriction) => won't be listed in denied + $expected = ['2']; + $result = $this->getRoleLock('editor')->denied('make', 'events'); + sort($expected, SORT_STRING); + sort($result, SORT_STRING); + $this->assertEquals($expected,$result); + + + $this->getRoleLock('editor')->deny('make', 'events', '5'); + $this->getCallerLock()->allow('make', 'events', '5'); + + + $this->assertTrue($this->getCallerLock()->can('make', 'events', '1')); + $this->assertTrue($this->getCallerLock()->cannot('make', 'events', '2')); + $this->assertTrue($this->getCallerLock()->can('make', 'events', '3')); + $this->assertTrue($this->getCallerLock()->cannot('make', 'events', '4')); + $this->assertTrue($this->getCallerLock()->can('make', 'events', '5')); + $expected = ['2', '4']; + $result = $this->getCallerLock()->denied('make', 'events'); + sort($expected, SORT_STRING); + sort($result, SORT_STRING); + $this->assertEquals($expected,$result); + + $this->assertTrue($this->getRoleLock('editor')->can('make', 'events', '1')); + $this->assertTrue($this->getRoleLock('editor')->cannot('make', 'events', '2')); + $this->assertTrue($this->getRoleLock('editor')->cannot('make', 'events', '3')); // Editor as no Permission on that (no Privilege neither Restriction) => won't be listed in denied + $this->assertTrue($this->getRoleLock('editor')->cannot('make', 'events', '4')); // Editor as no Permission on that (no Privilege neither Restriction) => won't be listed in denied + $this->assertTrue($this->getRoleLock('editor')->cannot('make', 'events', '5')); + $expected = ['2', '5']; + $result = $this->getRoleLock('editor')->denied('make', 'events'); + sort($expected, SORT_STRING); + sort($result, SORT_STRING); + $this->assertEquals($expected,$result); + + + $this->getRoleLock('editor')->allow('make', 'events', '6'); + $this->getCallerLock()->deny('make', 'events', '6'); + + + $this->assertTrue($this->getCallerLock()->can('make', 'events', '1')); + $this->assertTrue($this->getCallerLock()->cannot('make', 'events', '2')); + $this->assertTrue($this->getCallerLock()->can('make', 'events', '3')); + $this->assertTrue($this->getCallerLock()->cannot('make', 'events', '4')); + $this->assertTrue($this->getCallerLock()->can('make', 'events', '5')); + $this->assertTrue($this->getCallerLock()->cannot('make', 'events', '6')); + $expected = ['2', '4', '6']; + $result = $this->getCallerLock()->denied('make', 'events'); + sort($expected, SORT_STRING); + sort($result, SORT_STRING); + $this->assertEquals($expected,$result); + + $this->assertTrue($this->getRoleLock('editor')->can('make', 'events', '1')); + $this->assertTrue($this->getRoleLock('editor')->cannot('make', 'events', '2')); + $this->assertTrue($this->getRoleLock('editor')->cannot('make', 'events', '3')); // Editor as no Permission on that (no Privilege neither Restriction) => won't be listed in denied + $this->assertTrue($this->getRoleLock('editor')->cannot('make', 'events', '4')); // Editor as no Permission on that (no Privilege neither Restriction) => won't be listed in denied + $this->assertTrue($this->getRoleLock('editor')->cannot('make', 'events', '5')); + $this->assertTrue($this->getRoleLock('editor')->can('make', 'events', '6')); + $expected = ['2', '5']; + $result = $this->getRoleLock('editor')->denied('make', 'events'); + sort($expected, SORT_STRING); + sort($result, SORT_STRING); + $this->assertEquals($expected,$result); + + + $this->getRoleLock('editor')->allow('make', 'events', '7'); + $this->getCallerLock()->allow('make', 'events', '7'); + + + $this->assertTrue($this->getCallerLock()->can('make', 'events', '1')); + $this->assertTrue($this->getCallerLock()->cannot('make', 'events', '2')); + $this->assertTrue($this->getCallerLock()->can('make', 'events', '3')); + $this->assertTrue($this->getCallerLock()->cannot('make', 'events', '4')); + $this->assertTrue($this->getCallerLock()->can('make', 'events', '5')); + $this->assertTrue($this->getCallerLock()->cannot('make', 'events', '6')); + $this->assertTrue($this->getCallerLock()->can('make', 'events', '7')); + $expected = ['2', '4', '6']; + $result = $this->getCallerLock()->denied('make', 'events'); + sort($expected, SORT_STRING); + sort($result, SORT_STRING); + $this->assertEquals($expected,$result); + + $this->assertTrue($this->getRoleLock('editor')->can('make', 'events', '1')); + $this->assertTrue($this->getRoleLock('editor')->cannot('make', 'events', '2')); + $this->assertTrue($this->getRoleLock('editor')->cannot('make', 'events', '3')); // Editor as no Permission on that (no Privilege neither Restriction) => won't be listed in denied + $this->assertTrue($this->getRoleLock('editor')->cannot('make', 'events', '4')); // Editor as no Permission on that (no Privilege neither Restriction) => won't be listed in denied + $this->assertTrue($this->getRoleLock('editor')->cannot('make', 'events', '5')); + $this->assertTrue($this->getRoleLock('editor')->can('make', 'events', '6')); + $this->assertTrue($this->getRoleLock('editor')->can('make', 'events', '7')); + $expected = ['2', '5']; + $result = $this->getRoleLock('editor')->denied('make', 'events'); + sort($expected, SORT_STRING); + sort($result, SORT_STRING); + $this->assertEquals($expected,$result); + + + $this->getRoleLock('editor')->deny('make', 'events', '8'); + $this->getCallerLock()->deny('make', 'events', '8'); + + + $this->assertTrue($this->getCallerLock()->can('make', 'events', '1')); + $this->assertTrue($this->getCallerLock()->cannot('make', 'events', '2')); + $this->assertTrue($this->getCallerLock()->can('make', 'events', '3')); + $this->assertTrue($this->getCallerLock()->cannot('make', 'events', '4')); + $this->assertTrue($this->getCallerLock()->can('make', 'events', '5')); + $this->assertTrue($this->getCallerLock()->cannot('make', 'events', '6')); + $this->assertTrue($this->getCallerLock()->can('make', 'events', '7')); + $this->assertTrue($this->getCallerLock()->cannot('make', 'events', '8')); + $expected = ['2', '4', '6', '8']; + $result = $this->getCallerLock()->denied('make', 'events'); + sort($expected, SORT_STRING); + sort($result, SORT_STRING); + $this->assertEquals($expected,$result); + + $this->assertTrue($this->getRoleLock('editor')->can('make', 'events', '1')); + $this->assertTrue($this->getRoleLock('editor')->cannot('make', 'events', '2')); + $this->assertTrue($this->getRoleLock('editor')->cannot('make', 'events', '3')); // Editor as no Permission on that (no Privilege neither Restriction) => won't be listed in denied + $this->assertTrue($this->getRoleLock('editor')->cannot('make', 'events', '4')); // Editor as no Permission on that (no Privilege neither Restriction) => won't be listed in denied + $this->assertTrue($this->getRoleLock('editor')->cannot('make', 'events', '5')); + $this->assertTrue($this->getRoleLock('editor')->can('make', 'events', '6')); + $this->assertTrue($this->getRoleLock('editor')->can('make', 'events', '7')); + $this->assertTrue($this->getRoleLock('editor')->cannot('make', 'events', '8')); + $expected = ['2', '5', '8']; + $result = $this->getRoleLock('editor')->denied('make', 'events'); + sort($expected, SORT_STRING); + sort($result, SORT_STRING); + $this->assertEquals($expected,$result); + + + $this->getRoleLock('publisher')->deny('make', 'events', '9'); + + + $this->assertTrue($this->getCallerLock()->can('make', 'events', '1')); + $this->assertTrue($this->getCallerLock()->cannot('make', 'events', '2')); + $this->assertTrue($this->getCallerLock()->can('make', 'events', '3')); + $this->assertTrue($this->getCallerLock()->cannot('make', 'events', '4')); + $this->assertTrue($this->getCallerLock()->can('make', 'events', '5')); + $this->assertTrue($this->getCallerLock()->cannot('make', 'events', '6')); + $this->assertTrue($this->getCallerLock()->can('make', 'events', '7')); + $this->assertTrue($this->getCallerLock()->cannot('make', 'events', '8')); + $this->assertTrue($this->getCallerLock()->cannot('make', 'events', '9')); + $expected = ['2', '4', '6', '8', '9']; + $result = $this->getCallerLock()->denied('make', 'events'); + sort($expected, SORT_STRING); + sort($result, SORT_STRING); + $this->assertEquals($expected,$result); + + $this->assertTrue($this->getRoleLock('editor')->can('make', 'events', '1')); + $this->assertTrue($this->getRoleLock('editor')->cannot('make', 'events', '2')); + $this->assertTrue($this->getRoleLock('editor')->cannot('make', 'events', '3')); // Editor as no Permission on that (no Privilege neither Restriction) => won't be listed in denied + $this->assertTrue($this->getRoleLock('editor')->cannot('make', 'events', '4')); // Editor as no Permission on that (no Privilege neither Restriction) => won't be listed in denied + $this->assertTrue($this->getRoleLock('editor')->cannot('make', 'events', '5')); + $this->assertTrue($this->getRoleLock('editor')->can('make', 'events', '6')); + $this->assertTrue($this->getRoleLock('editor')->can('make', 'events', '7')); + $this->assertTrue($this->getRoleLock('editor')->cannot('make', 'events', '8')); + $this->assertTrue($this->getRoleLock('editor')->cannot('make', 'events', '9')); // Editor as no Permission on that (no Privilege neither Restriction) => won't be listed in denied + $expected = ['2', '5', '8']; + $result = $this->getRoleLock('editor')->denied('make', 'events'); + sort($expected, SORT_STRING); + sort($result, SORT_STRING); + $this->assertEquals($expected,$result); + + $this->assertTrue($this->getRoleLock('Publisher')->cannot('make', 'events', '1')); // Publisher as no Permission on that (no Privilege neither Restriction) => won't be listed in denied + $this->assertTrue($this->getRoleLock('Publisher')->cannot('make', 'events', '2')); // Publisher as no Permission on that (no Privilege neither Restriction) => won't be listed in denied + $this->assertTrue($this->getRoleLock('Publisher')->cannot('make', 'events', '3')); // Publisher as no Permission on that (no Privilege neither Restriction) => won't be listed in denied + $this->assertTrue($this->getRoleLock('Publisher')->cannot('make', 'events', '4')); // Publisher as no Permission on that (no Privilege neither Restriction) => won't be listed in denied + $this->assertTrue($this->getRoleLock('Publisher')->cannot('make', 'events', '5')); // Publisher as no Permission on that (no Privilege neither Restriction) => won't be listed in denied + $this->assertTrue($this->getRoleLock('Publisher')->cannot('make', 'events', '6')); // Publisher as no Permission on that (no Privilege neither Restriction) => won't be listed in denied + $this->assertTrue($this->getRoleLock('Publisher')->cannot('make', 'events', '7')); // Publisher as no Permission on that (no Privilege neither Restriction) => won't be listed in denied + $this->assertTrue($this->getRoleLock('Publisher')->cannot('make', 'events', '8')); // Publisher as no Permission on that (no Privilege neither Restriction) => won't be listed in denied + $this->assertTrue($this->getRoleLock('Publisher')->cannot('make', 'events', '9')); + $expected = ['9']; + $result = $this->getRoleLock('Publisher')->denied('make', 'events'); + sort($expected, SORT_STRING); + sort($result, SORT_STRING); + $this->assertEquals($expected,$result); + + + $this->getRoleLock('editor')->allow('make', 'events', '10'); + $this->getRoleLock('publisher')->deny('make', 'events', '10'); + + + $this->assertTrue($this->getCallerLock()->can('make', 'events', '1')); + $this->assertTrue($this->getCallerLock()->cannot('make', 'events', '2')); + $this->assertTrue($this->getCallerLock()->can('make', 'events', '3')); + $this->assertTrue($this->getCallerLock()->cannot('make', 'events', '4')); + $this->assertTrue($this->getCallerLock()->can('make', 'events', '5')); + $this->assertTrue($this->getCallerLock()->cannot('make', 'events', '6')); + $this->assertTrue($this->getCallerLock()->can('make', 'events', '7')); + $this->assertTrue($this->getCallerLock()->cannot('make', 'events', '8')); + $this->assertTrue($this->getCallerLock()->cannot('make', 'events', '9')); + $this->assertTrue($this->getCallerLock()->can('make', 'events', '10')); + $expected = ['2', '4', '6', '8', '9']; + $result = $this->getCallerLock()->denied('make', 'events'); + sort($expected, SORT_STRING); + sort($result, SORT_STRING); + $this->assertEquals($expected,$result); + + $this->assertTrue($this->getRoleLock('editor')->can('make', 'events', '1')); + $this->assertTrue($this->getRoleLock('editor')->cannot('make', 'events', '2')); + $this->assertTrue($this->getRoleLock('editor')->cannot('make', 'events', '3')); // Editor as no Permission on that (no Privilege neither Restriction) => won't be listed in denied + $this->assertTrue($this->getRoleLock('editor')->cannot('make', 'events', '4')); // Editor as no Permission on that (no Privilege neither Restriction) => won't be listed in denied + $this->assertTrue($this->getRoleLock('editor')->cannot('make', 'events', '5')); + $this->assertTrue($this->getRoleLock('editor')->can('make', 'events', '6')); + $this->assertTrue($this->getRoleLock('editor')->can('make', 'events', '7')); + $this->assertTrue($this->getRoleLock('editor')->cannot('make', 'events', '8')); + $this->assertTrue($this->getRoleLock('editor')->cannot('make', 'events', '9')); // Editor as no Permission on that (no Privilege neither Restriction) => won't be listed in denied + $this->assertTrue($this->getRoleLock('editor')->can('make', 'events', '10')); + $expected = ['2', '5', '8']; + $result = $this->getRoleLock('editor')->denied('make', 'events'); + sort($expected, SORT_STRING); + sort($result, SORT_STRING); + $this->assertEquals($expected,$result); + + $this->assertTrue($this->getRoleLock('Publisher')->cannot('make', 'events', '1')); // Publisher as no Permission on that (no Privilege neither Restriction) => won't be listed in denied + $this->assertTrue($this->getRoleLock('Publisher')->cannot('make', 'events', '2')); // Publisher as no Permission on that (no Privilege neither Restriction) => won't be listed in denied + $this->assertTrue($this->getRoleLock('Publisher')->cannot('make', 'events', '3')); // Publisher as no Permission on that (no Privilege neither Restriction) => won't be listed in denied + $this->assertTrue($this->getRoleLock('Publisher')->cannot('make', 'events', '4')); // Publisher as no Permission on that (no Privilege neither Restriction) => won't be listed in denied + $this->assertTrue($this->getRoleLock('Publisher')->cannot('make', 'events', '5')); // Publisher as no Permission on that (no Privilege neither Restriction) => won't be listed in denied + $this->assertTrue($this->getRoleLock('Publisher')->cannot('make', 'events', '6')); // Publisher as no Permission on that (no Privilege neither Restriction) => won't be listed in denied + $this->assertTrue($this->getRoleLock('Publisher')->cannot('make', 'events', '7')); // Publisher as no Permission on that (no Privilege neither Restriction) => won't be listed in denied + $this->assertTrue($this->getRoleLock('Publisher')->cannot('make', 'events', '8')); // Publisher as no Permission on that (no Privilege neither Restriction) => won't be listed in denied + $this->assertTrue($this->getRoleLock('Publisher')->cannot('make', 'events', '9')); + $this->assertTrue($this->getRoleLock('Publisher')->cannot('make', 'events', '10')); + $expected = ['9', '10']; + $result = $this->getRoleLock('Publisher')->denied('make', 'events'); + sort($expected, SORT_STRING); + sort($result, SORT_STRING); + $this->assertEquals($expected,$result); + + + $this->getRoleLock('editor')->deny('make', 'events', '11'); + $this->getRoleLock('publisher')->allow('make', 'events', '11'); + + + $this->assertTrue($this->getCallerLock()->can('make', 'events', '1')); + $this->assertTrue($this->getCallerLock()->cannot('make', 'events', '2')); + $this->assertTrue($this->getCallerLock()->can('make', 'events', '3')); + $this->assertTrue($this->getCallerLock()->cannot('make', 'events', '4')); + $this->assertTrue($this->getCallerLock()->can('make', 'events', '5')); + $this->assertTrue($this->getCallerLock()->cannot('make', 'events', '6')); + $this->assertTrue($this->getCallerLock()->can('make', 'events', '7')); + $this->assertTrue($this->getCallerLock()->cannot('make', 'events', '8')); + $this->assertTrue($this->getCallerLock()->cannot('make', 'events', '9')); + $this->assertTrue($this->getCallerLock()->can('make', 'events', '10')); + $this->assertTrue($this->getCallerLock()->can('make', 'events', '11')); + $expected = ['2', '4', '6', '8', '9']; + $result = $this->getCallerLock()->denied('make', 'events'); + sort($expected, SORT_STRING); + sort($result, SORT_STRING); + $this->assertEquals($expected,$result); + + $this->assertTrue($this->getRoleLock('editor')->can('make', 'events', '1')); + $this->assertTrue($this->getRoleLock('editor')->cannot('make', 'events', '2')); + $this->assertTrue($this->getRoleLock('editor')->cannot('make', 'events', '3')); // Editor as no Permission on that (no Privilege neither Restriction) => won't be listed in denied + $this->assertTrue($this->getRoleLock('editor')->cannot('make', 'events', '4')); // Editor as no Permission on that (no Privilege neither Restriction) => won't be listed in denied + $this->assertTrue($this->getRoleLock('editor')->cannot('make', 'events', '5')); + $this->assertTrue($this->getRoleLock('editor')->can('make', 'events', '6')); + $this->assertTrue($this->getRoleLock('editor')->can('make', 'events', '7')); + $this->assertTrue($this->getRoleLock('editor')->cannot('make', 'events', '8')); + $this->assertTrue($this->getRoleLock('editor')->cannot('make', 'events', '9')); // Editor as no Permission on that (no Privilege neither Restriction) => won't be listed in denied + $this->assertTrue($this->getRoleLock('editor')->can('make', 'events', '10')); + $this->assertTrue($this->getRoleLock('editor')->cannot('make', 'events', '11')); + $expected = ['2', '5', '8', '11']; + $result = $this->getRoleLock('editor')->denied('make', 'events'); + sort($expected, SORT_STRING); + sort($result, SORT_STRING); + $this->assertEquals($expected,$result); + + $this->assertTrue($this->getRoleLock('Publisher')->cannot('make', 'events', '1')); // Publisher as no Permission on that (no Privilege neither Restriction) => won't be listed in denied + $this->assertTrue($this->getRoleLock('Publisher')->cannot('make', 'events', '2')); // Publisher as no Permission on that (no Privilege neither Restriction) => won't be listed in denied + $this->assertTrue($this->getRoleLock('Publisher')->cannot('make', 'events', '3')); // Publisher as no Permission on that (no Privilege neither Restriction) => won't be listed in denied + $this->assertTrue($this->getRoleLock('Publisher')->cannot('make', 'events', '4')); // Publisher as no Permission on that (no Privilege neither Restriction) => won't be listed in denied + $this->assertTrue($this->getRoleLock('Publisher')->cannot('make', 'events', '5')); // Publisher as no Permission on that (no Privilege neither Restriction) => won't be listed in denied + $this->assertTrue($this->getRoleLock('Publisher')->cannot('make', 'events', '6')); // Publisher as no Permission on that (no Privilege neither Restriction) => won't be listed in denied + $this->assertTrue($this->getRoleLock('Publisher')->cannot('make', 'events', '7')); // Publisher as no Permission on that (no Privilege neither Restriction) => won't be listed in denied + $this->assertTrue($this->getRoleLock('Publisher')->cannot('make', 'events', '8')); // Publisher as no Permission on that (no Privilege neither Restriction) => won't be listed in denied + $this->assertTrue($this->getRoleLock('Publisher')->cannot('make', 'events', '9')); + $this->assertTrue($this->getRoleLock('Publisher')->cannot('make', 'events', '10')); + $this->assertTrue($this->getRoleLock('Publisher')->can('make', 'events', '11')); + $expected = ['9', '10']; + $result = $this->getRoleLock('Publisher')->denied('make', 'events'); + sort($expected, SORT_STRING); + sort($result, SORT_STRING); + $this->assertEquals($expected,$result); + + } + + /** + * @test + */ + final function it_can_succeed_or_failed_on_explicit_call_ALL(){ + $this->getCallerLock()->allow('eat'); + $this->getCallerLock()->allow('sleep'); + $this->getRoleLock('editor')->allow('run'); + + $this->assertTrue($this->getCallerLock()->can('eat')); + $this->assertTrue($this->getCallerLock()->canExplicitly('eat')); + $this->assertFalse($this->getRoleLock('editor')->can('eat')); + $this->assertTrue($this->getRoleLock('editor')->cannot('eat')); + $this->assertFalse($this->getRoleLock('editor')->canExplicitly('eat')); + $this->assertFalse($this->getRoleLock('editor')->cannotExplicitly('eat')); + $this->assertTrue($this->getCallerLock()->can('run')); + $this->assertFalse($this->getCallerLock()->canExplicitly('run')); + $this->assertTrue($this->getRoleLock('editor')->can('run')); + $this->assertFalse($this->getRoleLock('editor')->cannot('run')); + $this->assertTrue($this->getRoleLock('editor')->canExplicitly('run')); + $this->assertFalse($this->getRoleLock('editor')->cannotExplicitly('run')); + $this->assertTrue($this->getCallerLock()->can('sleep')); + $this->assertTrue($this->getCallerLock()->canExplicitly('sleep')); + $this->assertFalse($this->getCallerLock()->cannotExplicitly('sleep')); + + + $this->getCallerLock()->deny('standup'); + $this->getRoleLock('editor')->deny('jump'); + + $this->assertFalse($this->getCallerLock()->can('standup')); + $this->assertTrue($this->getCallerLock()->cannot('standup')); + $this->assertFalse($this->getCallerLock()->canExplicitly('standup')); + $this->assertTrue($this->getCallerLock()->cannotExplicitly('standup')); + $this->assertFalse($this->getRoleLock('editor')->can('standup')); + $this->assertTrue($this->getRoleLock('editor')->cannot('standup')); + $this->assertFalse($this->getRoleLock('editor')->canExplicitly('standup')); + $this->assertFalse($this->getRoleLock('editor')->cannotExplicitly('standup')); + + $this->assertFalse($this->getCallerLock()->can('jump')); + $this->assertTrue($this->getCallerLock()->cannot('jump')); + $this->assertFalse($this->getCallerLock()->canExplicitly('jump')); + $this->assertFalse($this->getCallerLock()->cannotExplicitly('jump')); + $this->assertFalse($this->getRoleLock('editor')->can('jump')); + $this->assertTrue($this->getRoleLock('editor')->cannot('jump')); + $this->assertFalse($this->getRoleLock('editor')->canExplicitly('jump')); + $this->assertTrue($this->getRoleLock('editor')->cannotExplicitly('jump')); + + $this->assertFalse($this->getCallerLock()->can('drive')); + $this->assertTrue($this->getCallerLock()->cannot('drive')); + $this->assertFalse($this->getCallerLock()->canExplicitly('drive')); + $this->assertFalse($this->getCallerLock()->cannotExplicitly('drive')); + + + } + + /** + * @test + */ + final function it_can_succeed_or_failed_on_explicit_call_ALL_RESOURCE_TYPE(){ + $this->getCallerLock()->allow('speak', 'teacher'); + $this->getCallerLock()->allow('talk', 'teacher'); + $this->getRoleLock('editor')->allow('move', 'teacher'); + + $this->assertTrue($this->getCallerLock()->can('speak', 'teacher')); + $this->assertTrue($this->getCallerLock()->canExplicitly('speak', 'teacher')); + $this->assertFalse($this->getRoleLock('editor')->can('speak', 'teacher')); + $this->assertTrue($this->getRoleLock('editor')->cannot('speak', 'teacher')); + $this->assertFalse($this->getRoleLock('editor')->canExplicitly('speak', 'teacher')); + $this->assertFalse($this->getRoleLock('editor')->cannotExplicitly('speak', 'teacher')); + $this->assertTrue($this->getCallerLock()->can('move', 'teacher')); + $this->assertFalse($this->getCallerLock()->canExplicitly('move', 'teacher')); + $this->assertTrue($this->getRoleLock('editor')->can('move', 'teacher')); + $this->assertFalse($this->getRoleLock('editor')->cannot('move', 'teacher')); + $this->assertTrue($this->getRoleLock('editor')->canExplicitly('move', 'teacher')); + $this->assertFalse($this->getRoleLock('editor')->cannotExplicitly('move', 'teacher')); + $this->assertTrue($this->getCallerLock()->can('talk', 'teacher')); + $this->assertTrue($this->getCallerLock()->canExplicitly('talk', 'teacher')); + $this->assertFalse($this->getCallerLock()->cannotExplicitly('talk', 'teacher')); + + + $this->assertFalse($this->getCallerLock()->can('speak', 'student')); + $this->assertTrue($this->getCallerLock()->cannot('speak', 'student')); + $this->assertFalse($this->getCallerLock()->canExplicitly('speak', 'student')); + $this->assertFalse($this->getCallerLock()->cannotExplicitly('speak', 'student')); + $this->assertFalse($this->getRoleLock('editor')->can('speak', 'student')); + $this->assertTrue($this->getRoleLock('editor')->cannot('speak', 'student')); + $this->assertFalse($this->getRoleLock('editor')->canExplicitly('speak', 'student')); + $this->assertFalse($this->getRoleLock('editor')->cannotExplicitly('speak', 'student')); + + + $this->getCallerLock()->deny('climb', 'teacher'); + $this->getRoleLock('editor')->deny('swim', 'teacher'); + + $this->assertFalse($this->getCallerLock()->can('climb', 'teacher')); + $this->assertTrue($this->getCallerLock()->cannot('climb', 'teacher')); + $this->assertFalse($this->getCallerLock()->canExplicitly('climb', 'teacher')); + $this->assertTrue($this->getCallerLock()->cannotExplicitly('climb', 'teacher')); + $this->assertFalse($this->getRoleLock('editor')->can('climb', 'teacher')); + $this->assertTrue($this->getRoleLock('editor')->cannot('climb', 'teacher')); + $this->assertFalse($this->getRoleLock('editor')->canExplicitly('climb', 'teacher')); + $this->assertFalse($this->getRoleLock('editor')->cannotExplicitly('climb', 'teacher')); + + $this->assertFalse($this->getCallerLock()->can('swim', 'teacher')); + $this->assertTrue($this->getCallerLock()->cannot('swim', 'teacher')); + $this->assertFalse($this->getCallerLock()->canExplicitly('swim', 'teacher')); + $this->assertFalse($this->getCallerLock()->cannotExplicitly('swim', 'teacher')); + $this->assertFalse($this->getRoleLock('editor')->can('swim', 'teacher')); + $this->assertTrue($this->getRoleLock('editor')->cannot('swim', 'teacher')); + $this->assertFalse($this->getRoleLock('editor')->canExplicitly('swim', 'teacher')); + $this->assertTrue($this->getRoleLock('editor')->cannotExplicitly('swim', 'teacher')); + + + $this->assertFalse($this->getCallerLock()->can('climb', 'student')); + $this->assertTrue($this->getCallerLock()->cannot('climb', 'student')); + $this->assertFalse($this->getCallerLock()->canExplicitly('climb', 'student')); + $this->assertFalse($this->getCallerLock()->cannotExplicitly('climb', 'student')); + $this->assertFalse($this->getRoleLock('editor')->can('swim', 'student')); + $this->assertTrue($this->getRoleLock('editor')->cannot('swim', 'student')); + $this->assertFalse($this->getRoleLock('editor')->canExplicitly('swim', 'student')); + $this->assertFalse($this->getRoleLock('editor')->cannotExplicitly('swim', 'student')); + } + + /** + * @test + */ + final function it_can_succeed_or_failed_on_explicit_call_ALL_RESOURCE_TYPE_AND_ID(){ + $this->getCallerLock()->allow('play', 'balloon', '3'); + $this->assertTrue($this->getCallerLock()->can('play', 'balloon', '3')); + $this->assertFalse($this->getCallerLock()->cannot('play', 'balloon', '3')); + $this->assertTrue($this->getCallerLock()->canExplicitly('play', 'balloon', '3')); + $this->assertFalse($this->getCallerLock()->cannotExplicitly('play', 'balloon', '3')); + $this->assertFalse($this->getCallerLock()->can('play', 'balloon', '1')); + $this->assertTrue($this->getCallerLock()->cannot('play', 'balloon', '1')); + $this->assertFalse($this->getCallerLock()->canExplicitly('play', 'balloon', '1')); + $this->assertFalse($this->getCallerLock()->cannotExplicitly('play', 'balloon', '1')); + + $this->getRoleLock('editor')->allow('play', 'balloon', '2'); + $this->assertTrue($this->getCallerLock()->can('play', 'balloon', '2')); + $this->assertFalse($this->getCallerLock()->cannot('play', 'balloon', '2')); + $this->assertFalse($this->getCallerLock()->canExplicitly('play', 'balloon', '2')); + $this->assertFalse($this->getCallerLock()->cannotExplicitly('play', 'balloon', '2')); + $this->assertTrue($this->getRoleLock('editor')->can('play', 'balloon', '2')); + $this->assertFalse($this->getRoleLock('editor')->cannot('play', 'balloon', '2')); + $this->assertTrue($this->getRoleLock('editor')->canExplicitly('play', 'balloon', '2')); + $this->assertFalse($this->getRoleLock('editor')->cannotExplicitly('play', 'balloon', '2')); + + $this->getCallerLock()->allow('play', 'pingpong'); + $this->assertTrue($this->getCallerLock()->can('play', 'pingpong', '2')); + $this->assertFalse($this->getCallerLock()->cannot('play', 'pingpong', '2')); + $this->assertFalse($this->getCallerLock()->canExplicitly('play', 'pingpong', '2')); + $this->assertFalse($this->getCallerLock()->cannotExplicitly('play', 'pingpong', '2')); + $this->assertFalse($this->getRoleLock('editor')->can('play', 'pingpong', '2')); + $this->assertTrue($this->getRoleLock('editor')->cannot('play', 'pingpong', '2')); + $this->assertFalse($this->getRoleLock('editor')->canExplicitly('play', 'pingpong', '2')); + $this->assertFalse($this->getRoleLock('editor')->cannotExplicitly('play', 'pingpong', '2')); + + $this->getRoleLock('editor')->allow('play', 'tennis'); + $this->assertTrue($this->getCallerLock()->can('play', 'tennis', '2')); + $this->assertFalse($this->getCallerLock()->cannot('play', 'tennis', '2')); + $this->assertFalse($this->getCallerLock()->canExplicitly('play', 'tennis', '2')); + $this->assertFalse($this->getCallerLock()->cannotExplicitly('play', 'tennis', '2')); + $this->assertTrue($this->getRoleLock('editor')->can('play', 'tennis', '2')); + $this->assertFalse($this->getRoleLock('editor')->cannot('play', 'tennis', '2')); + $this->assertFalse($this->getRoleLock('editor')->canExplicitly('play', 'tennis', '2')); + $this->assertFalse($this->getRoleLock('editor')->cannotExplicitly('play', 'tennis', '2')); + + $this->getCallerLock()->deny('play', 'basket', '3'); + $this->assertFalse($this->getCallerLock()->can('play', 'basket', '3')); + $this->assertTrue($this->getCallerLock()->cannot('play', 'basket', '3')); + $this->assertFalse($this->getCallerLock()->canExplicitly('play', 'basket', '3')); + $this->assertTrue($this->getCallerLock()->cannotExplicitly('play', 'basket', '3')); + $this->assertFalse($this->getCallerLock()->can('play', 'basket', '1')); + $this->assertTrue($this->getCallerLock()->cannot('play', 'basket', '1')); + $this->assertFalse($this->getCallerLock()->canExplicitly('play', 'basket', '1')); + $this->assertFalse($this->getCallerLock()->cannotExplicitly('play', 'basket', '1')); + + $this->getRoleLock('editor')->deny('play', 'basket', '2'); + $this->assertFalse($this->getCallerLock()->can('play', 'basket', '2')); + $this->assertTrue($this->getCallerLock()->cannot('play', 'basket', '2')); + $this->assertFalse($this->getCallerLock()->canExplicitly('play', 'basket', '2')); + $this->assertFalse($this->getCallerLock()->cannotExplicitly('play', 'basket', '2')); + $this->assertFalse($this->getRoleLock('editor')->can('play', 'basket', '2')); + $this->assertTrue($this->getRoleLock('editor')->cannot('play', 'basket', '2')); + $this->assertFalse($this->getRoleLock('editor')->canExplicitly('play', 'basket', '2')); + $this->assertTrue($this->getRoleLock('editor')->cannotExplicitly('play', 'basket', '2')); + + $this->getCallerLock()->deny('play', 'kayak'); + $this->assertFalse($this->getCallerLock()->can('play', 'kayak', '2')); + $this->assertTrue($this->getCallerLock()->cannot('play', 'kayak', '2')); + $this->assertFalse($this->getCallerLock()->canExplicitly('play', 'kayak', '2')); + $this->assertFalse($this->getCallerLock()->cannotExplicitly('play', 'kayak', '2')); + $this->assertFalse($this->getRoleLock('editor')->can('play', 'kayak', '2')); + $this->assertTrue($this->getRoleLock('editor')->cannot('play', 'kayak', '2')); + $this->assertFalse($this->getRoleLock('editor')->canExplicitly('play', 'kayak', '2')); + $this->assertFalse($this->getRoleLock('editor')->cannotExplicitly('play', 'kayak', '2')); + + $this->getRoleLock('editor')->deny('play', 'gameboy'); + $this->assertFalse($this->getCallerLock()->can('play', 'gameboy', '2')); + $this->assertTrue($this->getCallerLock()->cannot('play', 'gameboy', '2')); + $this->assertFalse($this->getCallerLock()->canExplicitly('play', 'gameboy', '2')); + $this->assertFalse($this->getCallerLock()->cannotExplicitly('play', 'gameboy', '2')); + $this->assertFalse($this->getRoleLock('editor')->can('play', 'gameboy', '2')); + $this->assertTrue($this->getRoleLock('editor')->cannot('play', 'gameboy', '2')); + $this->assertFalse($this->getRoleLock('editor')->canExplicitly('play', 'gameboy', '2')); + $this->assertFalse($this->getRoleLock('editor')->cannotExplicitly('play', 'gameboy', '2')); + + $this->getCallerLock()->allow('drink'); + $this->assertTrue($this->getCallerLock()->can('drink', 'coca', '2')); + $this->assertFalse($this->getCallerLock()->cannot('drink', 'coca', '2')); + $this->assertFalse($this->getCallerLock()->canExplicitly('drink', 'coca', '2')); + $this->assertFalse($this->getCallerLock()->cannotExplicitly('drink', 'coca', '2')); + $this->assertFalse($this->getRoleLock('editor')->can('drink', 'coca', '2')); + $this->assertTrue($this->getRoleLock('editor')->cannot('drink', 'coca', '2')); + $this->assertFalse($this->getRoleLock('editor')->canExplicitly('drink', 'coca', '2')); + $this->assertFalse($this->getRoleLock('editor')->cannotExplicitly('drink', 'coca', '2')); + + $this->getRoleLock('editor')->allow('cook'); + $this->assertTrue($this->getCallerLock()->can('cook', 'cake', '2')); + $this->assertFalse($this->getCallerLock()->cannot('cook', 'cake', '2')); + $this->assertFalse($this->getCallerLock()->canExplicitly('cook', 'cake', '2')); + $this->assertFalse($this->getCallerLock()->cannotExplicitly('cook', 'cake', '2')); + $this->assertTrue($this->getRoleLock('editor')->can('cook', 'cake', '2')); + $this->assertFalse($this->getRoleLock('editor')->cannot('cook', 'cake', '2')); + $this->assertFalse($this->getRoleLock('editor')->canExplicitly('cook', 'cake', '2')); + $this->assertFalse($this->getRoleLock('editor')->cannotExplicitly('cook', 'cake', '2')); + + $this->getCallerLock()->deny('read'); + $this->assertFalse($this->getCallerLock()->can('read', 'book', '2')); + $this->assertTrue($this->getCallerLock()->cannot('read', 'book', '2')); + $this->assertFalse($this->getCallerLock()->canExplicitly('read', 'book', '2')); + $this->assertFalse($this->getCallerLock()->cannotExplicitly('read', 'book', '2')); + $this->assertFalse($this->getRoleLock('editor')->can('read', 'book', '2')); + $this->assertTrue($this->getRoleLock('editor')->cannot('read', 'book', '2')); + $this->assertFalse($this->getRoleLock('editor')->canExplicitly('read', 'book', '2')); + $this->assertFalse($this->getRoleLock('editor')->cannotExplicitly('read', 'book', '2')); + + $this->getRoleLock('editor')->deny('fake'); + $this->assertFalse($this->getCallerLock()->can('fake', 'love', '2')); + $this->assertTrue($this->getCallerLock()->cannot('fake', 'love', '2')); + $this->assertFalse($this->getCallerLock()->canExplicitly('fake', 'love', '2')); + $this->assertFalse($this->getCallerLock()->cannotExplicitly('fake', 'love', '2')); + $this->assertFalse($this->getRoleLock('editor')->can('fake', 'love', '2')); + $this->assertTrue($this->getRoleLock('editor')->cannot('fake', 'love', '2')); + $this->assertFalse($this->getRoleLock('editor')->canExplicitly('fake', 'love', '2')); + $this->assertFalse($this->getRoleLock('editor')->cannotExplicitly('fake', 'love', '2')); + } } From 4a9a1e1c4a80c67a166b8fd6c8c42934a0c20bb2 Mon Sep 17 00:00:00 2001 From: simonbarbier Date: Wed, 13 May 2015 16:58:00 +0200 Subject: [PATCH 06/11] fix bug removing of permission --- src/Lock.php | 3 +-- src/Tests/PersistentDriverTestCase.php | 37 +++++++++++++++++++++++++- 2 files changed, 37 insertions(+), 3 deletions(-) diff --git a/src/Lock.php b/src/Lock.php index 515a787..b12cd75 100644 --- a/src/Lock.php +++ b/src/Lock.php @@ -133,7 +133,7 @@ public function deny($action, $resource = null, $resourceId = null, $conditions foreach ($actions as $action) { foreach ($permissions as $key => $permission) { - if ($permission instanceof Privilege && $permission->isAllowed($this, $action, $resource)) { + if ($permission instanceof Privilege && $permission->isAllowed($this, $action, $resource) && $permission->getResourceId() == $resource->getResourceId() && $permission->getResourceType() == $resource->getResourceType()) { $this->removePermission($permission); unset($permissions[$key]); } @@ -162,7 +162,6 @@ public function clear($action, $resource = null, $resourceId = null, $conditions { $actions = (array) $action; $resource = $this->convertResourceToObject($resource, $resourceId); - $permissions = $this->getPermissions(); foreach ($actions as $action) { $privilege = new Privilege($action, $resource); $this->removePermission($privilege); diff --git a/src/Tests/PersistentDriverTestCase.php b/src/Tests/PersistentDriverTestCase.php index 8d2ea7b..eb439fe 100644 --- a/src/Tests/PersistentDriverTestCase.php +++ b/src/Tests/PersistentDriverTestCase.php @@ -312,7 +312,6 @@ final function it_can_make_a_role_lock_aware() /** * @test - * @group failing */ final function it_can_return_allowed_resource_ids() { @@ -1242,4 +1241,40 @@ final function it_can_succeed_or_failed_on_explicit_call_ALL_RESOURCE_TYPE_AND_I $this->assertFalse($this->getRoleLock('editor')->cannotExplicitly('fake', 'love', '2')); } + + /** + * @test + */ + final function it_can_deny_permission_without_removing_other_privileges(){ + $this->getRoleLock('editor')->allow('fake', 'project'); + $this->assertTrue($this->getRoleLock('editor')->can('fake', 'project')); + $this->assertTrue($this->getRoleLock('editor')->can('fake', 'project', '1')); + $this->assertTrue($this->getRoleLock('editor')->can('fake', 'project', '2')); + $this->getRoleLock('editor')->allow('fake', 'project', '1'); + $this->assertTrue($this->getRoleLock('editor')->can('fake', 'project')); + $this->assertTrue($this->getRoleLock('editor')->can('fake', 'project', '1')); + $this->assertTrue($this->getRoleLock('editor')->can('fake', 'project', '2')); + $this->getRoleLock('editor')->deny('fake', 'project', '1'); + $this->assertTrue($this->getRoleLock('editor')->can('fake', 'project')); + $this->assertTrue($this->getRoleLock('editor')->cannot('fake', 'project', '1')); + $this->assertTrue($this->getRoleLock('editor')->can('fake', 'project', '2')); + } + + /** + * @test + */ + final function it_can_allow_permission_without_removing_other_restrictions(){ + $this->getRoleLock('editor')->deny('fake', 'project'); + $this->assertTrue($this->getRoleLock('editor')->cannot('fake', 'project')); + $this->assertTrue($this->getRoleLock('editor')->cannot('fake', 'project', '1')); + $this->assertTrue($this->getRoleLock('editor')->cannot('fake', 'project', '2')); + $this->getRoleLock('editor')->deny('fake', 'project', '1'); + $this->assertTrue($this->getRoleLock('editor')->cannot('fake', 'project')); + $this->assertTrue($this->getRoleLock('editor')->cannot('fake', 'project', '1')); + $this->assertTrue($this->getRoleLock('editor')->cannot('fake', 'project', '2')); + $this->getRoleLock('editor')->allow('fake', 'project', '1'); + $this->assertTrue($this->getRoleLock('editor')->cannot('fake', 'project')); + $this->assertTrue($this->getRoleLock('editor')->can('fake', 'project', '1')); + $this->assertTrue($this->getRoleLock('editor')->cannot('fake', 'project', '2')); + } } From 8df2ba18a991e71c2d95bb8982c4119e3c433767 Mon Sep 17 00:00:00 2001 From: Anthony Demarcy Date: Thu, 19 May 2016 10:20:21 +0200 Subject: [PATCH 07/11] Added a new function to the Driver interface to clear massively permissions in a resource-centric way. --- src/Drivers/Driver.php | 9 +++++++++ src/Manager.php | 13 +++++++++++++ 2 files changed, 22 insertions(+) diff --git a/src/Drivers/Driver.php b/src/Drivers/Driver.php index f011bec..6acdad1 100644 --- a/src/Drivers/Driver.php +++ b/src/Drivers/Driver.php @@ -82,4 +82,13 @@ public function removeRolePermission(Role $role, Permission $permission); * @return bool */ public function hasRolePermission(Role $role, Permission $permission); + + /** + * Removes all permissions for several resources, used + * for performance-critical massive removals. + * + * @param array $resources an array of Resource objects + * @return void + */ + public function clearPermissionsForResources(array $resources); } diff --git a/src/Manager.php b/src/Manager.php index 19e3835..e91dfab 100644 --- a/src/Manager.php +++ b/src/Manager.php @@ -138,6 +138,19 @@ protected function findRole($role) return new SimpleRole($role); } + /** + * Clear permissions for several resources (used + * for performance-critical massive removals). + * + * @param array $resources an array of Resource objects + * @return void + */ + public function clearPermissionsForResources($resources) + { + if(is_array($resources)) + $this->driver->clearPermissionsForResources($resources); + } + /** * @return \BeatSwitch\Lock\Drivers\Driver */ From 311d00fa2490cbd61ca0e0c088bc58124deb8eac Mon Sep 17 00:00:00 2001 From: Anthony Demarcy Date: Thu, 19 May 2016 10:36:50 +0200 Subject: [PATCH 08/11] Reworked design slightly and added an 'Unimplemented Exception' for ArrayDriver --- src/Drivers/ArrayDriver.php | 12 ++++++++++++ src/Drivers/Driver.php | 6 +++--- src/Manager.php | 7 +++++-- 3 files changed, 20 insertions(+), 5 deletions(-) diff --git a/src/Drivers/ArrayDriver.php b/src/Drivers/ArrayDriver.php index cab29ed..6754b85 100644 --- a/src/Drivers/ArrayDriver.php +++ b/src/Drivers/ArrayDriver.php @@ -145,6 +145,18 @@ public function hasRolePermission(Role $role, Permission $permission) return false; } + /** + * Removes all permissions for a resource, used + * for performance-critical massive removals. + * + * @param \BeatSwitch\Lock\Resources\Resource + * @return void + */ + public function clearPermissionsForResource(Resource $resource) + { + throw new Exception('Unimplemented method "ArrayDriver::clearPermissionsForResource"'); + } + /** * Creates a key to store the caller's permissions * diff --git a/src/Drivers/Driver.php b/src/Drivers/Driver.php index 6acdad1..325b20f 100644 --- a/src/Drivers/Driver.php +++ b/src/Drivers/Driver.php @@ -84,11 +84,11 @@ public function removeRolePermission(Role $role, Permission $permission); public function hasRolePermission(Role $role, Permission $permission); /** - * Removes all permissions for several resources, used + * Removes all permissions for a resource, used * for performance-critical massive removals. * - * @param array $resources an array of Resource objects + * @param \BeatSwitch\Lock\Resources\Resource * @return void */ - public function clearPermissionsForResources(array $resources); + public function clearPermissionsForResource(Resource $resource); } diff --git a/src/Manager.php b/src/Manager.php index e91dfab..629a2fd 100644 --- a/src/Manager.php +++ b/src/Manager.php @@ -147,8 +147,11 @@ protected function findRole($role) */ public function clearPermissionsForResources($resources) { - if(is_array($resources)) - $this->driver->clearPermissionsForResources($resources); + if(is_array($resources)) { + foreach($resources as $resource) { + $this->driver->clearPermissionsForResource($resource); + } + } } /** From 6bd4d4286db470a1e6f7170dcd24fdbafdb91402 Mon Sep 17 00:00:00 2001 From: Anthony Demarcy Date: Thu, 19 May 2016 10:45:02 +0200 Subject: [PATCH 09/11] Fixed an inclusion bug --- src/Drivers/Driver.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Drivers/Driver.php b/src/Drivers/Driver.php index 325b20f..6f364e6 100644 --- a/src/Drivers/Driver.php +++ b/src/Drivers/Driver.php @@ -4,6 +4,7 @@ use BeatSwitch\Lock\Callers\Caller; use BeatSwitch\Lock\Permissions\Permission; use BeatSwitch\Lock\Roles\Role; +use BeatSwitch\Lock\Resources\Resource; /** * A contract to identify an implementation to store permissions to From aa934a4d9ac244484833f93fa8c96432f0da4432 Mon Sep 17 00:00:00 2001 From: Anthony Demarcy Date: Thu, 19 May 2016 11:25:21 +0200 Subject: [PATCH 10/11] Fixed exception class visibility and simplified array check in Manager --- src/Drivers/ArrayDriver.php | 2 +- src/Manager.php | 8 +++----- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/Drivers/ArrayDriver.php b/src/Drivers/ArrayDriver.php index 6754b85..2941c7b 100644 --- a/src/Drivers/ArrayDriver.php +++ b/src/Drivers/ArrayDriver.php @@ -154,7 +154,7 @@ public function hasRolePermission(Role $role, Permission $permission) */ public function clearPermissionsForResource(Resource $resource) { - throw new Exception('Unimplemented method "ArrayDriver::clearPermissionsForResource"'); + throw new \Exception('Unimplemented method "ArrayDriver::clearPermissionsForResource"'); } /** diff --git a/src/Manager.php b/src/Manager.php index 629a2fd..868fdfe 100644 --- a/src/Manager.php +++ b/src/Manager.php @@ -145,12 +145,10 @@ protected function findRole($role) * @param array $resources an array of Resource objects * @return void */ - public function clearPermissionsForResources($resources) + public function clearPermissionsForResources(array $resources) { - if(is_array($resources)) { - foreach($resources as $resource) { - $this->driver->clearPermissionsForResource($resource); - } + foreach($resources as $resource) { + $this->driver->clearPermissionsForResource($resource); } } From 1fd1df22f57389ba8e1f8889387da019e57ac898 Mon Sep 17 00:00:00 2001 From: Ciprian Vate Date: Thu, 13 Oct 2016 17:02:41 +0200 Subject: [PATCH 11/11] Bugfix - initialize array --- src/Lock.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Lock.php b/src/Lock.php index b12cd75..0d70dbe 100644 --- a/src/Lock.php +++ b/src/Lock.php @@ -306,6 +306,7 @@ public function denied($actions, $resourceType) $denied[$action] = []; $allowedOnCallerLevel[$action] = []; $deniedOnCallerLevel[$action] = []; + $allowedOnRoleLevel[$action] = []; $deniedOnRoleLevel[$action] = []; // browse permission of a role / caller