Laravel user capabilities
Within Laravel you can easily define abilities and then hook into them later on a user request regarding to do different actions:
$gate->define('update-post', function ($user, $post) {
return $user->id === $post->user_id;
});
But almost all my defined abilities has this part $user->id === $model->user_id
in it. I don't like it as it's a kind of repeating a condition over and over which I think could be more abstract.
Most of my defined abilities are according to updating/deleting records, so it would be better if I could make a global condition applied to all of them or if there could be a group ability defining which is like to what we do in routing.
Is there any workaround for it? I really like it DRY.
Answer
Everything in Laravel is extendable, that's the power of its service providers.
You can extend the Gate
object to a MyCustomGate
object and do whatever you want in that object. Here's an example:
MyCustomGate.php
class MyCustomGate extends \Illuminate\Auth\Access\Gate
{
protected $hasOwnershipVerification = [];
/**
* Define a new ability.
*
* @param string $ability
* @param callable|string $callback
* @return $this
*
* @throws \InvalidArgumentException
*/
public function defineWithOwnership($ability, $callback, $foreignUserIdKey = "user_id")
{
// We will add this
$this->hasOwnershipVerification[$ability] = $foreignUserIdKey;
return $this->define($ability, $callback);
}
/**
* Resolve and call the appropriate authorization callback.
*
* @param \Illuminate\Contracts\Auth\Authenticatable $user
* @param string $ability
* @param array $arguments
* @return bool
*/
protected function callAuthCallback($user, $ability, array $arguments)
{
$callback = $this->resolveAuthCallback(
$user, $ability, $arguments
);
// We will assume that the model is ALWAYS the first key
$model = is_array($arguments) ? $arguments[0] : $arguments;
return $this->checkDirectOwnership($ability, $user, $model) && call_user_func_array(
$callback, array_merge([$user], $arguments)
);
}
/**
* Check if the user owns a model.
*
* @param string $ability
* @param \Illuminate\Contracts\Auth\Authenticatable $user
* @param \Illuminate\Database\Eloquent\Model $model
* @return bool
*/
protected function checkDirectOwnership($ability, $user, $model)
{
if(!isset($this->hasOwnershipVerification[$ability])) {
return true
}
$userIdKey = $this->hasOwnershipVerification[$ability];
// getAuthIdentifier() is just ->id, but it's better in case the pk of a user is different that id
return $user->getAuthIdentifier() == $model->{$userIdKey};
}
}
Then, you will have to tell Laravel to use your gate instead of the default one. You ca do that in your AuthServiceProvider
(assuming that it's extending Illuminate\Auth\AuthServiceProvider
, just add the following method.
AuthServiceProvider
/**
* Register the access gate service.
*
* @return void
*/
protected function registerAccessGate()
{
$this->app->singleton(\Illuminate\Contracts\Auth\Access\Gate::class, function ($app) {
return new MyCustomGate($app, function () use ($app) {
return $app['auth']->user();
});
});
}
And this way, you can define abilities using defineWithOwnership()
method instead of define()
. You can still use define()
for abilities that don't require ownership verification. There's a third parameter defineWithOwnership()
accepts which is $foreignUserIdKey
; that's used for the case when a model has a different field for the user id.
Note: I wrote the code on the fly and did not try it, it may have errors, but you get the idea.
Related Questions
- → "failed to open stream" error when executing "migrate:make"
- → October CMS Plugin Routes.php not registering
- → OctoberCMS Migrate Table
- → OctoberCMS Rain User plugin not working or redirecting
- → October CMS Custom Mail Layout
- → October CMS - How to correctly route
- → October CMS create a multi select Form field
- → October CMS - Conditionally Load a Different Page
- → How to disable assets combining on development in OctoberCMS
- → October CMS - Radio Button Ajax Click Twice in a Row Causes Content to disappear
- → OctoberCms component: How to display all ID(items) instead of sorting only one ID?
- → In OctoberCMS how do you find the hint path?
- → How to register middlewares in OctoberCMS plugin?