Laravel: Pass Model Binding to Route Middleware - laravel

I have routes that I'm grouping by Location. I need to authorize whether the current user can actually access a particular Location.
I'm wrapping routes in:
Route::group(['prefix' => "{location}", 'middleware' => "has-location-access:location"], function() {
...
My middleware handle method is as follows:
public function handle(Request $request, Closure $next, Location $location)
{
$account = Account::find($request->session()->get('account_id'));
$this->authorize('manageLocation', [$account, $location]);
return $next($request);
}
Instead of the model being passed into this method, I get a string of "location"
App\Http\Middleware\AuthorizeLocationAccess::handle(): Argument #3 ($location) must be of type App\Location, string given
How can I simply have it pass the Location $location into the handle method?

Try this:
Location::find($request->route()->location)

Related

laravel Localization per user

How do I change the language of each user? For example, some people don't change the language. Some people change the language.
Middleware :
use Closure;
use Auth;
class Localization
{
public function handle($request, Closure $next)
{
if(\Session::has('locale')) {
\App::setLocale(\Session::get('locale'));
}
return $next($request);
}
}
Save the locale for each user in database. This way you can override app's default locale to user's preferred locale in the middleware.
public function handle($request, Closure $next)
{
if($user = Auth::user()) {
App::setLocale($user->locale);
}
return $next($request);
}
If the your application doesn't require user to be authenticated, you can save locale in session for each user when the user change language.
Your method is right, no need to change the middleware. You can put the session on user with controller, like this below way.
Route :
Route::get('/lang',[
'uses' => 'HomeController#lang',
'as' => 'lang.index'
]);
Controller :
public function lang(Request $request)
{
$user = Auth::user()->id;
$locale = $request->language;
App::setLocale($locale);
session()->put('locale', $locale);
// if you want to save the locale on your user table, you can do it here
return redirect()->back();
}
Note : I added the GET method on route, so your URI will be like http://127.0.0.1:8000/lang?language=en, and o controller $request->language will catch the language parameter from your Request. You can modify and use POST method instead

Laravel 6 perform role based routing

I have a route group with different routes. I want to have different role levels access without changing the URL of the application.
For example I want to have /admin as the route and then I want to allow or disallow users based on their roles. Basically, I want every user to be able to see the same page but with different menu options(I know how to do this) but also secure the links from direct access.
Is there a nice way to achieve that without the need of using different middlewares seperately on each route? Since there doesn't seem to be a way to retrieve the $request variable inside the web.php file but only inside a controller. I'm using the sentinel package for auth.
Some sample code of my web.php:
Route::group(
['prefix' => 'admin', 'middleware' => 'customer', 'as' => 'admin.'],
function () {
// Ad list
Route::get('getMyAnnonsList', 'Admin\BackEndController#getMyAdList')->name('getMyAdList');
}
);
Great answer by #lagbox. This is what I did in the end. Very elegant.
web.php:
Route::group(['prefix' => 'admin', 'as' => 'admin.'], function () {
Route::middleware('admin:admin,user')->group(function(){
Route::get('getMyAnnonsList', 'Admin\BackEndController#getMyAdList')->name('getMyAdList');
});
});
middleware:
public function handle($request, Closure $next, ...$roles)
{
if (!Sentinel::check())
return redirect('admin/signin')->with('info', 'You must be logged in!');
foreach($roles as $role)
if($role == Sentinel::getUser()->roles[0]->slug)
return $next($request);
return redirect()->back();
}
I had already answered something like this before, should be working the same still.
You can create a middleware that can be applied to your group. In that middleware it is asking the route itself for the specific roles to check.
How to assign two middleware to the same group of routes. Laravel
Example of middleware:
class CheckMiddleware
{
public function handle($request, Closure $next)
{
$roles = $request->route()->getAction('roles', []);
foreach ((array) $roles as $role) {
// if the user has this role, let them pass through
if (...) {
return $next($request);
}
}
// user is not one of the matching 'roles'
return redirect('/');
}
}
Example route definition:
Route::middleware('rolescheck')->group(function () {
Route::get('something', ['uses' => 'SomeController#method', 'roles' => [...]])->name(...);
});
You can apply this arbitrary data at the group level, the individual route level or both, as all routes are individually registered; groups just allow for cascading of configuration.
You could also have this middleware take parameters, and just merge them with the arbitrary roles, then it is a dual purpose middleware:
public function handle($request, $next, ...$roles)
{
$roles = array_merge($roles, $request->route()->getAction('roles', []));
...
}
Route::middleware('rolescheck:admin,staff')->group(...);
You can use Laravel Gate And Policies
You can define the gate inside the App > Providers > AuthServiceProvider
and you can also create policies per CRUD. just see info in php artisan help make:policy. This will create a folder in your app called policies you can define the who can access it.
In your controller you can do is this: (this is a gate middleware)
I define the gate first:
Gate::define('check', function ($user, $request) {
return $user->roles->contains('name', $request) || $user->roles->contains('name', 'root');
});
then I initialise it in the controller
abort_if(Gate::denies('check', 'admin only'), 403);
This will throw 403 error if the user don't have access on that role. It will check if the user has admin only role. If it doesn't have it will throw the error
In your view if you want to disable anchor links you can do like this:
#can('check', 'admin only')
dashboard
#endcan
EDIT:
Controller
public function index() {
abort_if(Gate::denies('check', 'admin only'), 403);
// Your Code...
}

how to send id in route and controller in laravel localization

In my Laravel project with localization I made middleware, route group and all parameters, language switch work correct but when I click to send id by
I get the error:
Missing required parameters for [Route: products] [URI:
{lang}/products/{id}]
My Routes:
Route::group(['prefix' => '{lang}'], function () {
Route::get('/', 'AppController#index')->name('home');
Route::get('/categories', 'AppController#categories')->name('categories');
Route::get('products/{id}', 'AppController#products')->name('products');
Auth::routes();
});
My Middleware:
public function handle($request, Closure $next)
{
\App::setLocale($request->lang);
return $next($request);
}
My AppController:
public function products($id)
{
$products = Category::with('products')->where('id', $id)->get();
return view('products', compact('products'));
}
this is the URL:
http://127.0.0.1:8000/fa/products/1
if I change the above URL manually it works and shows the page:
http://127.0.0.1:8000/1/products/1
But if I click on:
I receive the error.
Since you added a route prefix the first parameter of the products method in your controller will be lang and the second one id.
This should fix the controller:
public function products($lang, $id)
{
$products = Category::with('products')->where('id', $id)->get();
return view('products', compact('products', 'lang'));
}
You need to use a key-value array in route('products', ['lang'=>app()->getLocale(), 'id'=>$category->id]) or whatever your route parameters are named in the original route.
Ref. Laravel Named Routes
PS. as Remul notes, since you have a lang param (as route prefix) the first param in your controller will be $lang then $id
public function products($lang, $id)
{
$products = Category::with('products')->where('id', $id)->get();
return view('products', compact('products'));
}

Access #store arguments in "create policy"

I have a route like this in routes/api.php:
Route::group(['middleware' => 'auth:api'], function() {
Route::post('messages/{pet}', 'MessageController#store')->middleware('can:create,message');
});
We see here that it has implicit {pet}.
My controller accesses {pet} just fine like this:
app\Http\Controllers\MessageController.php:
public function store(Request $request, Pet $pet)
{
dd($pet);
}
I want to my ->middleware('can:create,message') to get the arguments of store seen here, so I want $request and $pet, is this possible?
Here is my current MessagePolicy#create but its not getting the arguments I expect:
app\Policies\MessagePolicy.php
public function create(User $user, Request $request, Pet $pet)
{
dd($request); // dd($pet);
return $user->can('view', $pet) && ($request->input('kind') == null|| $request->input('kind') == 'PLAIN');
}
Also dd is not working for some reason.
Assuming you want create a Pet for a given message, in this case the implicit model binding will not work here because the pet not yet created so finding a pet by the given id will always return null.
In this case laravel offer the possibility to use Actions That Don't Require Models (see documentation -> Via Middleware section)
Again, some actions like create may not require a model instance. In
these situations, you may pass a class name to the middleware. The
class name will be used to determine which policy to use when
authorizing the action
So in your case :
Route::group(['middleware' => 'auth:api'], function() {
Route::post('messages/{pet}', 'MessageController#store')->middleware('can:create,App\Pet');
});
And in the PetPolicy you can use the request() helper method :
public function create(User $user)
{
return request('kind') == null|| request('kind') == 'PLAIN';
}
You could use the request() helper method.
https://laravel.com/docs/5.5/helpers#method-request
The $request have a method has() for determining if a value is present (Link).
You can alter your method to check if the value exists or its equals to "PLAIN"
public function create(User $user, Request $request)
{
return !$request->has('kind') || $request->input('kind') == 'PLAIN';
}
use
return ( $request->has('kind') )? $request->has('kind') && $request->input('kind') === 'PLAIN': true;

Laravel 5 : passing a Model parameter to the middleware

I would like to pass a model parameter to a middleware. According to this link (laravel 5 middleware parameters) , I can just include an extra parameter in the handle() function like so :
public function handle($request, Closure $next, $model)
{
//perform actions
}
How would you pass it in the constructor of the Controller? This isn't working :
public function __construct(){
$model = new Model();
$this->middleware('myCustomMW', $model);
}
**NOTE : ** it is important that I could pass different Models (ex. ModelX, ModelY, ModelZ)
First of all make sure that you're using Laravel 5.1. Middleware parameters weren't available in prior versions.
Now I don't believe you can pass an instantiated object as a parameter to your middleware, but (if you really need this) you can pass a model's class name and i.e. primary key if you need a specific instance.
In your middleware:
public function handle($request, Closure $next, $model, $id)
{
// Instantiate the model off of IoC and find a specific one by id
$model = app($model)->find($id);
// Do whatever you need with your model
return $next($request);
}
In your controller:
use App\User;
public function __construct()
{
$id = 1;
// Use middleware and pass a model's class name and an id
$this->middleware('myCustomMW:'.User::class.",$id");
}
With this approach you can pass whatever models you want to your middleware.
A more eloquent way of resolving this problem is to create a constructor method in the middleware, inject the model(s) as dependencies, pass them to class variables, and then utilize the class variables in the handle method.
For authority to validate my response, see app/Http/Middleware/Authenticate.php in a Laravel 5.1 installation.
For middleware MyMiddleware, model $myModel, of class MyModel, do as follows:
use App\MyModel;
class MyMiddleware
{
protected $myModel;
public function __construct(MyModel $myModel)
{
$this->myModel = $myModel;
}
public function handle($request, Closure $next)
{
$this->myModel->insert_model_method_here()
// and write your code to manipulate the model methods
return $next($request);
}
}
You don't need to pass the model to middleware, Because you already have access to model instance inside the middleware!
Lets say we have a route like this:
example.test/api/post/{post}
now in our middleware if we want to have access to that post dynamically we go like this
$post = $request->route()->parameter('post');
now we can use this $post, for example $post->id will give us the id of the post, or $post->replies will give us the replies belong to the post.

Resources