dimanche 27 novembre 2016

How to design multiple permissions in laravel?

Let's say, I have many models: user, group, complex, house, flat.

I want to assign different permissions to different users.

For example, users in group1 can have these permissions (self explanatory): "complex.show", "complex.edit"

Users in group2: "house.show", "flat.show", "flat.edit"

Users in group3: "house.show", "house.edit.1", "house.edit.3" (these users can see all houses and can edit house with id 1&3)

Migration

use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class CreateUsersTable extends Migration
{
    public function up()
    {
        Schema::create('app_users', function (Blueprint $table) {
            $table->increments('id');
            $table->string('email')->unique();
            $table->string('first_name');
            $table->string('last_name');
            $table->string('password');
            $table->rememberToken();
            $table->boolean('active')->default(0);
            $table->string('timezone')->default('Europe/Moscow');
            $table->timestamp('created_at')->useCurrent();
            $table->timestamp('updated_at')->default(DB::raw('CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP'));
        });

        Schema::create('app_permissions', function (Blueprint $table) {
            $table->increments('id');
            $table->string('title');
            $table->string('description')->default('');
            $table->timestamp('created_at')->useCurrent();
            $table->timestamp('updated_at')->default(DB::raw('CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP'));
        });

        Schema::create('app_groups', function (Blueprint $table) {
            $table->increments('id');
            $table->string('title');
            $table->string('description')->default('');
            $table->timestamp('created_at')->useCurrent();
            $table->timestamp('updated_at')->default(DB::raw('CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP'));
        });

        Schema::create('app_group_users', function (Blueprint $table) {
            $table->integer('user_id')->unsigned();
            $table->foreign('user_id')->references('id')->on('app_users');
            $table->integer('group_id')->unsigned();
            $table->foreign('group_id')->references('id')->on('app_groups');
            $table->timestamp('created_at')->useCurrent();
            $table->timestamp('updated_at')->default(DB::raw('CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP'));
        });

        Schema::create('app_group_permissions', function (Blueprint $table) {
            $table->integer('group_id')->unsigned();
            $table->foreign('group_id')->references('id')->on('app_groups');
            $table->integer('permission_id')->unsigned();
            $table->foreign('permission_id')->references('id')->on('app_permissions');
            $table->timestamp('created_at')->useCurrent();
            $table->timestamp('updated_at')->default(DB::raw('CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP'));
        });
    }

    public function down()
    {
        Schema::dropIfExists('app_group_permissions');
        Schema::dropIfExists('app_group_users');
        Schema::dropIfExists('app_groups');
        Schema::dropIfExists('app_permissions');
        Schema::dropIfExists('app_users');
    }
}

User model

namespace App;

use Illuminate\Notifications\Notifiable;
use Illuminate\Foundation\Auth\User as Authenticatable;

class User extends Authenticatable
{
    use Notifiable;

    protected $table = 'app_users';

    protected $fillable = [
        'name', 'email', 'password', 'first_name', 'last_name'
    ];

    protected $visible = [
        'id', 'email', 'first_name', 'last_name', 'created_at', 'updated_at', 'timezone'
    ];

    public function groups() {
        return $this->belongsToMany('App\Group', 'app_group_users');
    }

    protected static $_PERMISSIONS_CACHE = [];

    public function permissions() {
        if(isset(self::$_PERMISSIONS_CACHE[$this->id])) {
            return self::$_PERMISSIONS_CACHE[$this->id];
        }
        $groups = $this->groups()->get();
        $_permissions = GroupPermission::whereIn('group_id', $groups)
        ->join('app_permissions', 'app_permissions.id', '=', 'app_group_permissions.permission_id')
        ->select('app_permissions.title', 'app_permissions.description')->get()->toArray();
        $permissions = [];
        foreach($_permissions as $_permission) {
            $permissions[$_permission['title']] = [
                'description' => $_permission['description']
            ];
        }
        self::$_PERMISSIONS_CACHE[$this->id] = $permissions;
        return $permissions;
    }

    protected static $_AVAILABLE_PERMISSIONS = [
        'show' => 'show', 'add' => 'add', 'edit' => 'edit', 'delete' => 'delete'
    ];
    protected static $_AVAILABLE_ENTITIES = [
        'user' => 'user', 'group' => 'group',
        'complex' => 'complex', 'house' => 'house', 'flat' => 'flat',
        'property' => 'property'
    ];

    public function has_permission($entity_permission, $id = null) {
        list($entity, $permission) = explode('.', $entity_permission);

        if(!isset(self::$_AVAILABLE_ENTITIES[$entity])) {
            throw new \InvalidArgumentException("Wrong enity '" . $entity . "'");
        }
        if(!isset(self::$_AVAILABLE_PERMISSIONS[$permission])) {
            throw new \InvalidArgumentException("Wrong permission '" . $permission . "'");
        }

        if($id !== null) {
            $entity_permission = $entity_permission . '.' . $id;
        }

        return isset($this->permissions()[$entity_permission]);
    }
}

Also I want to store all revisions of permissions (when administrator changes user's permissions) this way (not production code):

\DB::transaction(function() {
    $g = App\Group::find(Input::get('group_id'));
    $p = App\Permission::find(Input::get('permission_id'));
    $permissions = $g->permissions()->lockForUpdate()->get()->pluck('id')->toArray();
    $new_permissions = array_merge($permissions, [$p->id]);
    $g->permissions()->sync($new_permissions);
    $permissions_revision = App\GroupPermissionRevision::firstOrNew(['group_id' => $g->id]);
    $permissions_revision->value = implode('|', $new_permissions);
    $permissions_revision->save();
});

And here is GroupPermissionRevision

namespace App;

use Illuminate\Database\Eloquent\Model;

class GroupPermissionRevision extends Model
{
    protected $table = 'app_group_permissions_revisions';

    use \Venturecraft\Revisionable\RevisionableTrait;

    protected $keepRevisionOf = ['value'];

    protected $fillable = ['group_id', 'value'];

}

$value would contain current permissions' ids (like this 10|35|42) and revisions table would contain history of permissions.

Is it good way to do it?



via Chebli Mohamed

Aucun commentaire:

Enregistrer un commentaire