kodeine / laravel-acl

Light-weight role-based permissions system for Laravel 6+ built in Auth system.
MIT License
786 stars 215 forks source link

Assign Permission to role with slug #191

Open kushal555 opened 7 years ago

kushal555 commented 7 years ago

@kodeine Actually I am integrating your package. It's good to use. Now I am having a problem with assigning Permission to roles. What I want: - ActuallyI have an admin panel in which I listed out all the permission with checkbox and user enter the role-name and checked permissions. actually, you provided the slug concept so I assuming them as child permissions So my listing view is like this.

So in laravel side i am trying to assign permission to role like that :- $roleAdmin = Role::first(); // administrator $roleAdmin->assignPermission('servicelocation, currency ,privacyandpolicy.create');

It gives error that

InvalidArgumentException in HasPermission.php line 183: Specified permission name does not exist.

kodeine commented 7 years ago

@kushal555 $roleAdmin->assignPermission(->'servicelocation, currency ,privacyandpolicy.create' <--); maybe you forgot to adjust the quotes?

kushal555 commented 7 years ago

@kodeine Sorry, I didn't get you. can you please explain a little bit more.

konovalov-nk commented 7 years ago

@kushal555

  1. Show your permissions. How are you creating permissions?
  2. As far as I understand, assignPermission works with permission name, not permission slugs. Do you have permissions named servicelocation, currency, privacyandpolicy.create created prior to this assignment?
  3. Have a look for commas, It could be the parser issue. See example:
'servicelocation, currency ,privacyandpolicy.create' 

vs

'servicelocation,currency,privacyandpolicy.create' 

Make sure you understand how assignPermission() works.

kushal555 commented 7 years ago

Is there any way, So we can assign Permission name and slug both in array or passing comma separate Actually i have privacyandpolicy is name of permission and create is the slug. Is there any track log maintained in which we can know who is created the roles and permissions ?.

konovalov-nk commented 7 years ago

@kushal555 Let me show you an example

One single permission (name='blog') consists of many slug items (permission items), such as:

In the code, all these slug items actually become JSON-encoded string like slug=["create":true,"update":false,"upload-images":true, etc...].

Our permission object would look like this:

id: 3
name: 'blog'
slug: ["create":true,"update":false,"upload-images":true, etc...],
...

When you assign permission to the role or user you are referring permission id value to role or user id. For these purposes, there are permission_role and permission_user tables.

So there is simply no infrastructure for doing slug-based permission assignments at the moment.

kushal555 commented 7 years ago

Thanks @konovalov-nk for your replying. Actually what I am trying to get, Just explain me the little bit:- First, i am using the AngularJS client side. I list down all permission with possibilities slug for my project and save all them with false status. eg.

#About Us
        $permAboutUS = $permission->create([
            'name' => 'aboutus',
            'slug' => [          // pass an array of permissions.
                'create' => false,
                'view' => false,
                'update' => false,
                'delete' => false
            ],
            'description' => 'manage About Us Page permissions',
            'is_main'=>'1'
        ]); 

Now, I have an admin panel in which I give the menu add the role, in which Super admin can add the role and I also listed out the permissions there with the checkbox in hierarchy structure( I provided screenshot above). So Super Admin can check them according to the role. Suppose if super admin checks only aboutus.view=true. So, I just create the new role with a name of role.aboutuse and insert slug ("create": true).

Now, it's making many entries if super admin creates every time new roles and in server side, i need to parse the every permission and check is it is name or slug.

konovalov-nk commented 7 years ago

@kushal555 So, basically, you need to implement an interface to create new roles with selected permission parameters, right?

Well, you probably need permission inheritance. As for the problem assigning permission name and slugs, it's really easy to solve.

You have multiple permissions listed with slugs as a set of checkboxes. When you check permission name that would mean you want to inherit rules from this permission for your new role. Here is how permission inheritance works:

$permAboutUS = Permission::create([
    'name'        => 'aboutus',
    'slug'        => [ // An array of permissions.
        'create' => false,
        'view'   => false,
        'update' => false,
        'delete' => false,
    ],
    'description' => 'manage About Us Page permissions'
]);

$permAboutUSNewRoleName = Permission::create([
    'name'        => 'aboutus.new_role_slug',
    'slug'        => [ // An array of permissions only for new role you've created
        'create' => true,
    ],
    // We are using permission inheriting here.
    'inherit_id' => $permAboutUS ->getKey(),
    'description' => 'About Us Page permissions for new_role_slug'
]);

That would create a permission aboutus.new_role_slug with this slug:

'slug'        => [ // An array of permissions.
  'create' => true, // See $permAboutUSNewRoleName 'slug' value.
  'view'   => false,
  'update' => false,
  'delete' => false,
],

but it wouldn't store the whole slug in the DB because it uses inherit_id to fetch them from parent permission object. So, it would store only slug="{'create':true}" for this case.

Now, back to your interface. What you want to do is when you check permission name checkbox, it would use that permission id to inherit rules. Aside from that, you don't want simply inherit it, but also change some rules (slugs). I don't think checkboxes is a good way to describe slug values because it could confuse the user. Is checked rule means that this rule needs to be overridden or taken from the parent permission? Consider using radio buttons instead: Default [value=-1], Forbid [value=0], Permit [value=1]. That is standard UX practice for defining permissions for the role. To make the interface less cluttered you can use bootstrap tooltips and title values.

When you finally got your input from the submitted form you can compose your slug items:

// Get form data.
$data = request()->all();
// We have this input data structure:
// 'role_name' => 'New Role'
// 'role_slug' => 'new_role'
// 'role_description' => 'Some description'
// 'aboutus' => true
// 'aboutus.create' => 1
// 'aboutus.view' => -1
// 'aboutus.update' => -1
// 'aboutus.delete' => -1

// All new permissions we'll create that would be assigned to the new role.
$new_permissions = [];
// First check if we have checked 'aboutUS' permission.
if (isset($data["aboutus"])) {
  // Get our parent permission.
  $permAboutUS = Permission::whereName('aboutus')->first();
  // Get our slug items from this permission.
  $slug = $permAboutUS->slug;
  $new_slug = [];
  foreach ($slug as $slug_name => $value) {
    // Create new slug item for new permission we want to create.
    if ($data["aboutus.{$slug_name}"] >= 0) {
      $new_slug[$slug_name] = (bool) $value;
    } else {
      // It would be inherited.
    }
  }

  // Now create new permission:
  $permAboutUSNewRoleName = Permission::create([
    'name'        => "aboutus.{$data['role_slug']}",
    'slug'        => $new_slug,
    // We are using permission inheriting here.
    'inherit_id' => $permAboutUS ->getKey(),
    'description' => "About Us Page permissions for $data['role_name']"
  ]);
  // Add permission name we've created, so we can assign it to the role later.
  $new_permissions []= "aboutus.{$data['role_slug']}";
}

$roleNew = new Role();
$roleNew->name = $data['role_name'];
$roleNew->slug = $data['role_slug'];
$roleNew->description = $data['role_description'];
$roleNew->save();

// Assign all new permissions we've created to the role.
$roleNew->assignPermission(implode(',', $new_permissions));

I haven't tested the code, but you should get the idea.

kushal555 commented 7 years ago

Thank you so much @konovalov-nk for your support and consideration. Actually, i also need to add who add this role, Because I have many types of Admin users So need to maintain roles according to them. If user A create a role test.a then he also able to assign role test.a only not any other users. So for that, i need to change in your table, May I change it or find any other solution?

Once again I want to say thank you @konovalov-nk . :+1:

konovalov-nk commented 7 years ago

@kushal555 If you already have production data, then you must use migrations to add a column named user_id to roles table.

If that's not the case, then you can just update this migration file and run

php artisan migrate:rollback --step=7
php artisan migrate

When the user creates a new role, you can assign current user id to that role object. Then you can add your business logic to forbid/allow access to roles based on user_id value.

kushal555 commented 7 years ago
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class AddRoleTypeToRolesTable extends Migration {

    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up() {
        //
        Schema::table('roles', function($table) {
            $table->tinyInteger('role_type')->after('description')->default(1)->comment('1 = User Role, 2 = Subscription, 3 = Super Admin');
            $table->integer('user_id')->after('role_type')->nullable()->unsigned()->comment("who created this role");
            $table->tinyInteger('is_editable')->after('user_id')->default(1)->comment('1 = Role is Editable, 0 = System Defautl Role which is not editable');
            $table->foreign('user_id')->references('id')->on('users');
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down() {
        //
        Schema::table('roles', function (Blueprint $table) {
            $table->dropForeign(['user_id']);
            $table->dropColumn('role_type');
            $table->dropColumn('user_id');
            $table->dropColumn('is_editable');
        });
    }

}

@kodeine I just create the new migration for existing role and also override the model fillable array according to my requirement.

Is there way to get the new create permission with parent options Like as above we discussed new role is

$newRole = 'aboutus.admin';
// So if i get the new role permission 
$permission=$newRole->getPermissions();

so it only return the allowed permissions. Actually i need the whole slugs which is belongs to user event they are associated or not. @konovalov-nk can you please look into that ?