Laravel Policy for a large array of permissions - laravel

I am looking to use Laravel's policy to authorize a large array of user permissions with Blade.
My authorizations are under a ActionPermission model holding all app permissions.
User has his permission names accessed via a pivot
using:
public function permissions(){
return $this->belongsToMany('App\ActionPermission', 'users__action_permission');
}
So essentially I am receiving a collection of permissions for user and calling $user->permissions()->pluck('module_name')->toArray(); I will get the names of permissions as array.
Is there a way to bring an array of policies via blade in one line? So in my layout I will insert a one line blade directive?
In Laravel's docs, they use the name of the function as the blade directive, like this in the policy:
public function update(User $user, Post $post)
{
return $user->id === $post->user_id;
}
And in the blade:
#can('update', $post)
<!-- The Current User Can Update The Post -->
#elsecan('create', App\Post::class)
<!-- The Current User Can Create New Post -->
#endcan
The above isn't relevant when you have a large number of permissions.
What would be the best practice to execute a large array of permissions?
BTW any method (not only provider bootstrap) would be welcome.

Accessing the model to create from the blade can be dangerous, as you're skipping some validation and requiring Eloquent to handle everything well (it won't).
Aside from that, I hope this can explain what you seek:
$user = User::find($idOfUser) // retrieves an active record of User
$user->permissions // gets the record of permissions that is related to the user, believing the relationship is estabished
User::find($idOfUser)->permissions->update($data) // updates the record in permissions that is related to that user

Related

MiddleWare vs CustomRequest for check user rights

Let's say there some users who have several posts. Every user has many posts, and every post belongs to one user.
To change post frontend uses URL like users/1/posts/3. The goal is to check, that post number 3 belongs to user number 1.
There are several ways to do it:
Check inside controller method or service(bad method IMHO)
Check inside custom request (authorize function)
Check inside middleware
I choose between the last 2, but have some doubts. The custom request should contain validation rules and do not be linked with authorization(SOLID), and I don't know if it's good to do it inside middleware.
I assume you want to check if user can modify the post.
For authorizing access https://laravel.com/docs/7.x/authorization#creating-policies is a way to go.
After defining policy you can reuse it in blade files, in controller and etc..
The policy defines rules for standard actions like create, read, update, delete.
For blades then you can:
#can('update', $post)
For methods in controllers:
$this->authorize('update', $post)
For anywhere else:
$user->can('update', $post)
You can use Policies in conjunction with custom Form Requests

How to make custom authentication on Laravel?

I have a Laravel application, with a login form. I am using subdomains, for example: {{business_subdomain}}.laravel.test. The purpose of my application is create companies in my database, and make registered users login on their attached company and specific domain.
So in my web.php I declared the following:
Route::domain('{business_subdomain}.' . env('APP_URL'))->middleware(['business.subdomain'])->group(function ()
My database contains the following tables:
* Standard tables like users
* Businesses which contains id, name and subdomain
* Business_user which contains business_id and user_id
The business_id (business_user table) has a relationship with id (business table)
The user_id (business_user table) has a relationship with id (users table)
I created a middleware that checks the following:
$business = $request->user()->businesses->where('subdomain', $request->route('business_subdomain'))->first();
So the subdomain value (business table) must be the equal to the URL/route subdomain value, otherwise when you login, you will get an 403 page.
So what I want to achieve: When a user has an account, but is not attached to the right company/business, I want to display the standard authentication error: These credentials do not match our records.
I've tried to use the code in my middleware on the LoginController and override almost every function separately, but it didn't work.
Do I need to override Laravel authentication functions, do I need to create another middleware or use Guards?
The laravel authentication login order (the order the application uses for logging in a user) seems very confusing for me.
Maybe someone can provide me with more information or help me out!
I tried to use the code in my middleware on the LoginController and override almost every function separately, but it didn't work.
Sounds like you might be looking for multi-tenancy. There are a couple different packages available, both for single and multi-database setups.
Here are some very informative slides on the topic, specific to Laravel. And an excellent article for more detail.
We have a similar application with subdomain routing using torzer/awesome-landlord. The tenant package ensures that login will only search for users of the correct tenant.
To set the correct tenant we added a middleware (App is the tenant, in your case it would be Business):
public function handle($request, Closure $next)
{
$subdomain = $this->getSubdomain($request->server('HTTP_HOST'));
if ($subdomain) {
$app = App::getBySlug($subdomain);
abort_if(!$app, 404);
$this->tenantManager->addTenant($app);
} else {
// empty or generic subdomain
// this is OK as long as there is no user
if ($user = $request->user()) {
return $this->redirectToUserAppSubdomain($request, $user);
}
}
return $next($request);
}

Laravel 5/ Form security (require clarification)

Not entirely confident I have understood security in Laravel forms enough. For example, if a form contains
<input type="hidden" name="user_id">
then obviously a hacker could change the value before submitting an update.
While I have looked here at CSRF, I've not fully understood if this is enough protection?
E.g. Taking the above, if I go to a site and open a form to edit a record I'm permitted to view but not change, and maliciously alter the "user_id", is it enough that the form is protected with {{ csrf_field() }} or must I employ some further security such as Crypt::encrypt($id) to hide the user_id (held in a database) and Crypt::decrypt($id)?
Is it considered a bad practice to expose a row id (like a user id) in a client browser (even though everything is sent over https)?
Many Thanks
No, it's not enough to use just CSRF token in this case. You also need to use policies, guards, middleware to protect your app.
In this case, someone can alter the user_id if you read it from the form and use after that, so you need to use a policy like this one to protect data (this example is from the docs):
public function update(User $user, Post $post)
{
return $user->id === $post->user_id;
}
Also, when you need to use user ID, always use auth()->id() or auth()->user() if you need whole object. Never read user ID from the form.
The Laravel framework stores the value of this CSRF field like a session variable and matches it when you submit it.
When you submit the form Laravel checks that value from the session value stored. if there is a mismatch an error is thrown !
:)
CSRF token protect the site from cross-site requests, means an external user can't duplicate the form and send a post request. Laravel create a random session token which we place in the hidden field using csrf_field() or Session::token() function. Laravel checks the session with hidden field value from the form before processing the form.
Try removing the form action. It should work.

Query in Laravel with Relationships from blade?

I have the following tables
Users
request
servers
Which are related in the following way.
User has many request
Sever has many request
Now what I want is to get all the servers associated with the requests that the user has without repetitions.
I have to do it in the blade view since in the view I am iterating the services and showing them in a table.
So far I've been trying the following
#foreach($requests as $request)
#php
$serversUser = \App\Server::where('requestsofservice', function($query){
$query->where('user_id',$request->user->id);
});
#endphp
#endforeach
But it doesn't work because of the variable $request->user->id.
and the result is
Undefined variable: request
So, how could i get the services that the user a has related to their request without repetitions.
What happens is that as I am iterating the requests that I send from
the controller to the view, being inside the click foreach I get the
user and from that user all the servers that he has in his requests.
$request doesn't exist in your function. Pass it by doing function($query) use($request).
See PHP's documentation on variable scope: http://php.net/manual/en/language.variables.scope.php
and on anonymous functions (in particular, the "Example #3 Inheriting variables from the parent scope" bit): http://php.net/manual/en/functions.anonymous.php

Laravel 5 user role check before

I have some trouble figuring out how to do this properly.
I have Users that can create Articles which can be viewed, edited and deleted. I want to restrict the edit and delete actions if the currently logged in user is not the creator of the Article.
I have already done this using Policies and use Gate within a Form Request, but if i understand correctly FormRequest is only for POST requests. And I currently pass the FormRequest as a parameter to the getEdit() action. That seems wrong.
So how can I make sure the user can neither access nor edit the Article?
If you already defined a Policy you can directly access it using the Gate facade within your controller action like this:
public function getEdit($id)
{
$reference = Reference::findOrFail($id);
if (Gate::denies('owns-reference', $reference))
abort(403);
return view('reference.edit')
->with('reference', $reference);
}
Just make sure to include the Gate on top of your file like this:
use Gate;

Resources