I use caffinated/shinobi.
Best Laravel Packages
Hey Guys,
I am building an app that will need roles and permissions ect.
What packages do you recommend get installed on a blank install?
Imo entrust is great and Laravel specific, but in most cases, by the time you install and set them up you could have code it your self. Couple models, 3-4 migrations and less then 30 lines of code.
@jekinney Thats true but i'm from a CI background and am yet to find a decent walk through of how to do it. Yes its built into 5.2 but it doesn't really sink in until you DIY
sibler/bouncer is my favorite so far. It works well with the new Policy objects - you can always override it if you need some more advanced auth checks.
@jzmwebdevelopment I'll try to give a basic overview:
// User model
class User extends Model implements AuthenticatableContract, CanResetPasswordContract
{
use Authenticatable, CanResetPassword;
/**
* Relation ship to Role Model through role_user pivot table
*/
public function roles()
{
return $this->belongsToMany(Role::class)->withTimestamps();
}
/**
* Checks if the user has a role by its slug.
*
* @param string|array $name Role name or array of role names.
* @param bool $requireAll All roles in the array are required.
*
* @return bool
*/
public function hasRole($slug, $requireAll = false)
{
// check if array of role slugs have been passed in, if so loop through them.
// if $requireAll is passed in as true, need to make sure the user has all roles passed in
// You can also pass an object and perform a check is_object then use $role->slug in loop
if (is_array($slug)) {
foreach ($slug as $roleName) {
$hasRole = $this->hasRole($roleName);
if ($hasRole && !$requireAll) {
return true;
} elseif (!$hasRole && $requireAll) {
return false;
}
}
return $requireAll;
} else {
foreach ($this->roles as $role) {
if ($role->slug == $slug) {
return true;
}
}
}
return false;
}
/**
* Check if user has a permission by its slug.
*
* @param string|array $permission Permission string or array of permissions.
* @param bool $requireAll All permissions in the array are required.
*
* @return bool
*/
public function can($permission, $requireAll = false)
{
// This checks for permissions attached to a role, so it grabs all the roles a user has and loops trough each role to get the permissions accosiacted and loops through the permissions looking for a match.
if (is_array($permission)) {
foreach ($permission as $permName) {
$hasPerm = $this->can($permName);
if ($hasPerm && !$requireAll) {
return true;
} elseif (!$hasPerm && $requireAll) {
return false;
}
}
return $requireAll;
} else {
foreach ($this->roles->load('permissions') as $role) {
// Validate against the Permission table
foreach ($role->permissions as $perm) {
if ($perm->slug == $permission) {
return true;
}
}
}
}
return false;
}
}
// Role Model
class Role extends Model
{
/**
* Fillable on mass assignment
*
* @var array
*/
protected $fillable = [
'slug',
'name',
'description',
];
/**
* Always ensure slug is sluggified
*/
public function setSlugAttribute($slug)
{
return $this->attributes['slug'] = str_slug($slug);
}
/**
* Relationship to User Model(many to many)
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
*/
public function users()
{
return $this->belongsToMany(User::class)->withTimestamps();
}
/**
* Relationship to Permission Model(many to many)
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
*/
public function permissions()
{
return $this->belongsToMany(Permission::class)->withTimestamps();
}
// Permission Model
class Permission extends Model
{
/**
* Fillable on mass assignment
*
* @var array
*/
protected $fillable = [
'slug',
'name',
'description',
];
/**
* Relationship to Role Model(many to many)
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
*/
public function roles()
{
return $this->belongsToMany(Role::class)->withTimestamps();
}
}
Highly inspired by Entrust code, redone some and left out some so I can have basic roles and permissions but able to tweak it as needed for each app I use it on.
//To use:
if(auth()->user()->hasRole('admin'))
{
Do some work
}
if(auth()->user()->can('access_backend'))
{
return Allow redirect to backend/admin panel, doesn't matter what role they have just the permission
}
return redirect back() because logged in user doesn't have the permission assigned.
@jekinney you could have charged for this. A question for you on this, first of all that is great code, how do you use it so a user can view only their data? I use if statements but I bet you have a better way.
And I agree with you, with all the stuff Taylor added in for Authentication and authorization it pays to learn that rather than using an outside package. That especially with good instructions you just gave.
https://github.com/spatie/laravel-permission
https://github.com/spatie/laravel-authorize
work perfectly! laravel 5.2
@jlrdw Thank you for the kind words, and like I said it is heavily inspired the by Entrust code base.
To answer your question it depends on the use case. I cheat and use auth() a lot. So lets say a user wants to edit/access their profile, I don't pass anything in the route. Just use something like:
auth()->user()->profile
But as I said it depends
@acitjazz LOL, not much different!! Added a few more functions to separate things out a bit.
public function hasRole($roles)
{
if (is_string($roles)) {
return $this->roles->contains('name', $roles); //<-- contains is a helper that loops through the collection in a foreach, just else where
}
if ($roles instanceof Role) { //<-- checks if is an object
return $this->roles->contains('id', $roles->id);
}
// Below is almost line for line the same if is array check.
if (is_array($roles)) {
foreach ($roles as $role) {
if ($this->hasRole($role)) {
return true;
}
}
return false;
}
return (bool) $roles->intersect($this->roles)->count();
}
https://github.com/spatie/laravel-permission/blob/master/src/Traits/HasRoles.php :)
Like I stated the issue is your kinda locked in the methods and "ways" of doing things on a package. You either have to override a method if you can, or implement others that must exist together. You can't go in the vendor folder and update/change as it will be deleted on a composer update.
@jekinney I was kind of referring to let's say a list of articles or post or whatever and a user is allowed to only see his or her listings no one else's. And of course the same thing goes for editing their own data but no one else's.
Okay, I don't pass anything in, so lets say:
http://homestead.app/profile/my-articles
// I would:
$articles = Article::where('author_id', auth()->id())->get(); // Or user_id how ever
or
$articles = auth()->user()->articles; // works fine too, simple and direct
or
$user = auth()->user()->load('articles'); // this gives you a user variable with the user's data and lazy eager loads the associated articles to loop through in the view
That will collect only those records that are associated with the user. Add the default 'auth' middleware to protect the route.
@jekinney in one site I did, the admin could view and edit all, user couldn't, so I would imagine in a case like that you almost have to have at least one if else block to weed out the admin from the regular user.
I was just wondering how you deal with it. I wish @TaylorOtwell convered various scenarios like this in the documentation. I think Authentication and autorisation is one of the hardest things in laravel for people to grasp. The code you gave above should go in the documentation.
I generally have separate routes for admins, so the query would be different. If you wanted to use the same route, yes, you definitely would have to perform checks for sure to retrieve the correct data.
http://homestead.app/profile/my-articles
ProfileController@articles
http://homestead.app/admin/articles
Admin\ArticleController@index
@jzmwebdevelopment Defiantly is, and a basic feature that 99% of sites need. Tayler mentioned in a podcasts he doesn't push more until guard stuff (which I haven't fully embraced as of yet, just no time to dig into it) because use case is different. Like teams for example: A team may have groups/roles and permissions too which complicates things unless you set up another set of tables and models, or add to the role and permissions models a team_id to associate a Role and Permissions to a team... LOL, gets complicated fast (think a forum and forum groups, moderators for a specific forum etc.). I personally don't use the default auth controllers for the reasons stated above, constant overrides etc takes just as long if not longer just to code it yourself.
Excelent @jlrdw ! Can you tell me please how are the tables structures and their relations? users, roles, permissions, user_roles. Thank you in advance!
Please or to participate in this conversation.