I'm trying to make this policy stuff work, and i have followed the documentation. But it doesn't seem like the policy code is not even run.
I have Role model. And i created RolePolicy. The thing i want to do in the policy is to ensure that the role with the ID of 1 never-ever gets updated or deleted.
My RolePolicy looks like this:
<?php
namespace App\Policies;
use App\Models\Role;
use Illuminate\Support\Facades\Response;
class RolePolicy
{
/**
* Determine whether the user can update the model.
*
* #param \App\User $user
* #param \App\Models\Role $role
* #return mixed
*/
public function update(Role $role)
{
return $role->id === 1
? Response::deny('Cannot change super-admin role')
: Response::allow();
}
/**
* Determine whether the user can delete the model.
*
* #param \App\User $user
* #param \App\Models\Role $role
* #return mixed
*/
public function delete(Role $role)
{
return $role->id === 1
? Response::deny('Cannot delete super-admin role')
: Response::allow();
}
}
I even tried to do a dd() inside both delete and update method in the policy, but when i try to delete/update the model with the ID of 1, nothing happens. The dd wont run, nor will the response in the current code above.
I have registered the policy in the AuthServiceProvider where i also have this gate to give the super-admin all the permissions.
<?php
namespace App\Providers;
use App\Models\Role;
use App\Policies\RolePolicy;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
use Illuminate\Support\Facades\Gate;
class AuthServiceProvider extends ServiceProvider
{
/**
* The policy mappings for the application.
*
* #var array
*/
protected $policies = [
Role::class => RolePolicy::class
];
/**
* Register any authentication / authorization services.
*
* #return void
*/
public function boot()
{
$this->registerPolicies();
// Implicitly grant "Super Admin" role all permissions
// This works in the app by using gate-related functions like auth()->user->can() and #can()
Gate::before(function($user, $ability) {
return $user->hasRole('super-admin') ? true : null;
});
}
}
Here is also my RoleController method for updating the Role model:
/**
* Edit role
*
* #param Edit $request
* #param Role $role
* #return void
*/
public function postEdit(Edit $request, Role $role)
{
# Validation checks happens in the FormRequest
# Session flash also happens in FormRequest
# Update model
$role->update([
'name' => $request->name
]);
# Sync permissions
$permissions = Permission::whereIn('name', $request->input('permissions', []))->get();
$role->syncPermissions($permissions);
return redirect(route('dashboard.roles.edit.get', ['role' => $role->id]))->with('success', 'Changes saved');
}
Does the gate i use to give all permissions have anything to do with the policy not running? Or what am i doing wrong here?
Thanks in advance if anyone can point me in the right direction.
The User model that is included with your Laravel application includes two helpful methods for authorizing actions: can and cant. The can method receives the action you wish to authorize and the relevant model. For example, let's determine if a user is authorized to update a given Role model:
if ($user->can('update', $role)) {
//
}
If a policy is registered for the given model, the can method will automatically call the appropriate policy and return the boolean result. If no policy is registered for the model, the can method will attempt to call the Closure based Gate matching the given action name.
Via Controller Helpers
In addition to helpful methods provided to the User model, Laravel provides a helpful authorize method to any of your controllers which extend the App\Http\Controllers\Controller base class. Like the can method, this method accepts the name of the action you wish to authorize and the relevant model. If the action is not authorized, the authorize method will throw an Illuminate\Auth\Access\AuthorizationException, which the default Laravel exception handler will convert to an HTTP response with a 403 status code:
<?php
namespace App\Http\Controllers;
use App\Http\Controllers\Controller;
use App\Role;
use Illuminate\Http\Request;
class RoleController extends Controller
{
/**
* Update the given role.
*
* #param Request $request
* #param role $role
* #return Response
* #throws \Illuminate\Auth\Access\AuthorizationException
*/
public function update(Request $request, Role $role)
{
$this->authorize('update', $role);
// The current user can update the role...
}
}
The Gate::before method in the AuthServiceProvider was the problem. Removed this and rewrote the permissions, policies and some gates to get the error messages from the policies.
Decided to give the role super-admin the permission * and check for this with $user->can() and middleware .....->middlware('can:*') and everything is working now.
Related
Greetings. I'm having difficulties to use Policy Laravel 9. I have tried all day to figuring this out why POLICY class is not hitting by controller / route but could not find solution that. All possible solutions which I understand already tried.
Here is my code.
Policy class:
class PacketPolicy
{
use HandlesAuthorization;
/**
* Determine whether the user can create models.
*
* #param \App\Models\User $user
* #return \Illuminate\Auth\Access\Response|bool
*/
public function create(User $user)
{
dd('hit packet create');
}
}
And controller class
class PacketController extends Controller
{
/**
* Create the controller instance.
* Also check authorization on route level as per associated policies
*
* #return void
*/
public function __construct()
{
$this->authorizeResource(Packet::class, 'packet'); // Option-1 tried. Not working
}
public function create()
{
//request()->user()->can('create', Packet::class); // Option-2 tried. Not working
return inertia('Packet/Create', [
'item' => [],
]);
}
}
and even register policy classes in AuthServiceProvider class like so.
class AuthServiceProvider extends ServiceProvider
{
/**
* The model to policy mappings for the application.
*
* #var array<class-string, class-string>
*/
protected $policies = [
'App\Models\Packet' => 'App\Policies\PacketPolicy', // Not working
OutboundOrder::class => OutboundOrderPolicy::class, // Not working either
];
}
Route is
Route::resource('packet', PacketController::class);
Please help me out what i'm missing here. I don't understand why my Policy class not hitting when in visit domain/packet/create route
Today I am facing a problem. I have several levels of authentication on my site, and on my nova, I connect with the Administrators table. How can I use it to declare Policies other than with User?
I tried to do it like this:
/**
* Determine whether the user can view any models.
*
* #param Administrator $admin
* #return mixed
*/
public function viewAny(Administrator $admin)
{
return $admin->superadmin == 1;
}
The problem being that I get an error telling me that I'm not using the User model when I'm waiting for it? How can I fix this problem? I would like to give access to a nova page only to administrators who have the "superadmin" column on 1...
My table looks like this. It's named "administrators".
I've been stuck for quite a long time on this problem without really finding solutions... Do I have to use the User model?
You can make use of Guest users
From laravel documentation
Guest Users
By default, all gates and policies automatically return false if the incoming HTTP request was not initiated by an authenticated user. However, you may allow these authorization checks to pass through to your gates and policies by declaring an "optional" type-hint or supplying a null default value for the user argument definition:
<?php
namespace App\Policies;
use App\Models\Post;
use App\Models\User;
class PostPolicy
{
/**
* Determine if the given post can be updated by the user.
*
* #param \App\Models\User $user
* #param \App\Models\Post $post
* #return bool
*/
public function update(?User $user, Post $post)
{
return optional($user)->id === $post->user_id;
}
}
Im using Laravel 5.4 i have created the auth system guard using the laravel stuff, i have also created a role based registration form.
At this point all works so far, but for example i want to only certains pages to be available only for admin, so i have created a New User page to allow the admin create users.
My RegisteruserControoller:
namespace App\Http\Controllers;
use App\Proveedor;
use App\RubroProveedor;
use Illuminate\Http\Request;
use App\Http\Requests\StoreProveedor;
use App\Http\Requests\UpdateProveedor;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Redirect;
use Alert;
use Exception;
use Auth;
class RegisteruserController extends Controller
{
/**
* Create a new controller instance.
*
* #return void
*/
public function __construct() {
$this->middleware('role:admin');
}
public function index(Request $request)
{
return view('usuarios.registeruser');
}
My problem here is if i use the middleware in construct im always being asked to login again and again, and i need to disable that but keep the guard to allow only admin user create new users.
Im not sure if im clear, im sorry my bad english.
I have created a Middleware called CheckRole:
<?php
namespace App\Http\Middleware;
use Closure;
class CheckRole
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next, $role)
{
if (! $request->user()->hasRole($role)) {
abort(401, 'This action is unauthorized.');
}
return $next($request);
}
}
I'm building some API endpoints with Laravel, and I'm using JWTAuth as the token provider for authorizing requests.
I've gotten the setup to protect a group of API routes that works correctly using:
Route::group(['prefix' => '/v1', 'middleware' => 'jwt.auth'], function() {
Route::resource('messages', 'MessagesController');
});
The Message model belongs to a User
I'm attempting to perform some requests using that relationship while keeping the request from providing data that didn't belong to the user:
Get a list of the logged in user's messages
Get an individual message of the logged in user
The main question I have is how to prevent the user from accessing a Message that does not belong to them. I have this in my controller:
public function show($message_id)
{
$message = Message::findOrFail($message_id);
return $message;
}
That obviously will return the Message regardless of whether or not it belongs to that user. Any suggestions on how to improve that to restrict accessing other user's messages?
For the listing of all messages, I was able to do the following in the controller:
public function index()
{
$user_id = Auth::User()->id;
$messages = Message::where('user_id', $user_id)->paginate(10);
return $messages;
}
This works, but I'm not sure this is the best way to do it. Maybe it is, but some feedback would be appreciated. I'm confused as to whether or not the middleware should be handling what the user has access to or if it should be part of the eloquent query?
Your question is,
how to prevent the user from accessing a Message that does not belong
to them.
Thats falls under Authorization
You can use Gates to Authorize users and I think it's the best approach here. Fist of all you'd need a Policy object.
<?php
namespace App\Policies;
use App\User;
use App\Message;
class MessagePolicy
{
/**
* Determine if the given Message can be viewed by the user.
*
* #param \App\User $user
* #param \App\Message $Message
* #return bool
*/
public function view(User $user, Message $Message)
{
return $user->id === $Message->user_id;
// Here this User's id should match with the user_id of the Massage
}
}
You can even generate the boilerplate like this,
php artisan make:policy MessagePolicy --model=Message
Then you can register it in your AuthServiceProvider like this,
<?php
namespace App\Providers;
use App\Message;
use App\Policies\MessagePolicy;
use Illuminate\Support\Facades\Gate;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
class AuthServiceProvider extends ServiceProvider
{
/**
* The policy mappings for the application.
*
* #var array
*/
protected $policies = [
Message::class => MessagePolicy::class,
];
/**
* Register any application authentication / authorization services.
*
* #return void
*/
public function boot()
{
$this->registerPolicies();
Gate::define('view-message', 'MessagePolicy#view');
}
}
And in your Controller you can use it like this,
public function show($message_id)
{
if (Gate::allows('view-message', Message::findOrFail($message_id)) {
return $message;
}
}
Note: Please use this code snippet as a reference or a starting point since I haven't tested it properly. But underlying concept is correct. Use this as pseudocode. If something wrong found, please update it here :)
I'm quite new to Laravel and have been stumped on a problem for 2 days - I'd be grateful for some guidance.
I'm using the default out-of-the-box User authentication system with Laravel 5.3. A new user is created automatically behind the scenes by an existing Admin user - I will in time hide the user registration page. I have also successfully set up middleware to check if a user is newly registered (by looking for a null 'last_logged_in_date' that I've added to the migration).
All I want to happen is for a new registered user to be redirected to the password reset screen that ships with Laravel (again, in time I will create a dedicated page). I would like this to happen within the middleware file. So far, my middleware looks like this:
<?php
namespace App\Http\Middleware;
use Closure;
use App\Http\Controllers\Auth;
class CheckIfNewUser
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next)
{
$user = $request->user();
if (! is_null($user->last_logged_in_date )) {
return $next($request);
}
// This is where I'm stuck!!!
}
}
I'm not sure what code to enter at the location indicated by the comments above. I've tried sendResetLinkEmail($request); etc and have imported what I though were the correct classes but I always end up with a Call to undefined function App\Http\Middleware\sendResetLinkEmail() message irregardless of what I 'use' at the top of my class.
Where am I going wrong? Thanks!
Well that happens because you have not defined your sendResetLinkEmail($request) function yet. You can do it like this, or you can create a new class with that and then call the class.
Call the trait SendsPasswordResetEmails and then access it with $this since traits are not classes and you cannot access their members directly.
<?php
namespace App\Http\Middleware;
use Closure;
use App\Http\Controllers\Auth;
use Illuminate\Foundation\Auth\SendsPasswordResetEmails;
class CheckIfNewUser
{
use SendsPasswordResetEmails;
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next)
{
$user = $request->user();
if (! is_null($user->last_logged_in_date )) {
return $next($request);
}
// This is where I'm stuck!!!
//EDIT
//return $this->SendsPasswordResetEmails->sendResetLinkEmail($request);
return $this->sendResetLinkEmail($request);
}
}