Apply model policy in a global scope query - laravel-5

I have a complexe user policy (extending the PolicyAbstract class) granting reading/editing access for a given user to other user based on several rules and other parameters of the current user.
I am currently applying the policy on each records and filtering out the user the given does not have access after the query has been performed by walking the collection. This has the adverse effect of messing up the pagination collection results as I remove records after the pagination was set.
I would like to apply the policy in a global scope so the forbiden user are already filtered from the paginated collection.
Something like this:
public static function boot() {
parent::boot();
static::addGlobalScope('user_selection', function(Builder $builder) {
$builder->[involve the user policy here]
}
}
Is this possible in any way?
Any help or pointer to achieve it would be greatly appreciated.

Could find a way to involve a policy in a global scope.
Had to duplicate the "read" policy rules in a Scope definition and use that to filter records before pagination.

Related

Laravel permissions pivot table

I am making custom permissions,
I would like them to be divided into sections e.g. orders, customers etc. and each section would have some permission e.g. edit, delete, add.
Just to check if a particular user has access I would have to start with the Section model?
Because I can't do something like Auth::user()->permissions()->with('sections')->contains('name','display')->contains('sections','order')
I would like to simply check if the user has access to, for example, order.display.
I have a feeling that this section does not make sense here.
How to do it? I know there is a spatie(laravel-permission), but there is a role division there.
Example schema of the database (user_permission is a pivot):
My advice is if you're looking for something simple, drop the sections table and just have all permissions in the permissions table. Say you had a section called dashboard in permissions you could have view_dashboard edit_dashboard etc. Then to check the user is authorised, implement the hasManyThrough logic below Official documentation
(untested)
public function permissions()
{
return $this->hasManyThrough(Permissions::class, UserPermissions::class);
}
public function permission_check($permission_to_check)
{
return $this->permissions->contains($permission_to_check);
}
You could then eager load permissions() on the user model to reduce DB queries if you are checking permission in many places.
Hope this helps, let me know if I can assist further :)

Laravel scope query only for users - exclude result for admin

I am very new into programming and trying to make some work on Laravel / Voyager.
I have a Customers table and on this table there is also Customer_Representative field where I can select / assign customer representatives from dropdown list.
What I am trying to achieve is I want each users with their assigned roles (Customer Representative) to list customers only assigned for them.
I am using this below code, but I am not able to see list of customers that other users created. I can only see the customers I / Admin created but If I login with their details I can see their assigned customers.
So as an admin I want to be able to see all customers, so I can also edit them with my login credentials.
public function scopeCurrentUser($query)
{
return $query->where('Customer_Representative', Auth::user()->id);
}
I saw many suggested to use if but I couldn't figure out how to do it.
Appreciate your supports !
If I understand correctly, you want to display:
admin users - all users
non-admin users - only assigned users
You can either change the query in controller, but if you want to do it in scope I suggest something like this:
public function scopeCurrentUser($query)
{
return $query->when(auth()->user()->is_not_admin, function ($q) {
$q->where('Customer_Representative', auth()->user()->id)
});
}
You change the auth()->user()->is_not_admin to your condition for non-admin validation. The scope utilizes the ->when() function to append the condition to query only if the condition (in this case if authenticated user is not an admin) is true.
So if authenticated user is not an admin, his query will be filtered by Customer_Representative.

Laravel: Check if the field was filled in the route, using Trait and Scope

I have an api that I am implementing multi-tenant based on an additional column in the tables.
For this I created a trait that implements a
static::addGlobalScope() and static::creating().
I have a user_idXtenant_id table. In this table I store the accesses that the user has.
My Route looks like this:
Route::get('{tenant_id}/products', function ($tenant_id) {
return App\Products::where(['tenant_id' => $tenant_id])->get();
})->middleware('role_or_permission:admin|seller|get-products');
Route::get('products', function () {
return App\Products::get();
})->middleware('role_or_permission:admin|seller|get-products');
The idea is that in the first route it is verified if the user has access to tenant_id and if he has access, return only the products of that tenant_id (here Polices can be useful and solve the problem).
The second route, on the other hand, must return all products of all tenant_id that the user has access to. For this, I use static :: addGlobalScope() to set the tenants that the user has access to in this way:
$builder->whereIn('tenant_id', $tenants);
The question is how to check in GlobalScope if tenant_id was filled in the model. So, if it is present, I only check the permission and if it is not present I include the tenant_id that the user has access to.
I hope I have been able to adequately exemplify the problem.
Any help is most welcome.
Thank you!

Idiomatic way in Laravel to manage permissions with field level specificity

I am new to Laravel and I want to make data in my database available via RESTFUL API. I need to control permissions of data objects with field level specificity. I was curious what is the idiomatic way to do this in Laravel?
For example, I will have a database table called PrintMachine that has the fields Id,MachineName,ActivityStatus,ManufacturingYear. I want to assign the following permissions:
Web Administrators get Read and Edit access to all records and all fields in PrintMachine
Factory Managers get Read and Edit access to the PrintMachine.MachineName and PrintMachine.ActivityStatus fields for all records and get no access to any other fields in PrintMachine.
Floor Operators get Read access to the PrintMachine.MachineName field for all records and get no access to any other fields in PrintMachine.
People told me to consider Spatie Module and also read Gates and Policies, but it's not clear how either achieves field level permissions on their own.
The other option I was considering was to custom create my own solution by:
for GET requests, create three ViewModels called PrintMachineAdmin,PrintMachineManager,PrintMachineOperator, each class with properties accessible to the corresponding roles.
for POST/PUT requests, I'll have to write my own conditional statements to validate data based on the users roles
Is there a more idiomatic way to develop the a feature for field level permissions for restful apis?
So many options. An implementation of role and permission structure can achieve this and you can most certainly do this via the Spatie Module.
Eg adapted from spatie documentation:
$role = Role::create(['name' => 'Manager']); //db has roles table
//or if already created
//$role = Role::where('name', 'Manager')->first();
$permission = Permission::create(['name' => 'edit PrintMachine.MachineName']); //db permissions table
$role->givePermissionTo($permission); //now manager role has been assigned permission to edit machine name.
//assigning role to user
$user = User::create(['name'=> 'Manager User']); //or get existing
$user->assigneRole($role); //now this user has edit access to machine name
//to see if user has access
if( $user->hasPermissionTo('edit PrintMachine.MachineName') )
//do efit
//OR if you want to check using role
if( $user->hasRole('Manager')
//do manager stuff
//and in view you can use #can blade directive
#can( 'edit PrintMachine.MachineName' )
//authenticated user can edit machine name //show edit button/form
#endcan
//similarly #role directive will do the check using role
For super admins in AuthServiceProvider's boot method.
Gate::before(function ($user, $ability) {
return $user->hasRole('Super Admin') ? true : null;
});
Gate's before method precedes all other gate operations so all other permissions can be overridden here.

Symfony2: Get roles of groups in user entity but avoid query inside entity

My User entity implements UserInterface and therefore provides the getRoles() method. In my system a User can belong to multiple Groups. A group can have multiple roles. Thus a user's roles can be determined by collecting all groups and merging the lists of those groups' roles. The same thing is possible with FOSUserBundle.
The easiest algorithm would be:
public function getRoles()
{
$roles = $this->roles;
foreach ($this->getGroups() as $group) {
$roles = array_merge($roles, $group->getRoles());
}
return array_unique($roles);
}
I think this solution is problematic because it will scale badly. For each group a new query has to be executed, so the amount of queries depends on the amount of groups.
Usually I'd solve this by defining a single query which collects all groups of the user and joins the groups' roles. That would require calling the UserRepository (or directly building a Doctrine query) from the User entity and I believe that is a bad practice.
So how could I avoid this bad practice while maintaining the performance advantages of a single join query?
(This time it was very hard for me to find a question title that fits. I'm not sure whether there are other simililar situations but I don't think so, because usually I would provide such method in the repository itself. In this case the UserInterface requires it to be in the entity)
The easiest way to do this is to create a custom UserProvider.
see http://symfony.com/doc/current/security/custom_provider.html#create-a-user-provider for the documentations.
Inside this user provider you have to make your query to select the user from an username and add a join to the group entity, so you only have one query

Resources