Separate roles/permissions if user belongs to many organizations - laravel

I have an application where a user can belong to multiple organizations. I want to set it up in a way that a user can have different roles/permissions for each organization. I am using Laravel and plan on implementing Spatie/laravel-permission. What is the best way to implement this?
I have tried setting up two guards, one for the main user account and another for the pivot model between the user and the organization they log into. So basically when they log into the app using the main user model, I ask them which organization they would like to log into, when they choose the organization I will then also set up an auth session on the pivot model that links the user to the organization and access the roles off that model. This works, but having to manage the auth sessions is kind of a pain.
// User Model
class User extends Authenticatable
{
public function organizationUsers()
{
return $this->hasMany(OrganizationUser::class);
}
}
// OrganizationUser Model
class Organziationuser extends Authenticatable
{
use HasRoles;
public $guard_name = 'organization_user';
public function organization()
{
return $this->belongsTo(Organization::class);
}
public function user()
{
return $this->belongsTo(User::class);
}
}
I would expect a user to be able to log into the application using a single login, but also be able to have different permissions for different organizations.

I have got around this issue by doing the following. I would welcome feedback on peoples views of this method!
Note: Currently I am only using the model_has_roles table with Spatie permissions and always use $user->can('Permission') to check permissions.
Our company model has the following relationships and method
class Company extends Model
{
public function owner(): HasOne
{
return $this->hasOne(User::class, 'id', 'user_id');
}
public function users(): BelongsToMany
{
return $this->belongsToMany(
User::class, 'company_users', 'company_id', 'user_id'
)->using(CompanyUser::class);
}
public function addTeamMember(User $user)
{
$this->users()->detach($user);
$this->users()->attach($user);
}
}
We modify the pivot model to have the Spatie HasRoles trait. This allows us to assign a role to the CompanyUser as opposed to the Auth User. You also need to specify the default guard or Spatie permissions squarks.
class CompanyUser extends Pivot
{
use HasRoles;
protected $guard_name = 'web';
}
On the user model, I have created the HasCompanies Trait. This provides the relationships and provides a method for assigning the roles to the new company user. Additionally, it overwrites the gate can() method.
A user can belong to many companies, but can only have one active company at a time (i.e. the one they are viewing). We define this with the current_company_id column.
It is also important to ensure the pivot table ID is pulled across (which it will not be as standard) as this is now what we are using in the Spatie model_has_roles table.
trait HasCompanies
{
public function companies(): HasMany
{
return $this->hasMany(Company::class);
}
public function currentCompany(): HasOne
{
return $this->hasOne(Company::class, 'id', 'current_company_id');
}
public function teams(): BelongsToMany
{
return $this->belongsToMany(
Company::class, 'company_users', 'user_id', 'company_id'
)->using(CompanyUser::class)->withPivot('id');
}
public function switchCompanies(Company $company): void
{
$this->current_company_id = $company->id;
$this->save();
}
public function assignRolesForCompany(Company $company, ...$roles)
{
if($company = $this->teams()->where('companies.id', $company->id)->first()){
/** #var CompanyUser $companyUser */
$companyUser = $company->pivot;
$companyUser->assignRole($roles);
return;
}
throw new Exception('Roles could not be assigned to company user');
}
public function can($ability, $arguments = [])
{
if(isset($this->current_company_id)){
/** #var CompanyUser $companyUser */
$companyUser = $this->teams()->where('companies.id', $this->current_company_id)->first()->pivot;
if($companyUser->hasPermissionTo($ability)){
return true;
}
// We still run through the gate on fail, as this will check for gate bypass. i.e. Super User
return app(Gate::class)->forUser($this)->check('InvalidPermission');
}
return app(Gate::class)->forUser($this)->check($ability, $arguments);
}
}
Now we can do something like this:
Create the role & permission
/** #var Role $ownerRoll */
$ownerRoll = Role::create(['name' => 'Owner']);
/** #var Permission $permission */
$permission = Permission::create([
'name' => 'Create Company',
'guard_name' => 'web',
]);
$ownerRoll->givePermissionTo($permission);
Create a new company with an owning user and then switch this company to that owner's active company.
public function store(CompanyStoreRequest $request)
{
DB::transaction(function () use($request) {
/** #var User $owner */
$owner = User::findOrFail($request->user_id);
/** #var Company $company */
$company = $owner->companies()->create($request->validated());
$company->addTeamMember($owner);
$owner->assignRolesForCompany($company, 'Owner');
$owner->switchCompanies($company);
});
return redirect()->back();
}
So this all works, my main concerns are that:
We are overwriting the can method. There may be other authorization methods/gate functions that are not caught.
We have 2 sets of model_permissions. The Auth user and the company user. I think I need to build in some checks to ensure that only the correct kinds of users can be assigned to the roles. At this stage, all administrator users would have permissions assigned to their auth user, while any users who own a company should only have permissions on the company user model

Related

How to show data for only a specific user using Laravel

I am trying to show data related to user but unfortunately it's not working. Currently, when I submit order via my form, every account can see the data. How can I show data for only the specific person who submitted it?
Database
Table digitizing_orders table has
id | order_name | order_placement | user_id
Digitizingorder Model
class Digitizingorder extends Model
{
protected $table = "digitizing_orders";
public function user()
{
return $this->belongsTo('App\User');
}
}
User Model
class User extends Authenticatable
{
public function digitizing()
{
return $this->hasMany('App\Digitizingorder');
}
}
Controller
public function index()
{
$data = [
'digitizings' => Digitizingorder::with('user')->where('id', '!=', Auth::id())->get()
];
return view('front_end.Customerprofile.digitizing_view_order', $data);
}
Use Polices they can protect controller actions on a model from unauthorized users.
After generating and registering the policy you can use its methods like this:
class DigitizingorderPolicy
{
/**
* Determine if the given Model can be Viewed by the user.
*
* #param \App\User $user
* #param \App\Digitizingorder $digitizingorder
* #return bool
*/
public function view(User $user, Digitizingorder $digitizingorder)
{
return $user->id === $digitizingorder->user_id;
}
}
You can define what's inside the method to satisfy a condition for the user to view a model (e.g privileges or Role).
One of the ways to call it is in the controller:
public function show(Request $request, Digitizingorder $digitizingorder )
{
$this->authorize('view', $digitizingorder);
// The current user can view the digitizingorder ...
}
I suggest reading the docs about it, they're Multiple Ways/Methods to use Polices and might contain something useful for you.
use this in controller
$user = \Auth::guard('web')->user();
$data=
[
'digitizings'=> $user->digitizing
];
return view('front_end.Customerprofile.digitizing_view_order',$data);

Amending foreign key on a Laravel relationship

In Laravel (v5.7.12), I have two models - User and Project.
A user has an id and can have many projects. A project has an owner_id.
I can't seem to configure the relationship correctly. In my user model, I have:
/**
* Get the projects associated with the user.
*/
public function projects()
{
$this->hasMany('\App\Project', 'owner_id', 'id');
}
In my project model, I have:
/**
* Get the owner associated with the user.
*/
public function owner()
{
$this->belongsTo('\App\User', 'id', 'owner_id');
}
But calling either $user->projects() or $project->owner() returns null.
How should I configure my non-standard relationship keys?
You forgot to return the method:
public function projects()
{
return $this->hasMany('\App\Project', 'owner_id');
}
Do this also for the second one:
public function owner()
{
return $this->belongsTo('\App\User', 'owner_id');
}

Clarification with eager loading/mutators/accessors for laravel

I'm making a laravel app for work and I need to load all users with their attached role without any nesting of the roles. I used this tutorial for roles: https://medium.com/#ezp127/laravel-5-4-native-user-authentication-role-authorization-3dbae4049c8a . If I use public $with = ['roles']; on my User model it returns the entire role object within the user object and I need it to just return role:'role_name';
/**
* set up eloquent between roles/user
*
* #return \Illuminate\Database\Eloquent\Relations\belongsToMany
*/
public function roles()
{
return $this->belongsToMany(Role::class);
}
Above is in my User model and below is in my Role model to define the relationships.
/**
* provides a many-to-many relationship to User model
*
* #return User::class
*/
public function users()
{
return $this->belongsToMany(User::class);
}
I thought that by adding this to the User model:
protected $appends = ['role_name'];
public function getRoleNameAttribute()
{
return $this->attribute['name'];
}
it would return everything but all it does is create a role_name: 'user_name'; On the model. So I guess I realize I'm accessing just the Users table and not the Roles table in the DB, but again not really sure what to do. Any help would be greatly appreciated.
If for the purpose of convenience you need to access the role name directly from the model you should refer to the actual relationship data:
protected $appends = ['role_name'];
public function getRoleNameAttribute()
{
return $this->roles->pluck('name');
}
This should append the array of role names to your user model. It will be an array because roles seem to have a many-to-many relationship with the User model.
The issue is you're returning $this->attributes['name'] in the getRoleNameAttribute. You want the role names, so instead you'd do something like this:
If you want an array with the names:
return $this->roles()->pluck('name')
If you want the names as a string:
return array_implode(", ", $this->roles()->pluck('name')->toArray());

Displaying users with 2 related tables records

I have 3 Models :
User, Role and Branch.
They are related like this :
Pour User model :
class User extends Model
{
// Define fields to be fillable
protected $fillable = [
'firstname',
'lastname',
'telephone',
'address',
'password',
'code',
'status',
'branches_id',
'roles_id'
];
/**
* Get the role record associated with the user.
*/
public function role()
{
return $this->hasOne('App\Role');
}
/**
* Get the branch record associated with the user.
*/
public function branch()
{
return $this->hasOne('App\Branch');
}
}
For Role Model :
class Role extends Model
{
// Define fields to be fillable
protected $fillable = ['description'];
/**
* Get the user that owns the role.
*/
public function user()
{
return $this->belongsToMany('App\User');
}
}
For branch model :
class Branch extends Model
{
// Define fields to be fillable
protected $fillable = ['description', 'location'];
/**
* Get the user that owns the branch.
*/
public function user()
{
return $this->belongsToMany('App\User');
}
}
I know that if i was using Blade, to list user's roles, i could have done something like : $user->role()
But i am trying to use angular 2 for the frontend and laravel 5.3 for my backend.
My question is how to retrieve users along with roles and branches
Here is my index action in my UserController:
public function index()
{
// Get all users, along with roles and branches
$users = User::all();
// Send the response
return response()->json([
'users' => $users
], 200);
}
Use eager loading:
$users = User::with('role', 'branch')->get();
Also if user has one role and one branch, relationships should be belongsTo() instead of belongsToMany():
return $this->belongsTo('App\User');
You can use the with() method for this, e.g.
$users = User::with('role')->get();
See https://laravel.com/docs/5.4/eloquent-relationships#eager-loading for more information

Many to Many to One relationship

I've got four tables Users[user_id] - role_user[user_id,role_id] - Roles[role_id] - Permissions[role_id]. A User could have many Roles, while the Role has many Permissions. So, a Permission has one Role, while a Role belongs to many Users.
// User.php ...
class User extends Model
{
public function roles()
{
return $this->belongsToMany('Role');
}
}
// Roles.php
class Role extends Model
{
public function users()
{
return $this->belongsToMany('App\User');
}
public function permissions()
{
return $this->hasMany('Permission');
}
}
// Permission.php
class Permission extends Model
{
public function role()
{
return $this->belongsTo('Role');
}
}
I guess the real question is; can you chain relationship methods, like: App\User::find(1)->roles->permissions; I don't think you can because the ->roles returns a Collection and not an eloquent model, so the permissions method doesn't exists off roles.
Is there another way I can get the collection of permissions for all roles for a given use, preferably with a single line?
I haven't tested it, but I think this will work or work with very small twick. Add this function in your User Model.
public function getPermission($id){
$roles = Roles::where('user_id','=', id)->get();
$permissions = array();
foreach($roles as $role){
array_push($permissions, $role->permissions);
}
return $permissions;
}
and access as $user->getPermission($user->id);. This might not be the best solution, but it should solve the problem.
UPDATED CODE
You can use accessor like the example bellow and this will return a permission collection. Use this function in your User Model
public function getPermissions($value){
$role_ids = Roles::where('user_id','=', $value)
->select(array('permission_id'))
->toArray();
return Permission::find($role_ids);
}
and access it like $permissions = App\User::find(1)->permissions;. I believe this will work as you expected.

Resources