I'm trying to set up a policy where only agents can view their properties, and eventually only agents should be able to update their own properties. I want to also make it so that administrators will be able to go in and access ALL properties. Right now, I can't seem to do it and I don't know what I'm doing wrong.
// this is my controller method
public function index($id)
{
$this->authorize('view', $id);
$properties = Property::where('agent_id', $id)->get();
return view('agents.index', compact('properties'));
}
// this is in my PropertyPolicy
public function view(User $user, Agent $agent)
{
return $agent->email_address == $user->email_address;
}
use App\Property;
use App\User;
public function view(User $user, Property $property)
{
return $user->id === $property->agent_id;
}
You can check that the id of the current authenticated user matches the agent_id of the property (assuming this is how you setup the foreign key and relationship).
I am not sure if you are using the User model for authentication or the Agent model.
To allow admins to just pass through you can create a before method:
public function before($user, $ability)
{
// how ever you determine if they are an admin
if ($user->isAdmin()) {
return true;
}
}
Related
Almost for all models, the policy functions look like this example:
public function index(User $user)
{
return $user->hasPermission('view-post');
}
The hasPermission exists in the User model:
public function hasPermission($permission){
$roleId = Auth()->user()->role_id;
$id = $this->select('p.id')->join('role_permissions AS rp', 'rp.role_id', 'users.role_id')->join('permissions AS p', 'p.id', 'rp.permission_id')->where('rp.role_id', roleId)->where('permission', $permission)->get()->toArray();
return $id ? true : false;
}
Then in postController:
public function index(User $user){
if ($user->cannot('index', Post::class)) {
return redirect('/');
}
}
Is there a way to avoid repeating the policy example at the top for every model and every policy function?
I want to be able to use the method in both controller and view to check against permissions
I want to show the user role from my user role table, but I can't.
User.php
public function role()
{
return $this->belongsTo('App\Models\Designation');
}
UserController
public function index()
{
$user = User::all();
return view('dashboard2', compact('user'));
}
View
<h1>{{ Auth::user()->role->designation_name}}</h1>
You actually should have shared more details about the relationships. But I will give an answer based on an assumption. It seems like there is a one-to-one relationship betwen User and Role as well as Role and Designation. So you want to reach designation from user. Based on that:
//User.php
public function role()
{
return $this->hasOne(Role::class);
}
//Role.php
public function user()
{
return $this->belongsTo(User::class);
}
public function designation()
{
return $this->hasOne(Designation::class);
}
//Designation.php
public function role()
{
return $this->belongsTo(Role::class);
}
// Controller
public function index()
{
// If you need only the auth user's data, you don't
// need adding relationships into the query.
$users = User::with('role.desgination')->all();
return view('dashboard2', compact('users'));
}
// View
// For auth user:
// I haven't used this way before,
// but technically it should work.
auth()->role()->designation()->name;
// For users collection:
// Make sure you added the query to the controller.
#foreach($users as $user)
$user->role->desgination->name
#endforeach
I have a laravel app using Policies to assign roles and permissions, i cant seem to access the show page and im not sure what im doing wrong?
If i set return true it still shows a 403 error as well, so im unsure where im going wrong here. The index page is accessable but the show page is not?
UserPolicy
public function viewAny(User $user)
{
if ($user->isSuperAdmin() || $user->hasPermissionTo(44, 'web')) {
return true;
}
return false;
}
public function view(User $user, User $model)
{
if ($user->isSuperAdmin() || $user->hasPermissionTo(44, 'web')) {
return true;
}
return false;
}
UserController
public function __construct()
{
$this->authorizeResource(User::class, 'user');
}
public function index()
{
$page_title = 'Users';
$page_description = 'User Profiles';
$users = User::all();
return view('pages.users.users.index', compact('page_title', 'page_description', 'users'));
}
public function create()
{
//
}
public function store(Request $request)
{
//
}
public function show($id)
{
$user = User::findOrFail($id);
$user_roles = $user->getRoleNames()->toArray();
return view('pages.users.users.show', compact('user', 'user_roles'));
}
Base on Authorize Resource and Resource Controller documentation.
You should run php artisan make:policy UserPolicy --model=User. This allows the policy to navigate within the model.
When you use the authorizeResource() function you should implement your condition in the middleware like:
// For Index
Route::get('/users', [UserController::class, 'index'])->middleware('can:viewAny,user');
// For View
Route::get('/users/{user}', [UserController::class, 'view'])->middleware('can:view,user');
or you can also use one policy for both view and index on your controller.
I had an issue with authorizeResource function.
I stuck on failed auth policy error:
This action is unauthorized.
The problem was that I named controller resource/request param with different name than its model class name.
F. ex. my model class name is Acknowledge , but I named param as timelineAcknowledge
Laravel writes in its documentation that
The authorizeResource method accepts the model's class name as its first argument, and the name of the route / request parameter that will contain the model's ID as its second argument
So the second argument had to be request parameter name.
// Here request param name is timelineAcknowledge
public function show(Acknowledge $timelineAcknowledge)
{
return $timelineAcknowledge->toArray();
}
// So I used this naming here also
public function __construct()
{
$this->authorizeResource(Acknowledge::class, 'timelineAcknowledge');
}
Solution was to name request param to the same name as its model class name.
Fixed code example
// I changed param name to the same as its model name
public function show(Acknowledge $acknowledge)
{
return $acknowledge->toArray();
}
// Changed here also
public function __construct()
{
$this->authorizeResource(Acknowledge::class, 'acknowledge');
}
I looked over Laravel policy auth code and I saw that the code actually expects the name to be as the model class name, but I couldn't find it anywhere mentioned in Laravel docs.
Of course in most of the cases request param name is the same as model class name, but I had a different case.
Hope it might help for someone.
I have implemented multiple Auth guards in a Laravel 5.4 project (one of for admins and the other for regular users). This has worked successfully so far and both admins and users are able to log in. I am now trying to implement a Policy class that works for both Auth guards. This is because I have certain models that I want all administrators to edit and only users who own the model to be able to edit. So I have defined a policy with this method.
App\Policies\ModelPolicy
public function update(User $user, Model $model)
{
if ($user->id === $model->user_id) {
return true;
}
if (Auth::guard('admin')->check()) {
return true;
}
return false;
}
Then in whatever controller method I have for my model:
App\Http\Controllers\ModelController
public function update(Model $model)
{
$this->authorize('update', $model);
// update model
}
This works perfectly if a regular user is logged in. However, when an admin user is logged in, it doesn't even reach the policy (I know this from error logging). I am guessing that the Policy class does something to automatically deny a request if the default guard in Auth::check() fails. However, since it is valid for my users to have one of several guards (not just the default), I need to bypass this behavior.
I know I could implement the admin logic in my controller method and only use the policy if I know I am dealing with a non-admin:
public function update(Model $model)
{
if (!Auth::guard('admin')->check()) {
$this->authorize('update', $model);
}
// update model
}
However, this can quickly spiral out of control if my admin condition is more complicated than simply being logged in. More importantly, all of this logic belongs in a Policy, not muddying up my controller.
How is it possible to use the same Policy class for multiple authentication guards?
I ended up overriding the authorize method on the base controller class to make the correct Guard the default Guard. Then, the $user argument passed into my policy will be an instance of whichever Auth guard the current user is logged in as.
app/Http/Controllers/Controller.php
use Auth
class Controller extends BaseController
{
use DispatchesJobs, ValidatesRequests;
use AuthorizesRequests {
authorize as protected baseAuthorize;
}
public function authorize($ability, $arguments = [])
{
if (Auth::guard('admin')->check()) {
Auth::shouldUse('admin');
}
$this->baseAuthorize($ability, $arguments);
}
}
Now that the Policy will be passed in either my User model or my Admin model, I need to make sure that I remove the type-hinting for the argument and check the type of the model that is passed in. I don't need to do any Auth::check() because I know that the $user that is passed in must be a logged in user of the type that I want.
App\Policies\ModelPolicy
use App\User;
public function update($user, Model $model)
{
if ($user instanceof User) {
return $user->id == $userId;
}
// Is an Admin
return true;
}
And now I have access to desired Auth guard to do whatever I want with it in my Policy.
You can override the "authorize" method in your common controller (/app/Http/Controllers/Controller.php):
class Controller extends BaseController
{
use AuthorizesResources, DispatchesJobs, ValidatesRequests;
use AuthorizesRequests {
authorize as protected laravelAuthorize;
}
public function authorize($ability, $arguments = [])
{
if (!Auth::guard('admin')->check()) {
$this->laravelAuthorize($ability, $arguments);
}
}
}
I am trying to create a project to add edit contacts.
To restrict the user can add/edit their own contacts, So added policy ContactPolicy as below
<?php
namespace App\Policies;
use Illuminate\Auth\Access\HandlesAuthorization;
use App\User;
use App\Contact;
class ContactPolicy
{
use HandlesAuthorization;
/**
* Create a new policy instance.
*
* #return void
*/
public function __construct()
{
//
}
public function before($user, $ability)
{
if ($user->isAdmin == 1) {
return true;
}
}
public function add_contact(User $user)
{
return $user->id;
}
public function update_contact(User $user, Contact $contact)
{
return $user->id === $contact->user_id;
}
}
And registered in AuthServiceProvider as below
protected $policies = [
'App\Model' => 'App\Policies\ModelPolicy',
Contact::class => ContactPolicy::class,
];
To restrict adding of contact by current user I added Gate in my controller function as below without passing parameters
if (Gate::denies('add_contact')) {
return response('Unauthorized.', 401);
}
Even if current user tries to add contact, it shows Unauthorized message.
How will I solve this problem?
Policies are intended to have all authorization logic related to a certain class of resource in one place.
So, you define Contact::class => ContactPolicy::class meaning the ContactPolicy has all policies regarding Contacts. When you write Gate::denies('add_contact'), how could the framework know which policy to search? You have to pass an object of type Contact as second parameter in order to access the ContactPolicy.
Anyway, there is in fact a place to write authorization logic which is not particular to any class of resource. In the method boot of AuthServiceProvider you could add
$gate->define('add_contact', function ($user) {
return $user->id;
});
By the way, what's the intention with returning the user id? I think you just need to return a boolean.
Also, if you are checking the permission within a controller, you should just call $this->authorize('add_contact') and the controller itself will check and return a Forbidden response (for which the proper code is 403, not 401) if it fails, no need to return it yourself.