I'm using Laravel 5.5. All we know the resource method of Gate facade contains CRUD methods by default. By the way, I add a custom method into policy class using resource method as a third argument like this:
Registering Policy:
Gate::resource('users', 'App\Policies\UserPolicy', ['edit' => 'edit']);
Policy Class:
use HandlesAuthorization;
public function before(Admin $admin)
{
return $admin->isSuperAdmin();
}
/*public function view(Admin $admin)
{
return true;
}*/
public function edit()
{
return true;
}
But, this causes the default CRUD methods like view returns false. I use this abilities for certain routes using middleware helper.
Route::get('/view', 'UsersController#view')->middleware('admin-authorization:users.view');
Route::get('/edit/{id}', 'UsersController#edit')->middleware('admin-authorization:users.edit');
So, admin-authorization is like this:
Auth::shouldUse('web_admin');
if (Gate::denies($ability)) {
return abort(403);
}
return $next($request);
The default method and custom works fine on absence of each other. When I comment the view method, the edit method works fine and vice versa, and the before method is being called in two cases.
What should I do that the default and custom method work fine with each other? any help will be appreciated.
I know it's weird, But i found the solution!!
we must pass an array into the resource as third argument that contains both custom and default methods like below:
Gate::resource('users', 'App\Policies\UserPolicy', ['view' => 'view'/*default*/, 'edit' => 'edit'/*custom*/]);
It's done well.
Related
I am trying to define some policies in Laravel 8 which I cannot get to work, however I have the same project in Laravel 7 which seemingly works perfectly.
I am using the JSON API Specification package and it comes built in with Authorizers which allow me to run a policy on different methods.
I am trying to add a policy for the 'create' on all routes no matter what.
I have the following code:
public function create($type, $request)
{
$this->authorize('create', $type);
}
In this context and example, $type = 'App\Models\User' if I do a dd before that line I can confirm that I am hitting that method.
Inside of my AuthServiceProvider I have the following:
public function boot()
{
Gate::guessPolicyNamesUsing(function ($modelClass) {
return 'App\\Policies\\' . class_basename($modelClass) . 'Policy';
});
}
Which as said earlier, works perfectly in another project.
The following is my policy, as you can see it's very basic.
<?php
namespace App\Policies;
use App\Models\User;
class UserPolicy
{
public function create(User $user)
{
return true;
}
}
If I make a constructor in the policy class I can confirm that it is getting hit and I am getting inside of the policy which is why this is confusing me so much.
I have tried changing the name of the method in case it was something clashing with the naming convention but nothing seems to agree with it.
I have tried to dump composer just as a double check but again, no luck.
The issue with this is that there wasn't currently an authenticated user and although specified in the method parameters a user it was still failing.
When providing a guest route, you still need to add the parameter to the method but make it optional.
public function create(?User $user)
{
// do logic here
}
The documentation for this can be found at the following link: https://laravel.com/docs/master/authorization#guest-users
I'm using Laravel 5.5 and I'm trying to use Gate facade to allow admins to access resources like users. First, I define a gate in AuthServiceProvider.php like following:
Gate::define('view-users', 'App\Policies\UserPolicy#view');
Then, I write view method in Policy class like this:
public function view(Admin $admin, User $user)
{
return true;
}
And, I apply the authorization like following:
//UsersController.php
$user = User::first();
if (Gate::allows('view-users', $user)) {
$users = User::all();
return view('admin.users.list', compact('users'));
}
return abort(403);
I note that, the $user argument is useless variable and I don't need it to perform authorization.
By the way, when I use allows() method of Gate facade, it always returns false. While, when I use denies() instead, these steps work fine.
what's wrong with allows() method?!
However, corresponding to the Laravel Docs, I tested other ways to apply authorization via middleware(), Model or authorize(). But, I got the same result.
Edit:
I should note that I'm using custom guard named web_admin
Thanks for any help.
Change your policy method to this:
public function view(User $user)
{
return $user->isAdmin;
}
The first argument of the policy method is always the current authenticated user. Note that you are not required to pass the currently authenticated user to these methods. Laravel will automatically take care of passing the user into the gate Closure:
if (Gate::allows('view-users')) {
// The current user can view all users...
}
If you want to check if the current user can update a specific user your policy method would be:
public function update(User $authenticatedUser, User $beeingEditedUser)
{
return $authenticatedUser->isAdmin;
}
Then authorize like this:
if (Gate::allows('update-user', $beeingEditedUser)) {
// The current user can update the user...
}
If you're using custom guard (according to your comment), you may have 2 options:
Use forUser method:
use Illuminate\Support\Facades\Auth;
if (Gate::forUser(Auth::guard('web_admin')->user())->allows('view-users')) {
// The current user can view all users...
}
Protecting the routes, specifying the guard:
Route::middleware('auth:web_admin')->group(function () {
Route::get('/users', 'UserController#index');
});
This causes Larvel to set your default auth driver and resolve the auth user based on your custom guard.
In Laravel, we can get route name from current URL via this:
Route::currentRouteName()
But, how can we get the route name from a specific given URL?
Thank you.
A very easy way to do it Laravel 5.2
app('router')->getRoutes()->match(app('request')->create('/qqq/posts/68/u1'))->getName()
It outputs my Route name like this slug.posts.show
Update: For method like POST, PUT or DELETE you can do like this
app('router')->getRoutes()->match(app('request')->create('/qqq/posts/68/u1', 'POST'))->getName()//reference https://github.com/symfony/http-foundation/blob/master/Request.php#L309
Also when you run app('router')->getRoutes()->match(app('request')->create('/qqq/posts/68/u1', 'POST')) this will return Illuminate\Routing\Route instance where you can call multiple useful public methods like getAction, getValidators etc. Check the source https://github.com/illuminate/routing/blob/master/Route.php for more details.
None of the solutions above worked for me.
This is the correct way to match a route with the URI:
$url = 'url-to-match/some-parameter';
$route = collect(\Route::getRoutes())->first(function($route) use($url){
return $route->matches(request()->create($url));
});
The other solutions perform bindings to the container and can screw up your routes...
I don't think this can be done with out-of-the-box Laravel. Also remember that not all routes in Laravel are named, so you probably want to retrieve the route object, not the route name.
One possible solution would be to extend the default \Iluminate\Routing\Router class and add a public method to your custom class that uses the protected Router::findRoute(Request $request) method.
A simplified example:
class MyRouter extends \Illuminate\Routing\Router {
public function resolveRouteFromUrl($url) {
return $this->findRoute(\Illuminate\Http\Request::create($url));
}
}
This should return the route that matches the URL you specified, but I haven't actually tested this.
Note that if you want this new custom router to replace the built-in one, you will likely have to also create a new ServiceProvider to register your new class into the IoC container instead of the default one.
You could adapt the ServiceProvider in the code below to your needs:
https://github.com/jasonlewis/enhanced-router
Otherwise if you just want to manually instantiate your custom router in your code as needed, you'd have to do something like:
$myRouter = new MyRouter(new \Illuminate\Events\Dispatcher());
$route = $myRouter->resolveRouteFromUrl('/your/url/here');
It can be done without extending the default \Iluminate\Routing\Router class.
Route::dispatchToRoute(Request::create('/your/url/here'));
$route = Route::currentRouteName();
If you call Route::currentRouteName() after dispatchToRoute call, it will return current route name of dispatched request.
I try to set some specific filter on all controller methods with:
public function __construct() {
$this->beforeFilter(function(){
//whathever
});
}
and it's working well on normal GET methods, problem occures when there is some POST method:
Route::post('settings/menu-order/{direction}', array(
'as' => 'setting.menu-order.move',
'uses' => function($direction) {
$controller = new CMSSettingsController();
return $controller->doMoveMenu($direction);
}));
after click in a button which send POST with $direction, I'v got
Call to a member function filter() on a non-object
in vendor/laravel/framework/src/Illuminate/Routing/Controller.php
protected function registerClosureFilter(Closure $filter)
{
$this->getFilterer()->filter($name = spl_object_hash($filter), $filter);
return $name;
}
If I use already registred filter it's working, so what's going on?
I have few controllers which need specific function todo before running controller methods, so I can't make global universal filter. Is there any other good solution?
The problem could be that you are calling the controller action directly instead of letting the Router do it for you. When the router tries to apply the filters, instead of applying them on the controller, it ends up attempting to apply them on the output of the doMoveMenu action - which, of course, is not a Controller object and has no method filter.
Instead, your route should look like this:
Route::post('settings/menu-order/{direction}', array(
'as' => 'setting.menu-order.move',
'uses' => 'CMSSettingsController#doMoveMenu'));
The reason you don't need to do the method call manually is that since your Route has a parameter in it and your method accepts a parameter, the Router will automatically pass the parameter into the action method. Additionally, since you are providing a method name as the uses value, Laravel knows that it has to instantiate the Controller and run the filters.
I'm building my frontend part of the API system.
Using default Resource Controller's by Laravel you can achieve this type of requests:
http://superapi.com/v1/soccer/10
Which will call the:
/app/controllers/SoccerController.php # show($id) method
I need at least one more level of depth, to be able to do it like this:
http://superapi.com/v1/soccer/player/10
So it will resolve to:
/app/controllers/SoccerController.php # show() method having both, "player" and "10" arguments.
public function show($model)
{
return Response::json(array('success' => true, 'message' => 'Test ' . $model));
}
Laravel passes only 1 parameter to method, and I cannot find anything about how to pass more.
I'm doing this basically because my 1 controller is responsible for talking to a few Models, not just one. Either SoccerPlayer or SoccerTeam or SoccerSchedule, it all should be nicely put under
/v1/<controller>/<type>/<id>
Advices?
Thanks!
You can have the best of both worlds anyway, by placing a custom route before the resource route:
Route::get('soccer', 'SoccerController#show');
Route::resource('soccer', 'SoccerController', array('except' => array('show')));
Now the show() method will be escluded from the resource controller control (thus calling soccer/10 or soccer/player will lead to a NotFoundHttpException), and placed into your custom route.
You'll need to edit the show() method to accept the second parameter though:
public function show($type, $id)
{
}
you can use route prefixing
Route::group(array('prefix' => 'soccer'), function()
{
Route::resource('player', 'PlayerController');
});
then, you can put all soccer relavant controllers in the same namespace (and same folder) like Soccer. in this case, change the above route to
Route::resource('player', 'Soccer\PlayerController');