Skip to content
This repository has been archived by the owner on Mar 18, 2022. It is now read-only.

Entity check #97

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
* text eol=lf
40 changes: 40 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -289,6 +289,42 @@ if ($user->allowed('edit.articles', $article, false)) { // now owner check is di
}
```

#### Checking Through Relationships

If your models have a different relationship than a simple `belongsTo` you can use the `allowedThrough` method which will check to see if the entity exists within the supplied relationship.

Let's say your articles have a many-to-many to users so you can have multiple authors. Within your user model you would add this relationship:

```php
// within User model
public function articles()
{
return $this->hasMany('App\Article');
}
```

Now you can substitute `allowed` with `allowedThrough` in the examples above, passing in the name of the dynamic property for ownership check.

```php
if ($user->allowedThrough('edit.articles', $article, 'articles')){
// article exists in the relation, do stuff
}
```

This method also includes the boolean value to skip the relationship check.

#### And/Or

By default both `allowed` and `allowedThrough` function using **OR** logic, they have the permission *or* they are the owner. This is useful for saying an admin *or* the owner can edit an article. But sometimes you need to check if a user has a permission **AND** is within a specific relationship. To accomplish this you can pass boolean `true` as the last argument to the functions to switch to AND mode.

```php
// user must have the edit.articles permission AND $article->user_id === $user->id
$user->allowed('edit.articles', $article, true, 'user_id', true)

// user must have the edit.articles permission AND the article is in $user->articles relationship
$user->allowedThrough('edit.articles', $article, 'articles', true, true)
```

### Blade Extensions

There are four Blade extensions. Basically, it is replacement for classic if statements.
Expand All @@ -310,6 +346,10 @@ There are four Blade extensions. Basically, it is replacement for classic if sta
// show edit button
@endallowed

@allowedThrough('edit', $article, 'articles') // @if(Auth::check() && Auth::user()->allowedThrough('edit', $article, 'articles'))
// show edit button
@endallowedthrough

@role('admin|moderator', 'all') // @if(Auth::check() && Auth::user()->is('admin|moderator', 'all'))
// user is admin and also moderator
@else
Expand Down
8 changes: 8 additions & 0 deletions src/Bican/Roles/RolesServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -76,5 +76,13 @@ protected function registerBladeExtensions()
$blade->directive('endallowed', function () {
return "<?php endif; ?>";
});

$blade->directive('allowedThrough', function($expression) {
return "<?php if(Auth::check() && Auth::user()->allowedThrough{$expression}): ?>";
});

$blade->directive('endallowedthrough', function () {
return "<?php endif; ?>";
});
}
}
223 changes: 220 additions & 3 deletions src/Bican/Roles/Traits/HasRoleAndPermission.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,17 @@

namespace Bican\Roles\Traits;

use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Relations\HasManyThrough;
use Illuminate\Database\Eloquent\Relations\HasOneOrMany;
use Illuminate\Database\Eloquent\Relations\MorphOneOrMany;
use Illuminate\Database\Eloquent\Relations\MorphTo;
use Illuminate\Database\Eloquent\Relations\MorphToMany;
use Illuminate\Support\Str;
use Illuminate\Database\Eloquent\Model;
use InvalidArgumentException;
use ReflectionClass;

trait HasRoleAndPermission
{
Expand Down Expand Up @@ -260,21 +268,230 @@ public function hasPermission($permission)
* @param \Illuminate\Database\Eloquent\Model $entity
* @param bool $owner
* @param string $ownerColumn
* @param bool $and
* @return bool
*/
public function allowed($providedPermission, Model $entity, $owner = true, $ownerColumn = 'user_id')
public function allowed($providedPermission, Model $entity, $owner = true, $ownerColumn = 'user_id', $and = false)
{
if ($this->isPretendEnabled()) {
return $this->pretend('allowed');
}

$related = false;
if ($owner === true && $entity->{$ownerColumn} == $this->id) {
return true;
if($and === false){
return true;
} else {
$related = true;
}

}

return $this->isAllowed($providedPermission, $entity);
$allowed = $this->isAllowed($providedPermission, $entity);

if($and === false){
return $related || $allowed;
} else {
return $related && $allowed;
}
}

/**
* Checks if the user is allowed to manipulate the entity, but through a relationship
*
* @param $providedPermission
* @param Model $entity
* @param string $relation_name
* @param bool $owner
* @param bool $and
* @return bool
*/
public function allowedThrough($providedPermission, Model $entity, $relation_name, $owner = true, $and = false)
{
if ($this->isPretendEnabled()) {
return $this->pretend('allowed');
}

$related = false;
if($owner === true){
$relation = $this->$relation_name();
switch ((new ReflectionClass($relation))->getShortName()) {
case 'BelongsTo':
$related = $this->checkBelongsTo($relation, $entity);
break;

case 'BelongsToMany':
$related = $this->checkbelongsToMany($relation, $entity);
break;

case 'HasMany':
case 'HasOne':
$related = $this->checkHasOneOrMany($relation, $entity);
break;

case 'HasManyThrough':
$related = $this->checkHasManyThrough($relation, $entity);
break;

case 'MorphMany':
case 'MorphOne':
$related = $this->checkMorphOneOrMany($relation, $entity);
break;

case 'MorphTo':
$related = $this->checkMorphTo($relation, $entity);
break;

case 'MorphToMany':
$related = $this->checkMorphToMany($relation, $entity);
break;
}

if($related && $and === false){
return true;
}

}

$allowed = $this->isAllowed($providedPermission, $entity);

if($and === false){
return $related || $allowed;
} else {
return $related && $allowed;
}
}

/**
* Checks if the provided entity is with the provided BelongsTo relationship
*
* @param BelongsTo $relation
* @param Model $entity
* @return bool
*/
protected function checkBelongsTo(BelongsTo $relation, Model $entity)
{
$this_key = $relation->getForeignKey();
$entity_key = $relation->getOtherKey();

return $this->$this_key === $entity->$entity_key;
}

/**
* Check if the provided entity is within the provided BelongsToMany relationship
*
* @param BelongsToMany $relation
* @param Model $entity
* @return bool
*/
protected function checkBelongsToMany(BelongsToMany $relation, Model $entity)
{
$entity_qualified_key_name = $relation->getOtherKey();

return $relation
->where($entity_qualified_key_name, $entity->getKey())
->exists();
}

/**
* Checks if the provided entity is within the provided HasOneOrMany relationship
*
* @param HasOneOrMany $relation
* @param Model $entity
* @return bool
*/
protected function checkHasOneOrMany(HasOneOrMany $relation, Model $entity)
{
$this_key = $this->unqualifiedKeyName($relation->getQualifiedParentKeyName());
$entity_key = $relation->getPlainForeignKey();

return $this->$this_key === $entity->$entity_key;
}

/**
* Checks if the provided entity is within the provided HasManyThrough relationship
*
* @param HasManyThrough $relation
* @param Model $entity
* @return bool
*/
protected function checkHasManyThrough(HasManyThrough $relation, Model $entity)
{
$entity_qualified_key_name = $relation->getRelated()->getQualifiedKeyName();
$entity_key_name = $relation->getRelated()->getKeyName();

return $relation
->where($entity_qualified_key_name, $entity->$entity_key_name)
->exists();
}

/**
* Checks if the provided entity is within the provided MorphOneOrMany relationship
*
* @param MorphOneOrMany $relation
* @param Model $entity
* @return bool
*/
protected function checkMorphOneOrMany(MorphOneOrMany $relation, Model $entity)
{
$entity_qualified_key_name = $relation->getRelated()->getQualifiedKeyName();
$entity_key_name = $relation->getRelated()->getKeyName();

return $relation
->where($entity_qualified_key_name, $entity->$entity_key_name)
->exists();
}

/**
* Checks if the provided entity is within the provided MorphTo relationship
*
* @param MorphTo $relation
* @param Model $entity
* @return bool
*/
protected function checkMorphTo(MorphTo $relation, Model $entity)
{
$morphed_type = $this->{$relation->getMorphType()};
$this_key = $relation->getForeignKey();
$entity_key = $relation->getOtherKey();

return (
$entity instanceof $morphed_type
&& $this->$this_key === $entity->$entity_key
);
}

/**
* Checks if the provided entity is within the provided MorphToMany relationship
*
* @param MorphToMany $relation
* @param Model $entity
* @return bool
*/
protected function checkMorphToMany(MorphToMany $relation, Model $entity)
{
$entity_qualified_key_name = $relation->getOtherKey();
$entity_key_name = $relation->getRelated()->getKeyName();

return $relation
->where($entity_qualified_key_name, $entity->$entity_key_name)
->exists();
}


/**
* Takes a qualified key name and returns the unqualified version
*
* @param $key
* @return mixed
*/
private function unqualifiedKeyName($key)
{
$segments = explode('.', $key);

return array_pop($segments);
}

/**
* Check if the user is allowed to manipulate with provided entity.
*
Expand Down