Giter Club home page Giter Club logo

lock's Introduction

Lock - Acl for PHP 5.4+

Build Status Code Climate Test Coverage Software License Packagist Version Total Downloads

I'm sad to say that Lock is currently not maintained. I won't be able to offer support or accept new contributions for the current time being. Other priorities are keeping me from putting the work into Lock that it deserves. Eventually I'll try to pick up work again but unfortunately I cannot say when. My thanks goes out to all the contributors and users.

-- Dries

Lock is a flexible, driver based Acl package for PHP 5.4+.

Created by Dries Vints. Made possible thanks to BeatSwitch. Inspired by Authority by Matthew Machuga. Logo by Jerry Low.

Table of Contents

Terminology

  • Lock: An acl instance for a subject. This package currently ships with a CallerLock and a RoleLock
  • Caller: An identity object that can have permissions to do something
  • Driver: A storage system for permissions which can either be static or persistent
  • Permission: A permission holds an action and an optional (unique) resource. Can be either a Restriction or a Privilege
  • Restriction: A restriction denies you from being able to perform an action (on an optional resource)
  • Privilege: A privilege allows you to perform an action (on an optional resource)
  • Action: An action is something you are either allowed or denied to do
  • Resource: A resource can be an object where you can perform one or more actions on. It can either target a certain type of resource or a specific resource by its unique identifier
  • Role: A role can also hold multiple permissions. A caller can have multiple roles. Roles can inherit permissions from other roles

Features

  • Flexible acl permissions for multiple identities (callers)
  • Static or persistent drivers to store permissions
  • Action aliases
  • Roles
  • Conditions (Asserts)
  • Easily implement acl functionality on your caller or role with a trait

Introduction

Lock differs from other acl packages by trying to provide the most flexible way for working with multiple permission callers and storing permissions.

By working with Lock's Caller contract you can set permissions on multiple identities.

The Driver contract allows for an easy way to store permissions to a persistent or static storage system. A default static ArrayDriver ships with this package. Check out the list below for more drivers which have already been prepared for you. Or build your own by implementing the Driver contract.

You can set and check permissions for resources by manually passing along a resource's type and (optional) identifier or you can implement the Resource contract onto your objects so you can pass them along to lock more easily.

The Manager allows for an easy way to instantiate new Lock instances, set action aliases or register roles.

Drivers

If you need a framework-specific implementation, pick one of the already prepared drivers below.

  • ArrayDriver (ships with this package)
  • Laravel 5

Roadmap

  • Group Permissions
  • More drivers (Symfony, Zend Framework, Doctrine, ...)
  • Event Listeners

Installation

Install this package through Composer.

$ composer require beatswitch/lock

Usage

Implementing the Caller contract

Every identity which should have permissions to do something must implement the BeatSwitch\Lock\Callers\Caller contract. The Caller contract identifies a caller by requiring it to return its type and its unique identifier. Let's look at an example below.

<?php

use BeatSwitch\Lock\Callers\Caller;

class User implements Caller
{
    public function getCallerType()
    {
        return 'users';
    }

    public function getCallerId()
    {
        return $this->id;
    }

    public function getCallerRoles()
    {
        return ['editor', 'publisher'];
    }
}

By adding the getCallerType function we can identify a group of callers through a unique type. If we would at some point wanted to set permissions on another group of callers we could easily implement the contract on another object.

<?php

use BeatSwitch\Lock\Callers\Caller;

class Organization implements Caller
{
    public function getCallerType()
    {
        return 'organizations';
    }

    public function getCallerId()
    {
        return $this->id;
    }

    public function getCallerRoles()
    {
        return ['enterprise'];
    }
}

And thus we can easily retrieve permissions for a specific caller type through a driver.

Working with a static driver

If you'd like to configure all of your permissions beforehand you can use the static ArrayDriver which ships with the package. This allows you to set a list of permissions for a caller before your application is run.

use \BeatSwitch\Lock\Drivers\ArrayDriver;
use \BeatSwitch\Lock\Lock;
use \BeatSwitch\Lock\Manager;

// Create a new Manager instance.
$manager = new Manager(new ArrayDriver());

// Instantiate a new Lock instance for an object which implements the Caller contract.
$lock = $manager->caller($caller);

// Set some permissions.
$lock->allow('manage_settings');
$lock->allow('create', 'events');

// Use the Lock instance to validate permissions on the given caller.
$lock->can('manage_settings'); // true: can manage settings
$lock->can('create', 'events'); // true: can create events
$lock->cannot('update', 'events'); // true: cannot update events
$lock->can('delete', 'events'); // false: cannot delete events

Working with a persistent driver

Working with a persistent driver allows you to store permissions to a persistent storage layer and adjust them during runtime. For example, if you'd implement the Laravel 5 driver, it would store the permissions to a database using Laravel's database component. By creating your own UI, you could easily attach the acl functionality from this package to create, for example, a user management system where different users have different permissions.

Let's take a look at a very basic user management controller to see how that's done. We'll assume we get a bootstrapped lock manager instance with our Laravel DB driver.

<?php

use BeatSwitch\Lock\Manager;

class UserManagementController extends BaseController
{
    protected $lockManager;

    public function __construct(Manager $lockManager)
    {
        $this->lockManager = $lockManager;
    }

    public function togglePermission()
    {
        $userId = Input::get('user');
        $action = Input::get('action');
        $resource = Input::get('resource');

        $user = User::find($userId);

        $this->lockManager->caller($user)->toggle($action, $resource);

        return Redirect::route('user_management');
    }
}

Every time the togglePermission method is used, the user's permission for the given action and resource type will be toggled.

Setting and checking permissions

You can either allow or deny a caller from doing something. Here are a couple of ways to set and check permissions.

Allow a caller to create everything.

$lock->allow('create');

$lock->can('create'); // true

Allow a caller to only create posts.

$lock->allow('create', 'posts');

$lock->can('create'); // false
$lock->can('create', 'posts'); // true

Allow a caller to only edit a specific post with an ID of 5.

$lock->allow('edit', 'posts', 5);

$lock->can('edit'); // false
$lock->can('edit', 'posts'); // false
$lock->can('edit', 'posts', 5); // true

Allow a caller to edit all posts but deny them from editing one with the id of 5.

$lock->allow('edit', 'posts');
$lock->deny('edit', 'posts', 5);

$lock->can('edit', 'posts'); // true
$lock->can('edit', 'posts', 5); // false

Toggle a permission's value.

$lock->allow('create');
$lock->can('create'); // true

$lock->toggle('create');
$lock->can('create'); // false

You can allow or deny multiple actions at once and also check multiple actions at once.

$lock->allow(['create', 'edit'], 'posts');

$lock->can('create', 'posts'); // true
$lock->can(['create', 'edit'], 'posts'); // true
$lock->can(['create', 'delete'], 'posts'); // false

Clearing permissions

You can easily clear permissions for a set specific combination of actions and resources.

$lock->allow(['create', 'edit'], 'posts');

$lock->clear('edit', 'posts');

$lock->can('edit', 'posts'); // false
$lock->can('create', 'posts'); // true

You can also just clear all permissions for a lock instance.

$lock->allow('manage-posts');
$lock->allow(['create', 'edit'], 'users');

$lock->clear();

$lock->can('manage-posts'); // false
$lock->can('create', 'users'); // false

Setting an action alias

To group multiple actions and set them all at once you might want to set an action alias.

$lock->alias('manage', ['create', 'read', 'delete']);
$lock->allow('manage', 'posts');

$lock->can('manage', 'posts'); // true
$lock->can('create', 'posts'); // true
$lock->can('delete', 'posts', 1); // true
$lock->can('update', 'posts'); // false

Setting a God caller

You could easily set a caller which has all permissions for everything by passing the all wildcard as an action on the lock instance.

$lock->allow('all');

Now every "can" method call will validate to true for this caller.

Working with roles

Lock provides an easy way to working with roles. You can work with roles out of the box but if you want to work with inheritance, you'll need to register the roles to the manager instance.

$manager->setRole('guest');
$manager->setRole('user', 'guest'); // "user" will inherit all permissions from "guest"

Or register multiple roles at once.

$manager->setRole(['editor', 'admin'], 'user'); // "editor" and "admin" will inherit all permissions from "user".

Let's set some permissions and see how they are resolved.

// Allow a guest to read everything.
$manager->role('guest')->allow('guest', 'read');

// Allow a user to create posts.
$manager->role('user')->allow('create', 'posts');

// Allow an editor and admin to publish posts.
$manager->role('editor')->allow('publish', 'posts');
$manager->role('admin')->allow('publish', 'posts');

// Allow an admin to delete posts.
$manager->role('admin')->allow('delete', 'posts');

// Let's assume our caller has the role of "editor" and check some permissions.
$lock = $manager->caller($caller);
$lock->can('read'); // true
$lock->can('delete', 'posts'); // false
$lock->can('publish'); // false: we can't publish everything, just posts.
$lock->can(['create', 'publish'], 'posts'); // true

Something you need to be aware of is that caller-level permissions supersede role-level permissions. Let's see how that works.

Our caller will have the user role.

$manager->caller($caller)->allow('create', 'posts');

// Notice that we don't need to set the role in the
// manager first if we don't care about inheritance.
$manager->role('user')->deny('user', 'create', 'posts');

$manager->caller($caller)->can('create', 'posts'); // true: the user has explicit permission to create posts.

Working with conditions

Conditions are actually asserts which are extra checks you can set for permissions. You can pass an array with them as the last parameter of allow and deny. All conditions must implement the BeatSwitch\Lock\Permissions\Condition interface.

Warning: please note that conditions currently only work with static drivers.

Let's setup a condition.

<?php

use BeatSwitch\Lock\Lock;
use BeatSwitch\Lock\Permissions\Condition;
use BeatSwitch\Lock\Permissions\Permission;
use BeatSwitch\Lock\Resources\Resource;
use Illuminate\Auth\AuthManager;

class LoggedInCondition implements Condition
{
    /**
     * The Laravel AuthManager instance
     *
     * @var \Illuminate\Auth\AuthManager
     */
    protected $auth;

    /**
     * @param \Illuminate\Auth\AuthManager $auth
     */
    public function __construct(AuthManager $auth)
    {
        $this->auth = $auth;
    }

    /**
     * Assert if the condition is correct
     *
     * @param \BeatSwitch\Lock\Lock $lock                         The current Lock instance that's being used
     * @param \BeatSwitch\Lock\Permissions\Permission $permission The Permission that's being checked
     * @param string $action                                      The action passed to the can or cannot method
     * @param \BeatSwitch\Lock\Resources\Resource|null $resource  The resource passed to the can or cannot method
     * @return bool
     */
    public function assert(Lock $lock, Permission $permission, $action, Resource $resource = null)
    {
        // Condition will succeed if the user is logged in.
        return $this->auth->check();
    }
}

Now let's see how this will work when setting up a permission.

$condition = App::make('LoggedInCondition');

$lock->allow('create', 'posts', null, $condition);
$lock->can('create', 'posts'); // true if logged in, otherwise false.

You can also pass along multiple conditions.

$lock->allow('create', 'posts', null, [$falseCondition, $trueCondition]);
$lock->can('create', 'posts'); // false: there's at least one false condition

You can pass along as many conditions as you like but they all need to succeed in order for the permission to work.

You can also use a callback if you like.

$lock->allow('create', 'posts', null, function ($lock, $permission, $action, $resource = null) {
    return false;
});
$lock->can('create', 'posts'); // false because the callback returns false.

Retrieving allowed or denied resources

If you'd like to retrieve a list of resources which are allowed or denied to perform a particularly action you can use the allowed and denied methods on a Lock instance.

$lock->allow('update', 'users', 1);
$lock->allow('update', 'users', 2);
$lock->allow('update', 'users', 3);
$lock->deny('update', 'users', 2);

$lock->allowed('update', 'users'); // Returns [1, 3];
$lock->denied('update', 'users'); // Returns [2];

Please keep in mind that you can only retrieve id's from resources which have permissions set. Resources which aren't registered through Lock won't be returned.

Using the LockAware trait

You can easily add acl functionality to your caller or role by implementing the BeatSwitch\Lock\LockAware trait.

<?php

use BeatSwitch\Lock\Callers\Caller;
use BeatSwitch\Lock\LockAware;

class Organization implements Caller
{
    use LockAware;

    public function getCallerType()
    {
        return 'organizations';
    }

    public function getCallerId()
    {
        return $this->id;
    }

    public function getCallerRoles()
    {
        return ['enterprise'];
    }
}

Now we need to set its lock instance.

$caller->setLock($lock);

And now your caller can use all of the lock methods onto itself.

$caller->can('create', 'posts');
$caller->allow('edit', 'pages');

If you have a caller which implements the LockAware trait but haven't bootstrapped the caller's lock instance yet you can easily make the caller lock aware by using the manager's makeCallerLockAware method.

$caller = $manager->makeCallerLockAware($caller);

And now your caller will be able to use the LockAware methods. There's a similar method for roles.

$role = $manager->makeRoleLockAware('guest');

This will bootstrap a SimpleRole object which already comes with the LockAware trait in place.

Api

BeatSwitch\Lock\Lock

The following methods can all be called on a BeatSwitch\Lock\Lock instance.

can

Checks to see if the current caller has permission to do something.

can(
    string|array $action,
    string|\BeatSwitch\Lock\Resources\Resource $resource = null,
    int $resourceId = null
)

cannot

Checks to see if it's forbidden for the current caller to do something.

cannot(
    string|array $action,
    string|\BeatSwitch\Lock\Resources\Resource $resource = null,
    int $resourceId = null
)

allow

Sets a Privilege permission on a caller to allow it to do something. Removes any matching restrictions.

allow(
    string|array $action,
    string|\BeatSwitch\Lock\Resources\Resource $resource = null,
    int $resourceId = null,
    \BeatSwitch\Lock\Permissions\Condition[] $conditions = []
)

deny

Sets a Restriction permission on a caller to prevent it from doing something. Removes any matching privileges.

deny(
    string|array $action,
    string|\BeatSwitch\Lock\Resources\Resource $resource = null,
    int $resourceId = null,
    \BeatSwitch\Lock\Permissions\Condition[] $conditions = []
)

toggle

Toggles the value for the given permission.

toggle(
    string|array $action,
    string|\BeatSwitch\Lock\Resources\Resource $resource = null,
    int $resourceId = null
)

allowed

Returns all the id's in an array of the given resource type to which the subject is allowed to perform the given action on.

allowed(
    string|array $action,
    string|\BeatSwitch\Lock\Resources\Resource $resourceType
)

denied

Returns all the id's in an array of the given resource type to which the subject is denied to perform the given action on.

denied(
    string|array $action,
    string|\BeatSwitch\Lock\Resources\Resource $resourceType
)

BeatSwitch\Lock\Manager

The following methods can all be called on a BeatSwitch\Lock\Manager instance.

caller

Returns a BeatSwitch\Lock\Lock instance for a caller.

caller(
    \BeatSwitch\Lock\Callers\Caller $caller
)

role

Returns a BeatSwitch\Lock\Lock instance for a role.

role(
    \BeatSwitch\Lock\Roles\Role $role
)

alias

Add an alias for one or more actions.

alias(
    string $name,
    string|array $actions
)

setRole

Set one or more roles and an optional role to inherit permissions from.

setRole(
    string|array $name,
    string $inherit = null
)

makeCallerLockAware

Sets the lock instance for a caller which implements the LockAware trait. Returns the caller with the lock instance set.

makeCallerLockAware(
    \BeatSwitch\Lock\Callers\Caller $caller
)

makeRoleLockAware

Sets the lock instance for a role which implements the LockAware trait. Returns the role with the lock instance set.

makeRoleLockAware(
    \BeatSwitch\Lock\Roles\Role|string $role
)

Building a driver

You can easily build a driver by implementing the BeatSwitch\Lock\Drivers\Driver contract. Below we'll demonstrate how to create our own persistent driver using Laravel's Eloquent ORM as our storage mechanism.

We'll assume we have a CallerPermission model class with at least the following database columns:

  • caller_type (varchar, 100)
  • caller_id (int, 11)
  • type (varchar, 10)
  • action (varchar, 100)
  • resource_type (varchar, 100, nullable)
  • resource_id (int, 11, nullable)

And we have a RolePermission model with the following database columns:

  • role (varchar, 100)
  • type (varchar, 10)
  • action (varchar, 100)
  • resource_type (varchar, 100, nullable)
  • resource_id (int, 11, nullable)

Let's check out a full implementation of the driver below. Notice that for the getCallerPermissions method we're using the PermissionFactory class to easily map the data and create Permission objects from them. The PermissionFactory's createFromData method will accept both arrays and objects.

<?php

use BeatSwitch\Lock\Callers\Caller;
use BeatSwitch\Lock\Drivers\Driver;
use BeatSwitch\Lock\Permissions\Permission;
use BeatSwitch\Lock\Permissions\PermissionFactory;
use BeatSwitch\Lock\Roles\Role;
use CallerPermission;
use RolePermission;

class EloquentDriver implements Driver
{
    /**
     * Returns all the permissions for a caller
     *
     * @param \BeatSwitch\Lock\Callers\Caller $caller
     * @return \BeatSwitch\Lock\Permissions\Permission[]
     */
    public function getCallerPermissions(Caller $caller)
    {
        $permissions = CallerPermission::where('caller_type', $caller->getCallerType())
            ->where('caller_id', $caller->getCallerId())
            ->get();

        return PermissionFactory::createFromData($permissions->toArray());
    }

    /**
     * Stores a new permission into the driver for a caller
     *
     * @param \BeatSwitch\Lock\Callers\Caller $caller
     * @param \BeatSwitch\Lock\Permissions\Permission
     * @return void
     */
    public function storeCallerPermission(Caller $caller, Permission $permission)
    {
        $eloquentPermission = new CallerPermission;
        $eloquentPermission->caller_type = $caller->getCallerType();
        $eloquentPermission->caller_id = $caller->getCallerId();
        $eloquentPermission->type = $permission->getType();
        $eloquentPermission->action = $permission->getAction();
        $eloquentPermission->resource_type = $permission->getResourceType();
        $eloquentPermission->resource_id = $permission->getResourceId();
        $eloquentPermission->save();
    }

    /**
     * Removes a permission from the driver for a caller
     *
     * @param \BeatSwitch\Lock\Callers\Caller $caller
     * @param \BeatSwitch\Lock\Permissions\Permission
     * @return void
     */
    public function removeCallerPermission(Caller $caller, Permission $permission)
    {
        CallerPermission::where('caller_type', $caller->getCallerType())
            ->where('caller_id', $caller->getCallerId())
            ->where('type', $permission->getType())
            ->where('action', $permission->getAction())
            ->where('resource_type', $permission->getResourceType())
            ->where('resource_id', $permission->getResourceId())
            ->delete();
    }

    /**
     * Checks if a permission is stored for a user
     *
     * @param \BeatSwitch\Lock\Callers\Caller $caller
     * @param \BeatSwitch\Lock\Permissions\Permission
     * @return bool
     */
    public function hasCallerPermission(Caller $caller, Permission $permission)
    {
        return (bool) CallerPermission::where('caller_type', $caller->getCallerType())
            ->where('caller_id', $caller->getCallerId())
            ->where('type', $permission->getType())
            ->where('action', $permission->getAction())
            ->where('resource_type', $permission->getResourceType())
            ->where('resource_id', $permission->getResourceId())
            ->first();
    }

    /**
     * Returns all the permissions for a role
     *
     * @param \BeatSwitch\Lock\Roles\Role $role
     * @return \BeatSwitch\Lock\Permissions\Permission[]
     */
    public function getRolePermissions(Role $role)
    {
        $permissions = RolePermission::where('role', $role->getRoleName())->get();

        return PermissionFactory::createFromData($permissions->toArray());
    }

    /**
     * Stores a new permission for a role
     *
     * @param \BeatSwitch\Lock\Roles\Role $role
     * @param \BeatSwitch\Lock\Permissions\Permission
     * @return void
     */
    public function storeRolePermission(Role $role, Permission $permission)
    {
        $eloquentPermission = new RolePermission;
        $eloquentPermission->role = $role->getRoleName();
        $eloquentPermission->type = $permission->getType();
        $eloquentPermission->action = $permission->getAction();
        $eloquentPermission->resource_type = $permission->getResourceType();
        $eloquentPermission->resource_id = $permission->getResourceId();
        $eloquentPermission->save();
    }

    /**
     * Removes a permission for a role
     *
     * @param \BeatSwitch\Lock\Roles\Role $role
     * @param \BeatSwitch\Lock\Permissions\Permission
     * @return void
     */
    public function removeRolePermission(Role $role, Permission $permission)
    {
        RolePermission::where('role', $role->getRoleName())
            ->where('type', $permission->getType())
            ->where('action', $permission->getAction())
            ->where('resource_type', $permission->getResourceType())
            ->where('resource_id', $permission->getResourceId())
            ->delete();
    }

    /**
     * Checks if a permission is stored for a role
     *
     * @param \BeatSwitch\Lock\Roles\Role $role
     * @param \BeatSwitch\Lock\Permissions\Permission
     * @return bool
     */
    public function hasRolePermission(Role $role, Permission $permission)
    {
        return (bool) RolePermission::where('role', $role->getRoleName())
            ->where('type', $permission->getType())
            ->where('action', $permission->getAction())
            ->where('resource_type', $permission->getResourceType())
            ->where('resource_id', $permission->getResourceId())
            ->first();
    }
}

Notice that we're not checking if the permission already exists when we're attempting to store it. You don't need to worry about that because that's all been done for you in the Lock instance.

Now we have a driver which supports storing of permissions for callers and roles.

Testing your driver

It's very easy for you to make sure your driver works as expected. If you're building a persistent driver you can easily test it by creating a PHPUnit test which extends the PersistentDriverTestCase class.

<?php

use BeatSwitch\Lock\Tests\PersistentDriverTestCase;

class EloquentDriverTest extends PersistentDriverTestCase
{
    public function setUp()
    {
        // Don't forget to reset your DB here.
        
        // Bootstrap your driver.
        $this->driver = new EloquentDriver();

        parent::setUp();
    }
}

And this is all you need! The PersistentDriverTestCase contains all the tests you'll need to make sure your driver works as expected. So if all those tests pass then your driver was set up correctly. No need to mock anything, this is a pure integration test case. Of course in this specific example above, for Eloquent to work you'll need to bootstrap Laravel. Working with a database like sqlite would be the best way here to test your driver.

Maintainer

Lock is unmaintained at this moment.

This package is currently maintained by Dries Vints.
If you have any questions please don't hesitate to ask them in an issue.

Contributing

Please see the contributing file for details.

Changelog

You can see a list of changes for each release in our changelog file.

License

The MIT License. Please see the license file for more information.

lock's People

Contributors

driesvints avatar grahamcampbell avatar mickrip avatar njohns-grovo avatar remipelhate avatar zspine avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

lock's Issues

The problem about creating new role and the structure.

Let's say I have three roles: Admin, Editor, User.

Admin can edits everyone's posts.
Editor can edits everyone's posts except admin's.
User can only edits their own posts.

Database structure

Post table

id author content
1 ImUser foobar.
2 ImEditor hello, world!
3 ImAdmin Hi!

User table

username rank
ImUser user
ImEditor editor
ImAdmin admin

User Permission table

Insert a new row each time the user create a post,

ImUser and ImEditor created the post, so they got the permission to edit their own post.

username action resource_type resource_id
ImUser edit posts 1
ImEditor edit posts 2

Role Permission table

Admin can edit any posts so we set the resource_id as null.

The row of the editor one has been inserted after ImUser created the post,

so editor has the permission to edit the post #1.

role action resource_type resource_id
admin edit posts NULL
editor edit posts 1

Problem

So If I want to create a new role named 'moderator' for example,

and moderator should've the permission to edit editor and user's posts,

but because we added the role after ImUser and ImEditor created their posts (#1 and #2)

now moderator has no permission to edit the post #1 and #2, how can I solve this problem?

And won't it be such a mess If I do really use this structure?

Think about this if I have many roles and posts:

role action resource_type resource_id
admin edit posts NULL
admin remove posts NULL
editor edit posts 1
editor remove posts 1
moderator edit posts 1
moderator remove posts 1

.. and many rows ..

Summarize

1.How do I fix the permission problem?
2.Is it bad to use this structure or everyone use it like this?

It's my first time to use the ACL system, any ideas even not an answer would be appreciated.

Schema

Is there a DB Schema? I can't seem to find it in this Repo...

Additional tests, eg: allow edit of own posts

Maybe I missed it in the README, but how would you go about allowing a role/caller to only allow something based on an assertion (function, method, etc)? Like if you have a user/role, and you only want to allow them editing their own posts. I see the example of using a static integer, but not sure how you could instead do something like this. Just wondering.
Thank you.

Rename CallerTrait?

I'm not happy with suffixing traits with Trait. It's not very expressive. Perhaps one of the following?

  • CallerAware
  • LockAware
  • AclAware
  • ...

Preference? Suggestions?

An example of permission checking on a specific instance(s)

Consider the following url:
/users/1/tasks/2

I want to ensure that only the user with an id of 1 can get to their specific task.

I would also like to have a super admin (let's say that user as an id of 8) be able to access both

/users/8/tasks/4
and
/users/1/tasks/2

The super admin can access any task. Standard users that go to a task that isn't their own will get denied.

Is there a fetchOwn('tasks') method or a similar way to implement the example above?

Tying multiple users to a role

I can create a role by doing:

LockManager::role('auth-user')->allow('create', 'tasks');

That will be the equivalent of a database insert statement

//only auth-user's can create tasks
DB::table('lock_permissions')->insert(
    [
                'caller_type'       => null,
                'caller_id'         => null,
                'role'              => 'auth-user',
                'type'              => 'privilege',
                'action'            => 'create',
                'resource_type'     => 'tasks',
                'resource_id'       => null,
    ]
);

That makes sense.

I can create user a permission by doing:

LockManager::caller($user)->allow('create', 'tasks');

That would be an insert equivalent of (assuming the user has an id of 1):

//user with id of 1 can create tasks
DB::table('lock_permissions')->insert(
    [
                'caller_type'       => 'users',
                'caller_id'         => 1,
                'role'              => '', //notice that user doesn't have a role name
                'type'              => 'privilege',
                'action'            => 'create',
                'resource_type'     => 'tasks',
                'resource_id'       => null,
    ]
);

That makes since to me too.

Where things break down for me is when I want to create the auth-user role above but then attach multiple users to that role. It's repetitive to have individual user permissions that each allow access to create tasks.

Is that possible to do using this package? If so, could you provide an example of how to attach roles to users?

The other thing that really confused me was the use of

\BeatSwitch\Lock\Callers\Caller::getCallerRoles()

In the example you had this method return a static array ie) ['editor', 'publisher']. I would think that the getCallRoles() method would query the lock_permissions table and grab all the roles for that caller instance.

Split Driver interface into Driver\Read and Driver\Write?

I've currently created a ReadOnlyDriver but something tells me I could best split up the Driver interface into a Driver\Read and a Driver\Write and require the developer to optionally implement the write one. This would make it easier to set up a Lock instance which is only used for reading permissions from a storage.

Good idea?

Our requirements - how to implement?

We have a role system whereby denial is always more important than an allow.

  1. A user can be assigned to many roles
  2. Each role has a number of resources and permissions
  3. Each permission has an allow/inherit/deny set of permissions.
  4. Inherit means basically do nothing
  5. Allow is only allowed if no deny is specified on any other roles for that action/resource

How can we implement this using lock?

Discussion: tagging stable

Good Evening!
I'm opening this issue as a discussion to find out what is left for this to be tagged as 1.0 stable? This pr ( #42 ) exists to provide an easy transition path to that in the interim but it would be great to get this 1.0-stable :)

Lock permission clear

Hi,

is there any particular reason why you only remove permission that is instance of Privilege but not Restriction?

How to get all permissions?

We can use $lock->allowed() to retrieve a list of allowed resources.
How can we get the list of any available permission for a given caller?

Laravel sample app

Not an issue but a request.

Is it possible to see a very simple sample laravel app

Thanks

allowed method returns only explicit resource

the allowed method only return explicit resource -> it doesn't return resources allowed to role that a caller inherits.

Ie :
Role ABC has privilege on Resource 1
And
Caller XYZ has privilege on Resource 2
And
Caller XYZ has Role ABC

when I make : $lock->caller($caller)->allowed(xxxx), it returns only [Resource 2]

Should the Manager class handle multiple drivers?

Currently the Manager class accepts a single driver to store all permissions. I was thinking about allowing multiple drivers to be set. So some can be ready only with the storePermission and deletePermission methods not do anything on a read-only driver. But then you can do things like:

$manager->driver('production')->caller($caller)->can(...)
$manager->driver('local')->caller($user)->can(...)

This makes it easy to determine permissions locally for production settings.

Would this be a nice to have?

Individual users should be able to have different roles

Is it possible to have individual users have different roles? I see that get Caller::getCallerRoles() allows you to set what role you would like to have, but that is for all User instances.

I would like for getCallerRoles() to return a different value depending on who, I'm logged in as. I would like to query the db to return this value instead of having it hard coded. When I tried this approach, I realized that caller data and role data can never be set at the same time in the lock_permissions table.

Returning all permissions on a caller

Is there a way to return a list of all privileges and/or restrictions on a given caller?

I'd like to return a list of the logged in user's permissions so I can display/edit them in an admin panel, and I can't see a way to easily retrieve all permissions.

Thanks!

Why differs the sample implementation of a driver from the laravel lock?

The Laravel lock uses a single table, see this file. But the example implementation in the readme.md shows two tables. Why? And why would I use the two tables instead of the single table from the Laravel plugin?

I'm asking because I'm building a CakePHP 3.0 plugin which has been so far surprisingly easy because the ORM is almost the same as Laravals. I copied the Laravel driver and started adapting the code and I think I'm already done with the very basics (test aside). My only show stopper right now is if I should or have to use one or two tables for a better implementation.

Edit: After reading the code a little more I guess the two table solutions would be better because I can see that the driver interface features Role and Caller methods? So I assume the methods that have Role in their name should use the roles table and the other methods the callers table? Is that how it is thought?

Implement $lock->clear() method?

Would it make sense to implement a clear method for a lock instance to clear any matching permissions, either privileges or restrictions? This could be an easy way to clear permissions to get a fresh start. But I'm not sure if it would be very useful for many people.

The api would be:

public function clear(
    string|array $action,
    string|\BeatSwitch\Lock\Contracts\Resource $resource = null,
    int $resourceId = null
)

Thoughts?

Should there be a find() method?

There seems to be one thing missing from this library that I miss from most ACL implementations but which IMHO is important for developers: Getting a list of resource IDs.

Imagine I have a couple of hundred users and a couple of thousand events. Right now it seems that the only way to get a list of events that a user can access is to load them all and check them in a loop. That is terribly inefficient.

I would like a method like this:

$lock->find('update', 'events'); // returns array of event IDs the caller is allowed to update

Caching

I've just gone through the library, but didn't find any results for this one.

Is it possible to integrate caching somehow? IMO it is much more cheaper in performance to cache permission/privileges when using a persistent driver.

PhalconDriver assertion failures

Hi guys,

I'm trying to write a Phalcon driver for Lock, but testing it with the PersistentDriverTestCase you've provided, I get 10 failures. Since Phalcon's ORM isn't much different from Laravel's Eloquent, to which you've kindly provided a driver, I felt it should be a pretty easy job converting that driver to Phalcon, but I'm at a loss now.
Maybe these failures give you a clue what I've done wrong or where to look for the problems? The failures are as follows:

it_can_check_multiple_permissions_at_once
PersistentDriverTestCase.php:230

it_can_clear_multiple_permissions_at_once
PersistentDriverTestCase.php:242

it_only_clears_the_requested_permissions
PersistentDriverTestCase.php:255

it_only_clears_permissions_on_the_given_resource
PersistentDriverTestCase.php:268

it_can_clear_every_single_permission_for_a_lock_instance
PersistentDriverTestCase.php:282

it_can_toggle_multiple_permissions_at_once
PersistentDriverTestCase.php:329

it_can_check_actions_from_aliases
PersistentDriverTestCase.php:367

it_can_work_with_roles
PersistentDriverTestCase.php:402

it_can_return_allowed_resource_ids
PersistentDriverTestCase.php:452

it_can_return_denied_resource_ids
PersistentDriverTestCase.php:470

I'm grateful and happy about any feedback you can give me. Thank you for coding this very cool package.
Cheers, Christian

Storing Aliases

Hello Everyone,

I have a problem/question.

In the config there is the permissions section and on the comments it says that this should only be used if you are using the array driver.

so my question is where am I suppose to define what the aliases are? and are they not suppose to be stored in the database if you are using the database driver?

Thank You

Rename clear method to something else?

Apparently clear is a bit too confusing to what it means. For some people it means clearing (allowing) someone to do something but it's currently implemented as removing permissions.

Any suggestions for another phrasing?

Move interfaces and exceptions where they belong

Currently the interfaces are in their own namespace Contracts. I believe each of these interfaces should be in the namespace where they belong.

For instance, the Caller interface and InvalidCaller exception should be in the Callers namespace. This adds more cohesion in the package and removes generic namespaces. Think about one namespace to rule them all. It makes it simpler. Classes inside the Callers namespace will no longer have to imported and when you look inside the namespace everything is nicely packed together.

Having them in a separate namespace, in my opinion, is a smell and feels like something is leaking.

Anyhow, this is my 2 cents. Do with it what you like!

Cheers

Flushing drivers

I think it would be better to be able to flush drivers at some point instead of updating them all upon request. This would allow saving roles at once. With a good ORM behind all queries to the database are optimized for the best performance.

Removing permission of specific resource removes all role permissions

Well i wrote a driver for Doctrine and thought I must have made a strange mistake. But it turns out that this error also happens with the ArrayDriver:

$manager->role('somegroup')->allow('update', 'jobs');
$manager->role('somegroup')->allow('create', 'jobs');
$manager->role('somegroup')->deny('update', 'jobs', 42);

In my logic this should only deny the resource "42" but the drivers removeRolePermission method receives a Permission object without a resource_id so it deletes the entire permission.

$this->assertTrue($manager->role('somegroup')->can('create', 'jobs')); // is true
$this->assertTrue($manager->role('somegroup')->can('update', 'jobs', 1)); // is false
$this->assertFalse($manager->role('somegroup')->can('update', 'jobs', 42)); // is false

Did i got something wrong?

Logic of results of allowing or denying permissions

This is an important one. Currently it's so that a top level allow or deny clears any lower level privilege or restriction. So for example, when you allow only an an action of create you basically get permission to create everything (every resource). It will also clear any restrictions in place for any resource where you wouldn't be allowed to create anything.

Practical example:

// Deny a caller from creating users and events.
$lock->deny('create', 'users');
$lock->deny('create', 'events');

// Allow the caller to create everything. After this he can both create users and events again.
$lock->allow('create');

This is the way I deem it to be logic but I don't know if the majority of you also thinks this is the logical way to do something.

Any thoughts or concerns that may arise? This is something I really want to tackle as early as possible so please give enough feedback on this.

Also: if you at any point find the current Acl not to behave in the way that makes sense for you then please tell me here so we can clear things out and improve it or make it more clear in the docs how things work.

Split driver interface into CallerDriver and RoleDriver?

I'm thinking about splitting these contracts so people can optionally implement the RoleDriver contract. This allows them to not specifically have to user the role functionality.

This could get messy if we also do #4 since we would have 4 interfaces that way.

Thoughts?

Allowed/Denied caller/role for a resource

Hi !

Is it possible to get an array of all callers and/or roles having privilege/restriction of a resource ?

For the moment, it's possible to make something like this $caller->getPersmissions()
But it would useful for me to be able to make something like this $resource->getAllowedCallers() (and the same for Roles and the same for "restriction" permission)

Add sync method to easily sync permissions for a resource

$manager->caller($caller)->sync(array $actions, $resource = null, $resourceId = null);

This will remove all allowed permissions which aren't in the $actions list and add allowed permissions which are in the $actions list. Could be useful when you need to change all of the actions at once,

0.1.0

These things need to be done before a stable 0.1.0 can be released.

Functionality

  • Action Aliases
  • Permission Conditions
  • Roles
  • Add abstract ReadOnlydriver
  • Extensive review and refactor of Lock class

Issues

Questions

Documentation

  • Write more extensive examples on using the Acl and the pre-defined classes that ship with the package. Also make sure to document the logic of the Acl and the hierarchy of permissions.
  • How to create drivers (and test them with the LockTestCase)
  • Create read-only driver
  • Document LockAware trait

Drivers

  • Write Laravel driver (separate package)

Package

  • Setup code coverage
  • Setup Scrutinizer
  • Add CONTRIBUTORS.md file
  • Add CHANGELOG.md file

Allow resources to inherit permissions

I've come to the conclusion that it would be nice if resources could inherit permissions from other resources. For example, take a contact entity. The contact could be either a person or a company. I want a caller or a role to both be able to create persons and companies but they should only be able to edit a person record and not a company. Currently this would be solved by doing the following:

$manager->caller($user)->allow('create', 'person');
$manager->caller($user)->allow('create', 'company');
$manager->caller($user)->allow('edit', 'person');

By allowing for permission inheritance through resources I could add a new method to the Resource interface called getInheritedResource which would return the resource type from which the resource inherits permissions from. So a Person entity could look like this:

<?php

use BeatSwitch\Lock\Resources\Resource

class Person extends Contact implements Resource
{
    private $id;

    public function getResourceType()
    {
        return 'person';
    }

    public function getResourceId()
    {
         return $this->id;
    }

    public function getInheritedResource()
    {
        return parent::getResourceType();
    }
}

And now it's easy to set permissions on a parent resource type and reducing the number of calls we have to make:

$manager->caller($user)->allow('create', 'contact');
$manager->caller($user)->allow('edit', 'person');

What do you guys think about this?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.