Using Spatie Permissions in Test doesn't have permissions - php

I am using a RolesAndPermissionsSeeder:
class RolesAndPermissionsSeeder extends Seeder
{
/**
* Run the database seeds.
*
* #return void
*/
public function run()
{
app()[\Spatie\Permission\PermissionRegistrar::class]->forgetCachedPermissions();
$permissions = [
'employers.index',
'employers.show',
'employers.create',
'employers.edit',
'employers.destroy',
];
foreach ($permissions as $permission) {
Permission::create(['name' => $permission]);
}
$role = Role::create(['name' => "Employer"]);
$role->givePermissionTo(Permission::all()->except(['employers.index', 'employers.destroy']));
}
}
When I create a new employer, a user get's created with it and it gets assigned a Employer role:
class Employer extends Model
{
use HasFactory;
public function user()
{
return $this->belongsTo(User::class);
}
protected static function booted()
{
static::created(function ($employer) {
$employer->user->assignRole('Employer');
});
}
}
Now in the EmployerTest I want to use the permissions:
class EmployerTest extends TestCase
{
use RefreshDatabase;
use WithFaker;
public function setUp(): void
{
parent::setUp();
$this->app->make(\Spatie\Permission\PermissionRegistrar::class)->registerPermissions();
$this->seed(RolesAndPermissionsSeeder::class);
}
public function test_permission()
{
$employer = \App\Models\Employer::factory()->create();
dump(Role::all()); // <-- shows all roles correctly
dump(Permission::all()); <-- shows all permissions correctly
dump(Role::where('name', 'Employer')->first()->permissions->pluck('name')->toArray()); // <-- shows the assigned permissions
dump($employer->user->hasRole('Employer')); // <-- shows true
dd($employer->user->getPermissionNames());
}
}
The dd() shows items: [] what is unexpected. What am I missing? The Employer's user has the Role Employer but it does not have the permissions, but the role itself has it. What's wrong here?

I think what you are missing is $employer->user->getAllPermissions() according to the documentation : https://spatie.be/docs/laravel-permission/v5/basic-usage/role-permissions

Related

Laravel: user id is not added as Foreignkey

I want that a user can create e.g. a football club. When the logged in user creates the club (with an Input-field) the foreignkey should automatically appear in the table of the club.
User
User Modell
class User extends Authenticatable
{
use Notifiable;
protected $fillable = [
'name',
'email',
'password',
];
public function wgGroup()
{
return $this->hasOne('WgGroup','user_id');
}
}
WgGroup e.g. for example to create a club
WgGroup Controller
class WGController extends Controller
{
public function dashboard()
{
return view('verified.dashboard');
}
public function createWG(Request $request)
{
$wg = new WgGroup();
$wg->wg_name = $request->wg_name;
$wg->user_id = User::find($request['id']);
$wg->save();
if($wg != null){
return redirect()->back()->with(session()->flash('alert-success', 'Your wg are createt'));
}
return redirect()->back()->with(session()->flash('alert-danger', 'Something went wrong!'));
}
}
WgGrup Model
class WgGroup extends Model
{
use HasFactory;
protected $table = 'wg_groups';
protected $fillable = [
'wg_name', 'user_id'
];
public function user() {
return $this->belongsTo('User');
}
}
WgGroup Database
class CreateWgGroups extends Migration
{
/**
* Run the migrations.
*
* #return void
*/
public function up()
{
Schema::create('wg_groups', function (Blueprint $table) {
$table->id();
$table->string('wg_name');
$table->foreignId('user_id')->nullable()->constrained('users')->onDelete('set null');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* #return void
*/
public function down()
{
Schema::dropIfExists('wg_groups');
}
}
I don't know exactly why the FK user_id is not added directly to the database.
public function user() {
return $this->belongsTo('User');
}
I thought through this function would happen.

How to deploy relationship in pivot of three model in laravel?

I'm developing a role and permissions based on laravel framework.
I have 3 models :
Weblog
User
Permissions
This is pivot table
user_id , weblog_id , permission_id
Now, a user can have a weblog with permission id 1,2 and another weblog with permission 1,2,3,4
How can I deploy relationships? and how can I check user permissions when managing a weblog. (middleware and ...)
With the fact that Permission are specific to Weblog
Say the pivot table is called permission_user_weblog
class User extends Model
{
public function weblogs()
{
return $this->belongsToMany(Weblog::class, 'permission_user_weblog');
}
public function permissionsFor(int $weblogId)
{
$permissionIds = null;
$this->weblogs()
->where('id', $weblogId)
->with('permissions')
->get()
->each(function($weblog) use(&$permissionIds) {
$permissionIds = $weblog->permissions->pluck('id');
});
return $permissionIds;
}
}
class Weblog extends Model
{
public function users()
{
return $this->belongsToMany(User::class, 'permission_user_weblog');
}
public function permissions()
{
return $this->belongsToMany(Permission::class, 'permission_user_weblog');
}
}
class Permission extends Model
{
public function weblogs()
{
return $this->belongsToMany(Weblog::class, 'permission_user_weblog');
}
}
Then you can check anywhere for whether logged in user has specific permission for a specific weblog
public function update(Request $request, $weblogId)
{
$user = auth()->user();
$permissions = $user->permissionsFor($weblogId);
//Check whether the logged in user has permission identified by id 1 or 4 for weblog
$can = !! $permissions->intersect([1,4])->count();
//Do rest of processing
}
your Weblog,User,Permission has ManyToMany Relation, its a kind of odd but if you want to have this kind of relation its not a problem.
just consider each pair a ManyToMany. and every one of those can have a hasMany to Pivot (i named it Access) too (based on your needs).
User model:
class User extends Model{
/**
* retrive weblogs
*
* #return BelongsToMany weblogs
*/
public function weblogs()
{
return $this->belongsToMany(App\WebLog::class,'accesses_table')
->withPivot("permission_id")
->using(App\Access::class);
}
/**
* retrive permissions
*
* #return BelongsToMany permissions
*/
public function permissions()
{
return $this->belongsToMany(App\Permission::class,'accesses_table')
->withPivot("weblog_id")
->using(App\Access::class);
}
/**
* retrive access
*
* #return hasMany [description]
*/
public function accesses()
{
return $this->hasMany(App\Access::class, "user_id");
}
}
Weblog model:
class Weblog extends Model{
/**
* retrive users
*
* #return BelongsToMany users
*/
public function users()
{
return $this->belongsToMany(App\User::class,'accesses_table')
->withPivot("permission_id")
->using(App\Access::class);
}
/**
* retrive permissions
*
* #return BelongsToMany permissions
*/
public function permissions()
{
return $this->belongsToMany(App\Permission::class,'accesses_table')
->withPivot("user_id")
->using(App\Access::class);
}
/**
* retrive access
*
* #return hasMany [description]
*/
public function accesses()
{
return $this->hasMany(App\Access::class, "weblog_id");
}
}
Permission model:
class Permission extends Model{
/**
* retrieve users
*
* #return BelongsToMany users
*/
public function users()
{
return $this->belongsToMany(App\User::class,'accesses_table')
->withPivot("weblog_id")
->using(App\Access::class);
}
/**
* retrieve weblogs
*
* #return BelongsToMany weblogs
*/
public function weblogs()
{
return $this->belongsToMany(App\Weblog::class,'accesses_table')
->withPivot("user_id")
->using(App\Access::class);
}
/**
* retrive access
*
* #return hasMany [description]
*/
public function accesses()
{
return $this->hasMany(App\Access::class, "permission_id");
}
}
and you can have a model for your pivot, which i named it Access :
Illuminate\Database\Eloquent\Relations\Pivot;
class Access extends Pivot
{
public $incrementing = true;
public function user()
{
return $this->belongsTo(App\User::class);
}
public function weblog()
{
return $this->belongsTo(App\Weblog::class);
}
public function permission()
{
return $this->belongsTo(App\Permission::class);
}
}

Role based permission to Laravel

I am trying to do a role based permission control in a Laravel application. I want to check what actions can some user do, but i can't figure out how to implement gates and policies in my model (the permission description is in the database and are booleans asociated to a table that stores the resource's ids).
This is the database model that im using:
I would like to know if laravel gates is useful in my case, and how can i implement it, if not, how to make a basic middleware that take care of permission control to protect routes (or controllers).
In the table resource i have a uuid that identifies the resources, the alias is the name of the resource and has dot notation values of actions or context of the resource (eg. 'mysystem.users.create', 'mysystem.roles.delete', 'mysystem.users.images.view'). The policy tables has a boolean 'allow' field that describes the permission of users.
Thanks in advance.
This is the way that I implement role based permissions in Laravel using Policies.
Users can have multiple roles.
Roles have associated permissions.
Each permission allows a specific action on a specific model.
Migrations
Roles table
class CreateRolesTable extends Migration
{
/**
* Run the migrations.
*
* #return void
*/
public function up()
{
Schema::create('roles', function (Blueprint $table) {
$table->increments('id');
$table->string('name')->unique();
$table->string('label');
$table->text('description');
$table->timestamps();
});
}
// rest of migration file
Permissions table
class CreatePermissionsTable extends Migration
{
/**
* Run the migrations.
*
* #return void
*/
public function up()
{
Schema::create('permissions', function (Blueprint $table) {
$table->increments('id');
$table->string('name')->unique();
$table->string('label');
$table->text('description');
$table->timestamps();
});
}
// rest of migration file
Permission Role Pivot Table
class CreatePermissionRolePivotTable extends Migration
{
/**
* Run the migrations.
*
* #return void
*/
public function up()
{
Schema::create('permission_role', function (Blueprint $table) {
$table->integer('permission_id')->unsigned()->index();
$table->foreign('permission_id')->references('id')->on('permissions')->onDelete('cascade');
$table->integer('role_id')->unsigned()->index();
$table->foreign('role_id')->references('id')->on('roles')->onDelete('cascade');
$table->primary(['permission_id', 'role_id']);
});
}
// rest of migration file
Role User Pivot Table
class CreateRoleUserPivotTable extends Migration
{
/**
* Run the migrations.
*
* #return void
*/
public function up()
{
Schema::create('role_user', function (Blueprint $table) {
$table->integer('role_id')->unsigned()->index();
$table->foreign('role_id')->references('id')->on('roles')->onDelete('cascade');
$table->integer('user_id')->unsigned()->index();
$table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
$table->primary(['role_id', 'user_id']);
});
}
// rest of migration file
Models
User
public function roles()
{
return $this->belongsToMany(Role::class);
}
public function assignRole(Role $role)
{
return $this->roles()->save($role);
}
public function hasRole($role)
{
if (is_string($role)) {
return $this->roles->contains('name', $role);
}
return !! $role->intersect($this->roles)->count();
}
Role
class Role extends Model
{
protected $guarded = ['id'];
protected $fillable = array('name', 'label', 'description');
public function permissions()
{
return $this->belongsToMany(Permission::class);
}
public function givePermissionTo(Permission $permission)
{
return $this->permissions()->save($permission);
}
/**
* Determine if the user may perform the given permission.
*
* #param Permission $permission
* #return boolean
*/
public function hasPermission(Permission $permission, User $user)
{
return $this->hasRole($permission->roles);
}
/**
* Determine if the role has the given permission.
*
* #param mixed $permission
* #return boolean
*/
public function inRole($permission)
{
if (is_string($permission)) {
return $this->permissions->contains('name', $permission);
}
return !! $permission->intersect($this->permissions)->count();
}
}
Permission
class Permission extends Model
{
protected $guarded = ['id'];
protected $fillable = array('name', 'label', 'description');
public function roles()
{
return $this->belongsToMany(Role::class);
}
/**
* Determine if the permission belongs to the role.
*
* #param mixed $role
* #return boolean
*/
public function inRole($role)
{
if (is_string($role)) {
return $this->roles->contains('name', $role);
}
return !! $role->intersect($this->roles)->count();
}
}
Policies
A policy is required for each model. Here is an example policy for a model item. The policy defines the 'rules' for the four actions 'view, create, update, delete.
class ItemPolicy
{
use HandlesAuthorization;
/**
* Determine whether the user can view the item.
*
* #param \App\User $user
* #return mixed
*/
public function view(User $user)
{
$permission = Permission::where('name', 'items-view')->first();
return $user->hasRole($permission->roles);
}
/**
* Determine whether the user can create items.
*
* #param \App\User $user
* #return mixed
*/
public function create(User $user)
{
$permission = Permission::where('name', 'items-create')->first();
return $user->hasRole($permission->roles);
}
/**
* Determine whether the user can update the item.
*
* #param \App\User $user
* #return mixed
*/
public function update(User $user)
{
$permission = Permission::where('name', 'items-update')->first();
return $user->hasRole($permission->roles);
}
/**
* Determine whether the user can delete the item.
*
* #param \App\User $user
* #return mixed
*/
public function delete(User $user)
{
$permission = Permission::where('name', 'items-delete')->first();
return $user->hasRole($permission->roles);
}
}
Register each policy in AuthServiceProvider.php
use App\Item;
use App\Policies\ItemPolicy;
class AuthServiceProvider extends ServiceProvider
{
/**
* The policy mappings for the application.
*
* #var array
*/
protected $policies = [
Item::class => ItemPolicy::class,
];
// rest of file
Controllers
In each controller, refer to the corresponding authorisation action from the policy.
For example, in the index method of ItemController:
public function index()
{
$this->authorize('view', Item::class);
$items = Item::orderBy('name', 'asc')->get();
return view('items', ['items' => $items]);
}
Views
In your views, you can check if the user has a specific role:
#if (Auth::user()->hasRole('item-administrator'))
// do stuff
#endif
or if a specific permission is required:
#can('create', App\User::class)
// do stuff
#endcan
Answer for your Question:how to make a basic middleware that take care of permission control to protect routes (or controllers)?.
Just an Example:
Here is the simple role middleware for your routes
AdminRole
namespace App\Http\Middleware;
use Illuminate\Support\Facades\Auth;
use Closure;
class AdminRole
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next)
{
if(Auth::user()->role->name!=="admin"){ //Check your users' role or permission, in my case only admin role for routes
return redirect('/access-denied');
}
return $next($request);
}
}
After defining this middleware
Update your kernel.php file as
protected $routeMiddleware = [
..............
'admin' =>\App\Http\Middleware\AdminRole::class,
...................
];
And to use this route middleware:
There are different way to use route middleware but following is one example
Route::group(['middleware' => ['auth','admin']], function () {
Route::get('/', 'AdminController#index')->name('admin');
});
Note: There are some tools and libraries for roles and permission on laravel but above is the example of creating basic route middle-ware.
Because the laravel model did not fit my database so much, I did almost everything again. This is a functional draft in which some functions are missing, the code is not optimized and it may be a bit dirty, but here it is:
proyect/app/Components/Contracts/Gate.php This interface is used to create singleton in AuthServiceProvider.
<?php
namespace App\Components\Contracts;
interface Gate
{
public function check($resources, $arguments = []);
public function authorize($resource, $arguments = []);
}
proyect/app/Components/Security/Gate.php This file loads the permissions from the database. This could be improved a lot :(
<?php
namespace App\Components\Security;
use App\Components\Contracts\Gate as GateContract;
use App\Models\Security\Resource;
use App\Models\Security\User;
use Illuminate\Auth\Access\HandlesAuthorization;
use Illuminate\Contracts\Container\Container;
use Illuminate\Support\Arr;
use Illuminate\Support\Str;
class Gate implements GateContract
{
use HandlesAuthorization;
protected $container;
protected $userResolver;
protected $policies = [];
public function __construct(Container $container, callable $userResolver)
{
$this->container = $container;
$this->userResolver = $userResolver;
}
public function permissionsForUser(User $user)
{
$result = User::with(['roles.resources', 'groups.resources', 'policies'])->where('id', $user->id)->first();
$list = [];
//role-specific ... the order is important role < group < user permissions
foreach ($result->roles as $role) {
foreach ($role->resources as $permission) {
if (isset($list[$permission->uuid])) {
if ($list[$permission->uuid]['on'] == User::ROLE_POLICY) {
if ($permission->pivot->allow == false) {
$list[$permission->uuid]['allow'] = false;
}
} else {
$list[$permission->uuid]['allow'] = $permission->pivot->allow ? true : false;
$list[$permission->uuid]['on'] = User::ROLE_POLICY;
$list[$permission->uuid]['id'] = $role->id;
}
} else {
$list[$permission->uuid] = [
'allow' => ($permission->pivot->allow ? true : false),
'on' => User::ROLE_POLICY,
'id' => $role->id];
}
}
}
// group-specific
foreach ($result->groups as $group) {
foreach ($group->resources as $permission) {
if (isset($list[$permission->uuid])) {
if ($list[$permission->uuid]['on'] == User::GROUP_POLICY) {
if ($permission->pivot->allow == false) {
$list[$permission->uuid]['allow'] = false;
}
} else {
$list[$permission->uuid]['allow'] = $permission->pivot->allow ? true : false;
$list[$permission->uuid]['on'] = User::GROUP_POLICY;
$list[$permission->uuid]['id'] = $group->id;
}
} else {
$list[$permission->uuid] = [
'allow' => ($permission->pivot->allow ? true : false),
'on' => User::GROUP_POLICY,
'id' => $group->id];
}
}
}
// user-specific policies
foreach ($result->policies as $permission) {
if (isset($list[$permission->uuid])) {
if ($list[$permission->uuid]['on'] == User::USER_POLICY) {
if ($permission->pivot->allow == false) {
$list[$permission->uuid]['allow'] = false;
}
} else {
$list[$permission->uuid]['allow'] = $permission->pivot->allow ? true : false;
$list[$permission->uuid]['on'] = User::USER_POLICY;
$list[$permission->uuid]['id'] = $result->id;
}
} else {
$list[$permission->uuid] = [
'allow' => ($permission->pivot->allow ? true : false),
'on' => User::USER_POLICY,
'id' => $result->id,
];
}
}
return $list;
}
public function check($resources, $arguments = [])
{
$user = $this->resolveUser();
return collect($resources)->every(function ($resource) use ($user, $arguments) {
return $this->raw($user, $resource, $arguments);
});
}
protected function raw(User $user, $resource, $arguments = [])
{
$list = $user->getPermissionList();
if (!Resource::isUUID($resource)) {
if (empty($resource = Resource::byAlias($resource))) {
return false;
}
}
if (empty($list[$resource->uuid]['allow'])) {
return false;
} else {
return $list[$resource->uuid]['allow'];
}
}
public function authorize($resource, $arguments = [])
{
$theUser = $this->resolveUser();
return $this->raw($this->resolveUser(), $resource, $arguments) ? $this->allow() : $this->deny();
}
protected function resolveUser()
{
return call_user_func($this->userResolver);
}
}
proyect/app/Traits/Security/AuthorizesRequests.php This file is added to controller. Allows to use $this->authorize('stuff'); in a controller when is added.
<?php
namespace App\Traits\Security;
use App\Components\Contracts\Gate;
trait AuthorizesRequests
{
public function authorize($ability, $arguments = [])
{
list($ability, $arguments) = $this->parseAbilityAndArguments($ability, $arguments);
return app(Gate::class)->authorize($ability, $arguments);
}
}
proyect/app/Providers/AuthServiceProvider.php This file is the same that can be found on proyect/vendor/laravel/framework/src/Illuminate/Auth/AuthServiceProvider.php, but i changed some parts to add new classe. Here are the important methods:
<?php
namespace App\Providers;
use App\Components\Contracts\Gate as GateContract;
use App\Components\Security\Gate;
use Illuminate\Auth\AuthManager;
use Illuminate\Contracts\Auth\Authenticatable as AuthenticatableContract;
use Illuminate\Support\ServiceProvider;
class AuthServiceProvider extends ServiceProvider
{
/* function register() ... */
/* other methods () */
protected function registerAccessGate()
{
$this->app->singleton(GateContract::class, function ($app) {
return new Gate($app, function () use ($app) {
return call_user_func($app['auth']->userResolver());
});
});
}
/* ... */
}
proyect /app/Http/Middleware/AuthorizeRequest.php This file is used to allow add the 'can' middleware to routes, eg: Route::get('users/', 'Security\UserController#index')->name('users.index')->middleware('can:inet.user.list');
<?php
namespace App\Http\Middleware;
use App\Components\Contracts\Gate;
use Closure;
use Illuminate\Contracts\Auth\Factory as Auth;
class AuthorizeRequest
{
protected $auth;
protected $gate;
public function __construct(Auth $auth, Gate $gate)
{
$this->auth = $auth;
$this->gate = $gate;
}
public function handle($request, Closure $next, $resource, ...$params)
{
$this->auth->authenticate();
$this->gate->authorize($resource, $params);
return $next($request);
}
}
but you must overwrite the default value in proyect/app/Http/Kernel.php:
/* ... */
protected $routeMiddleware = [
'can' => \App\Http\Middleware\AuthorizeRequest::class,
/* ... */
];
To use #can('inet.user.list') in a blade template you have to add this lines to proyect/app/Providers/AppServiceProvider.php:
class AppServiceProvider extends ServiceProvider
{
public function boot()
Blade::if ('can', function ($resource, ...$params) {
return app(\App\Components\Contracts\Gate::class)->check($resource, $params);
});
}
/* ... */
User model at proyect/app/Models/Security/User.php
<?php
namespace App\Models\Security;
use App\Components\Contracts\Gate as GateContract;
use App\Models\Security\Group;
use App\Models\Security\Resource;
use App\Models\Security\Role;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Illuminate\Support\Facades\Hash;
class User extends Authenticatable
{
use SoftDeletes;
use Notifiable;
public $table = 'user';
const CREATED_AT = 'created_at';
const UPDATED_AT = 'updated_at';
// tipos de politicas
const GROUP_POLICY = 'group_policy';
const ROLE_POLICY = 'role_policy';
const USER_POLICY = 'user_policy';
protected $dates = ['deleted_at'];
public $fillable = [
];
public function policies()
{
return $this->belongsToMany(Resource::class, 'user_policy', 'user_id', 'resource_id')
->whereNull('user_policy.deleted_at')
->withPivot('allow')
->withTimestamps();
}
public function groups()
{
return $this->belongsToMany(Group::class, 'user_group', 'user_id', 'group_id')
->whereNull('user_group.deleted_at')
->withTimestamps();
}
public function roles()
{
return $this->belongsToMany(Role::class, 'user_role', 'user_id', 'role_id')
->whereNull('user_role.deleted_at')
->withTimestamps();
}
public function getPermissionList()
{
return app(GateContract::class)->permissionsForUser($this);
}
}
Group model at proyect/app/Models/Security/Group.php THis is the same than Role, change only names
<?php
namespace App\Models\Security;
use App\Models\Security\Resource;
use App\Models\Security\User;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
class Group extends Model
{
use SoftDeletes;
public $table = 'group';
const CREATED_AT = 'created_at';
const UPDATED_AT = 'updated_at';
protected $dates = ['deleted_at'];
public $fillable = [
'name',
];
public static $rules = [
];
public function users()
{
return $this->hasMany(User::class);
}
public function resources()
{
return $this->belongsToMany(Resource::class, 'group_policy', 'group_id', 'resource_id')
->whereNull('group_policy.deleted_at')
->withPivot('allow')
->withTimestamps();
}
}
Resource Model proyect/app/Models/Security/Resource.php
<?php
namespace App\Models\Security;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
class Resource extends Model
{
use SoftDeletes;
public $table = 'resource';
const CREATED_AT = 'created_at';
const UPDATED_AT = 'updated_at';
protected $dates = ['deleted_at'];
public $fillable = [
'alias',
'uuid',
'type',
];
public static $rules = [
];
public static function isUUID($value)
{
$UUIDv4 = '/^[0-9A-F]{8}-[0-9A-F]{4}-4[0-9A-F]{3}-[0-9A-F]{4}-[0-9A-F]{12}$/i';
return preg_match($UUIDv4, $value);
}
public static function byAlias($value)
{
return Resource::where('alias', $value)->first();
}
}
There are a lot of things that I have not put here, but this is what I have so far
The problem i find with trying to combine permissions from a db with policies is when it comes to the ownership of a record.
Ultimately in our code we would like to check access to a resource using permission only. This is because as the list of roles grows we don't want to have to keep adding checks for these roles to the codebase.
If we have a users table we may want 'admin' (role) to be able to update all user records but a 'basic' user to only be able to update their own user record. We would like to be able to control this access SOLELY using the database.
However, if you have an 'update_user' permission then do you give it to both roles?
If you don't give it to the basic user role then the request won't get as far as the policy to check ownership.
Hence, you cannot revoke access for a basic user to update their record from the db alone.
Also the meaning of 'update_user' in the permissions table now implies the ability to update ANY user.
SOLUTION?
Add extra permissions to cater for the case where a user owns the record.
So you could have permissions to 'update_user' AND 'update_own_user'.
The 'admin' user would have the first permission whilst the 'basic' user would have the second one.
Then in the policy we check for the 'update_user' permission first and if it's not present we check for the 'update_own_user'.
If the 'update_own_user' permission is present then we check ownership. Otherwise we return false.
The solution will work but it seems ugly to have to have manage 'own' permissions in the db.

Laravel: Get class name in policy

I am writing a general policy which which will apply on multiple Models. How can I retrieve the class name of the class which needs to be authorized?
Policies:
protected $policies = [
'App\User' => 'App\Policies\ModelPolicy',
'App\Customer' => 'App\Policies\ModelPolicy',
];
The ModelPolicy:
class ModelPolicy
{
use HandlesAuthorization;
/**
* Create a new policy instance.
*
* #return void
*/
public function __construct()
{
}
public function index(User $user){
// how can I retrieve the class name, like User or Customer?
return true;
}
}
This is for example my customer controller. So in the policy I want to retrieve something like: App\Customer.
class CustomerController extends Controller
{
public function index(){
$this->authorize('index', Customer::class);
echo "test";
}
}
You'll need custom gates.
In your controller:
$this->authorize('model-index', Appointment::first());
In AuthServiceProvider:
Gate::define('model-index', function ($user, $model) {
var_dump(get_class($model));
die();
});
This way you can take the parameters you need for your authorization methods.
Please see
https://laravel.com/docs/5.5/authorization#gates
Laravel Policies - How to Pass Multiple Arguments to function

Send user id and post id with comment

I am following a laravel tutorial and created a form to create a comment on a post with the user_id. I can't seem to understand how I pass the user_id.
Post Model
class Post extends Model
{
protected $guarded = [];
public function comments()
{
return $this->hasMany(Comment::class);
}
public function addComment($body)
{
$this->comments()->create(compact('body'));
}
public function user()
{
return $this->belongsTo(User::class);
}
}
CommentModel
class Comment extends Model
{
protected $guarded = [];
public function post()
{
$this->belongsTo(Post::class);
}
public function user()
{
$this->belongsTo(User::class);
}
}
User Model
class User extends Authenticatable
{
use Notifiable;
/**
* The attributes that are mass assignable.
*
* #var array
*/
protected $fillable = [
'name', 'email', 'password',
];
/**
* The attributes that should be hidden for arrays.
*
* #var array
*/
protected $hidden = [
'password', 'remember_token',
];
public function posts()
{
return $this->hasMany(Post::class);
}
public function comments()
{
return $this->hasMany(Comment::class);
}
public function publish(Post $post)
{
$this->posts()->save($post);
}
}
CommentsController.php
class CommentsController extends Controller
{
public function store(Post $post)
{
$this->validate(request(), ['body' => 'required|min:2']);
$post->addComment(request('body'));
return back();
}
}
As you can see I call ->addComment in the Post Model to add the comment. It worked fine untill I added user_id to the Comments table. What is the best way to store the user id? I can't get it to work.
Update your addComment method :
public function addComment($body)
{
$user_id = Auth::user()->id;
$this->comments()->create(compact('body', 'user_id'));
}
PS : Assuming that the user is authenticated.
UPDATE
public function addComment($body)
{
$comment = new Comment;
$comment->fill(compact('body'));
$this->comments()->save($comment);
}
Create a new instance of the comment without savingit and you only need to save a comment in the post because a post already belongs to a user
There is no need to handle ids manually, let eloquent handle it for you:
$user = Auth::user(); // or $request->user()
$user->comments()->save(new Comment());
more information about saving eloquent models.

Categories