How to pass multiple string variables to a Laravel Gate - laravel

I have a “security” service which I want to gradually move over to a Laravel Gate, so I can benefit from the helper methods that Laravel provides within the rest of the APP.
I defined the gate as follows now:
Gate::define('denja', function($user, $module, $permission) {
// validation of access to $module and $permission goes here
});
This works fine when I do
$user->can('denja', ['accounting', 'invoice.create']);```
for instance, but I don’t see how in my routes, I can define the middleware to properly function...
Route::post( '/accounting/invoices', 'InvoiceController#create')
->middleware("can:denja,accounting,invoice.create");```
Passing these parameters seems to be impossible from the middleware - the page now always returns a 403...
Any thoughts on how I can pass these parameters correctly to the gate from the Middleware? I think it's in fact a problem with the parameters; even with a dd() in the defined gate, I'm getting the 403.
I know I’m a bit “abusing” the system, but since we have an existing service that basically expects a user, module and permission under that module, I just want to delegate to that service for now...

When you are using can middleware :
The first is the name of the action we wish to authorise and the later is the route parameter we wish to pass to the policy method or a Model class path. documentation
For example :
Route::put('/post/{postId}', function (Post $post) {
// The current user may update the post...
})->middleware('can:update,postId');
OR
Route::post('/post', function () {
// The current user may create posts...
})->middleware('can:create,App\Post');
In your case :
Route::post( '/accounting/invoices', 'InvoiceController#create')
->middleware("can:denja,accounting,invoice.create");
which is missing the basic parameter signatures as there is no route param with name accounting or invoice.create nor a class.
Solution :
Remove middleware from route declaration :
Route::post( '/accounting/invoices', 'InvoiceController#create');
You can use can() method in your controller :
public function create(Request $request){
// Initialize $model and $permissions
// as per your business logic
if(!$request->user()->can('denja', $module, $permission){
abort(403);
}
// continue your logic for authorised user
}
Even if above solution works, if you have more authorisation rules, its better to make a policy class.

I to had this same problem so I did some digging into the 'can' middleware (Which maps to Illuminate\Auth\Middleware\Authorize)
Once in the class we see the following code
/**
* Get the model to authorize.
*
* #param \Illuminate\Http\Request $request
* #param string $model
* #return \Illuminate\Database\Eloquent\Model|string
*/
protected function getModel($request, $model)
{
if ($this->isClassName($model)) {
return trim($model);
} else {
return $request->route($model, null) ?:
((preg_match("/^['\"](.*)['\"]$/", trim($model), $matches)) ? $matches[1] : null);
}
}
What this means is...
If our string passed in is a class name then return that class name
If it is not a class name then...
1) Try to get it from the route, then return the route param
2) Try to get the model from the string via the regex "/^['\"](.*)['\"]$/"
So now lets say we have the middleware call of
$this->middleware(sprintf("can:create,%s,%s", User::class, Role::SUPPORT));
This will not work because the Role::SUPPORT does not match the regex
To match it we simply need to place the Role::SUPPORT into quotes.
TAKE NOTE OF THE "'" around the second %s
$this->middleware(sprintf("can:create,%s,'%s'", User::class, Role::SUPPORT));
To answer your question specifically, quote your string
Route::post('/accounting/invoices', 'InvoiceController#create')
->middleware("can:'denja','accounting','invoice.create'");

Related

Laravel Authorization can always return false for API

I am trying to use Laravel authorization policies with API and Sanctum. However, I use middleware on the route as follows.
Route::get('/user/orders/{order}',
[OrderController::class, 'get_user_order_detail'])
->middleware('can:view:order');
OrderPolicy.php
namespace App\Policies;
use App\Models\Order;
use App\Models\User;
use Illuminate\Auth\Access\HandlesAuthorization;
class OrderPolicy
{
use HandlesAuthorization;
/**
* Create a new policy instance.
*
* #return void
*/
public function __construct()
{
// dd(1);
}
public function view(User $user, Order $order): bool
{
return $user->id === $order->user_id;
}
}
As you see, when I add dd(1) inside the constructor of the OrderPolicy, then I get 1 as expected, but when I move it to the inside of view function, I get unauthorized which indicates that is maybe the view function itself is not being called, but, the OrderPolicy is getting called.
Your middleware definition is wrong:
->middleware('can:view:order')
it should be:
->middleware('can:view,order')
From the docs:
Laravel includes a middleware that can authorize actions before the incoming request even reaches your routes or controllers. By default, the Illuminate\Auth\Middleware\Authorize middleware is assigned the can key in your App\Http\Kernel class. Let's explore an example of using the can middleware to authorize that a user can update a post:
use App\Models\Post;
Route::put('/post/{post}', function (Post $post) {
// The current user may update the post...
})->middleware('can:update,post');
In this example, we're passing the can middleware two arguments. The
first is the name of the action we wish to authorize and the second is
the route parameter we wish to pass to the policy method. In this
case, since we are using implicit model binding, a App\Models\Post
model will be passed to the policy method. If the user is not
authorized to perform the given action, an HTTP response with a 403
status code will be returned by the middleware.

Testing Laravel API resources with dependency injections and custom requests

Type hinted route parameter does not instantiate when called from a test.
I have a Laravel API Resource Route::apiResource('users', 'Api\UserController');
Here's my update method in the controller:
public function update(UpdateUserRequest $request, User $user)
{
//
}
Inside the UpdateUserRequest:
public function rules()
{
dd($this->route("user"));
}
If I call this endpoint from Postman, I get the full user object back. However, if I call it from a test:
$response = $this->actingAs($this->user)->
json('POST', '/api/users/'.$this->user->id, [
'_method' => 'PUT',
'data' => [
// ...
]
]);
I just get the string "1", not the instantiated User object.
This is probably caused by the \Illuminate\Foundation\Testing\WithoutMiddleware trait being used by your test case.
For posterity, should anyone come across this, route model binding is performed by the \Illuminate\Routing\MiddlewareSubstituteBindings middleware. The WithoutMiddleware trait therefore prevents it from running.
The base Laravel test case provides an undocumented withoutMiddleware() method via /Illuminate/Foundation/Testing/WithoutMiddleware which you can use to get around this, however it may be worth noting that the lead developer of Laravel, Taylor Otwell, recommends testing with all middleware active when possible.
Well, one thing that worked, and I don't know if this is the correct or the "Laravel" way of doing things is to force instantiate the model in the custom request constructor, and to bind the instance inside the test:
In the UpdateUserRequest:
private $user;
public function __construct(User $user)
{
$this->user = $user;
}
In the Test:
$this->user = factory(\App\Models\User::class)->create();
$this->app->instance(\App\Models\User::class, $this->user);

Laravel preventing user from accessing other users resource **url

I am passing a specific resource in the url, for ex.
https://www.example.com/{companyID}
And in the controller I can access the resource by
public function index($companyID)
{
// Code Here
}
I need to block users from changing the url and accessing other companyIDs from the system. Currently its open and is a security risk. I checked out Laravel Gate and Policy's but fail to see how this could be implemented for my case.
What I am really looking for is something in the AuthServiceProvider boot method that can check if the user really is the owner of the resource before continuing with the code.
Any help?
As mentioned before, you can do that by creating a Middleware that checks if your resource should be available to the logged in user.
See some details about middleware here
First, create a Middleware via php artisan, like this
php artisan make:middleware AuthResource
Next, add it to your App\Http\Kernel.php
protected $routeMiddleware = [
...
'AuthResource' => \App\Http\Middleware\AuthResource::class,
];
In your routes, you can now do the following:
Route::get('{companyID}', ['uses' => CompanyController#index, 'middleware' => 'AuthResource']);
That way, your AuthResource middleware is used whenenver the route is called.
In your App\Http\Middleware\AuthResource.php you have to change the code from
public function handle($request, Closure $next)
{
return $next($request);
}
to something that checks if the resource is available to the currently logged in user.
I assume that your companies table has a field user_id, which links the Company to a User. If your data structure is different, you need to change the code accordingly.
public function handle($request, Closure $next)
{
if ($request->route('companyID')) {
$company = Company::find($request->route('companyID'));
if ($company && $company->user_id != auth()->user()->id) {
return redirect('/');
}
}
return $next($request);
}
That way we check if the a route parameter with the name companyID exists, and if it does we check if it is available to the currently logged in user. If no companyID parameter is available, the page can be loaded without any restrictions.
That way you can copy/paste the code within the middleware for any parameters so that the middleware does work for multiple resources (not only companies).
This is can be done easily by middleware. But I’ll do this in more understandable way.
I assume that your user has one to one relationship with company.
So first create the relationship,
In your User model,
Public function company() {
return $this->hasOne(‘App\Company’);
}
Company model
Public function user(){
return $this->belongsTo(‘App\User’);
}
So, now make Authenticate by running php artisan make:auth . more details on Authenticate
And now in your controller,
public function index($companyID)
{
$current_user = Auth::user();
$user_company = $current_user->company; // get the current user's company details
If($companyID == $user_company->id){
// do something
}
}

User in Laravel controllers

I have a system in laravel 5.3 that uses over 40 controllers and probably 200 views.
I am attempting to clean up the code and use best practice. Given that certain calls are made pretty much everywhere, it makes sense to define it somewhere "semi globally". I would assume this would be in the Controller from which all controllers extend.
One object is $user, and has child $user->organisations and $user->organisation->locations.
Loading this at a base controller (or equivalent) way would also give me the advantage that I could ensure child relationships were eager loaded in an optimal way ensuring any foreach style code never results in multiple small database lookups. There are a number of other items I want to do this for with similar ramifications for database optimisation. These all use the Auth::user(), and they affect permissions with child objects.
Given that there are about 20 properties/variables of use to be shared (all dependant on Auth::user()) removing this duplicated code from almost every method is a huge improvement.
My aim is to be able to reference $this->user from any controller, and already have pre-loaded all the child/related objects.
Laravel 5.3 re-organised the loading order, so sharing the logged-in user data as part of Controller::__construct is no longer feasible.
Here's the code attempted so far:
In Controller::__construct
$this->middleware(function ($request, $next) {
$this->user = Auth::user();
view()->share('user', $this->user);
return $next($request);
});
Unsurprisingly, this correctly sets the $user variable at View level, but not Controller level. While I do $user it at view level, this doesn't help.
Given that there are about 20 properties/variables of use to be shared (all dependant on Auth::user()) I decided that one Helper would at least move this into a centralised location. I instantiate the helper to be stored as property across all controllers : $this->authentication_helper
In an example controller: SearchController :
public function index(Request $request): View
{
$this->authentication_helper->getAuthenticationData($this);
//... logic for the search
}
With the AuthenticationHelper doing (amongst other things):
public function getAuthenticationData(Controller $controller) : void
{
$user = Auth::user();
$controller->user = User::with(
organisations.locations', // .. other children .. //
)->find($user->id);
// share to the view
View::share('user', $controller->user);
// ... other $controller property setting
return;
}
I am unsure as to whether this is best practice.
edit - A previously raised second issue been solved - the main question remains:
Is there a problem with this approach - what would be an equivalent way of moving these 20 or so variable assignments to a higher level.
You can get authorized user in controller using Laravel DI, simply your code should be something like this
YourControlle extends Controller
{
public function test(Request $request)
$user = $requset->user(); //use auth user
}
The best approach would be a helper (or multiple helpers, you can create a app\Helpers namespace for that) and have all your logic within it.
The Auth::user() will be accessible from that helper using the Auth Facade, and have your logic there.
An other simpler way is just extending your controllers from a custom base controller you make (which you're going to extend from Controller) and append a $user member to it with a protected visibility, and share it to the view, to be done with in the constructor or in a method to be called through parent::magicMethod()
EDIT
You can override the callAction method used by the controller class
/**
* Execute an action on the controller.
*
* #param string $method
* #param array $parameters
* #return \Symfony\Component\HttpFoundation\Response
*/
public function callAction($method, $parameters)
{
// insert your logic here
return call_user_func_array([$this, $method], $parameters);
}
The reason why you can't access the Auth via the constructor is because the session was not fired up yet. You can catch it when it does with this event listener :
Event::listen(Authenticated::class, function ($event) {
$this->user = $event->user;
});
not tested
A middleware with a closure would do the work too. Make it have your logic, and use it in all your controllers.

Is it possible to alter error message in Laravel's Validator callback?

I have a custom validator set-up like this:
Validator::extend('valid_username', 'ProfileController#valid_username');
Then I have the following method which handles the validation. It checks both if the username already exists, and if the username contains valid characters.
public function valid_username($attribute, $value, $parameters)
{
$u = User::where('username', $value)->get();
if ($u->count())
{
// here I would like to return "Username already taken."
return FALSE;
}
else if (preg_match("/^[A-Za-z0-9#\.\-_]+$/", $value))
{
return TRUE;
}
else
{
// here I would like to return "Username contains invalid characters."
return FALSE;
}
}
I would like to alter the error message returned by this validator depending on which error caused the validation to fail. However, I don't know how to do this. In my language files I have set up the following line for the validator:
"valid_username" => "This username is already taken or contains invalid characters."
Is it possible with Laravel to return a specific error message? Or do I have to split this validation up in two custom validation rules? This might not be a problem in this case, but especially if database access is involved I would prefer to validate a retrieved Eloquent model in one validator instead of instantiating an Eloquent object twice.
After consulting the code, the answer is "not out of the box". You can, however, extend everything and make that work.
The process, which I don't have time to completely do at the moment (sorry!), would be to create a class extending Validator, making that functionality work, and then using a new ServiceProvider to replace Laravel's $app['validator'] with your own.
That process, a little more concretely, goes like this something like this:
<?php namespace MyLib\Validation;
class Validator extends \Illuminate\Validation\Validator {
// Fancy validation logic to be able to set custom messages
}
Then, you need to extend the Factory to return your new Validator:
<?php namespace MyLib\Validation;
class Factory extends \Illuminate\Validation\Factory {
// Change this method
/**
* Resolve a new Validator instance.
*
* #param array $data
* #param array $rules
* #param array $messages
* #return \MyLib\Validation\Validator
*/
protected function resolve($data, $rules, $messages)
{
if (is_null($this->resolver))
{
// THIS WILL NOW RETURN YOUR NEW SERVICE PROVIDER SINCE YOU'RE
// IN THE MyLib\Validation NAMESPACE
return new Validator($this->translator, $data, $rules, $messages);
}
else
{
return call_user_func($this->resolver, $this->translator, $data, $rules, $messages);
}
}
}
...and finally, extend the Validation service provider, use your new Factory, and then replace the default ValidationServiceProvider with your own.
<?php namespace MyLib\Validation;
class ValidationServiceProvider extends \Illuminate\Validation\ServiceProvider {
/**
* Register the service provider.
*
* #return void
*/
public function register()
{
$this->registerPresenceVerifier();
$this->app['validator'] = $this->app->share(function($app)
{
// THIS WILL NOW RETURN YOUR FACTORY SINCE YOU'RE
// IN THE MyLib\Validation NAMESPACE
$validator = new Factory($app['translator'], $app);
// The validation presence verifier is responsible for determining the existence
// of values in a given data collection, typically a relational database or
// other persistent data stores. And it is used to check for uniqueness.
if (isset($app['validation.presence']))
{
$validator->setPresenceVerifier($app['validation.presence']);
}
return $validator;
});
}
}
So anyway, that's one way to extend the Validation library with your own code. I didn't solve the issue of adding your own messages, but this will show you how, if you can read the core code and see how to add that functionality in, to go about making it work in your app.
Last note:
You may want to see how Laravel handles using Database "stuff" within validation rules - While this may not affect your application (unless it gets big!) you may want to consider using a Repository pattern of some sort and using that in your Validator::extend() call instead of the User class directly. Not necessary, just a note for something to check out.
Good luck and don't be afraid to RTFC!
Instead of making your own validation rule that does validates two things (which you shouldn't really do, validate one thing at a time), you can use the unique rule and then make your own rule that validates the characters of the username.
For example:
$rules = ['username' => 'required|username|unique:users,username'];
Where the username rule is your custom rule that ensures it contains the correct characters.
Maybe a bit "dirty" but it works:
The controller validates the input with something
$rules = array(
'title' => 'no_collision:'.$input['project_id']
);
In the validator function flash the message to the Session before returning false:
//...
public function validateNoCollision($attribute, $value, $parameters)
{
$project = Project::find($parameters[0]);
if($value == $project->title){
Session::flash('colliding_message','This collides with '.$project->title($).' created by '.$project->user->name;
return false;
}else{
return true;
}
}
In the view do something like the following:
#if($errors->has('title'))
<span class="help-block">{{ Session::get('colliding_message') }}</span>
#endif

Resources