Implementing Acl for My PHP Application

Implementing ACL for my PHP application

It sounds like you are going to need a role-based access control system. Developing one is not
really a trivial task, so as already suggested, finding a framework or ready-made class that does
the job would be a worth while start.

Role Based Access Control

http://www.tonymarston.net/php-mysql/role-based-access-control.html

http://www.sqlrecipes.com/database_design/fine_grained_role_based_access_control_rbac_system-3/

http://www.sitepoint.com/forums/showthread.php?threadid=162027

You should create a table wher you have to define all type of role.

and one table for users

relate different roles to different user Via linking two tables.
and some thing like this ......

How should I be implementing my ACL in a web application?

That's basically the same approach I take in my own web apps (and a bit of trial and error has gone into that for me). The only difference is, I'd probably use a table which has the different permissions as columns, so that if you want to add more permissions later on, you can. Using bits in an integer limits you to a fixed number of permissions, namely as many bits as there are in the integer. Typically that would be 32 which I suppose is probably enough, but I prefer not to limit myself that way.

For what it's worth, that's also the model that phpBB uses (permissions as table columns), and if it's good enough for arguably the most popular PHP web app, it's probably good enough for you ;-)

How can I implement an Access Control List in my Web MVC application?

First part/answer (ACL implementation)

In my humble opinion, the best way to approach this would be to use decorator pattern, Basically, this means that you take your object, and place it inside another object, which will act like a protective shell. This would NOT require you to extend the original class. Here is an example:

class SecureContainer
{

protected $target = null;
protected $acl = null;

public function __construct( $target, $acl )
{
$this->target = $target;
$this->acl = $acl;
}

public function __call( $method, $arguments )
{
if (
method_exists( $this->target, $method )
&& $this->acl->isAllowed( get_class($this->target), $method )
){
return call_user_func_array(
array( $this->target, $method ),
$arguments
);
}
}

}

And this would be how you use this sort of structure:

// assuming that you have two objects already: $currentUser and $controller
$acl = new AccessControlList( $currentUser );

$controller = new SecureContainer( $controller, $acl );
// you can execute all the methods you had in previous controller
// only now they will be checked against ACL
$controller->actionIndex();

As you might notice, this solution has several advantages:

  1. containment can be used on any object, not just instances of Controller
  2. check for authorization happens outside the target object, which means that:

    • original object is not responsible for access control, adheres to SRP
    • when you get "permission denied", you are not locked inside a controller, more options
  3. you can inject this secured instance in any other object, it will retain the protection
  4. wrap it & forget it .. you can pretend that it is the original object, it will react the same

But, there are one major issue with this method too - you cannot natively check if secured object implements and interface ( which also applies for looking up existing methods ) or is part of some inheritance chain.

Second part/answer (RBAC for objects)

In this case the main difference you should recognize is that you Domain Objects (in example: Profile) itself contains details about owner. This means, that for you to check, if (and at which level) user has access to it, it will require you to change this line:

$this->acl->isAllowed( get_class($this->target), $method )

Essentially you have two options:

  • Provide the ACL with the object in question. But you have to be careful not to violate Law of Demeter:

    $this->acl->isAllowed( get_class($this->target), $method )
  • Request all the relevant details and provide the ACL only with what it needs, which will also make it a bit more unit-testing friendly:

    $command = array( get_class($this->target), $method );
    /* -- snip -- */
    $this->acl->isAllowed( $this->target->getPermissions(), $command )

Couple videos that might help you to come up with your own implementation:

  • Inheritance, Polymorphism, & Testing
  • Don't Look For Things!

Side notes

You seem to have the quite common ( and completely wrong ) understanding of what Model in MVC is. Model is not a class. If you have class named FooBarModel or something that inherits AbstractModel then you are doing it wrong.

In proper MVC the Model is a layer, which contains a lot of classes. Large part of the classes can be separated in two groups , based on the responsibility:

- Domain Business Logic

(read more: here and here):

Instances from this group of classes deal with computation of values, check for different conditions, implement sales rules and do all the rest what you would call "business logic". They have no clue how data is stored, where it is stored or even if storage exists in first place.

Domain Business object do not depend on database. When you are creating an invoice, it does not matter where data comes from. It can be either from SQL or from a remote REST API, or even screenshot of a MSWord document. The business logic does no change.

- Data Access and Storage

Instances made from this group of classes are sometimes called Data Access Objects. Usually structures that implement Data Mapper pattern ( do not confuse with ORMs of same name .. no relation ). This is where your SQL statements would be (or maybe your DomDocument, because you store it in XML).

Beside the two major parts, there is one more group of instances/classes, that should be mentioned:

- Services

This is where your and 3rd party components come in play. For example, you can think of "authentication" as service, which can be provided by your own, or some external code. Also "mail sender" would be a service, which might knit together some domain object with a PHPMailer or SwiftMailer, or your own mail-sender component.

Another source of services are abstraction on to on domain and data access layers. They are created to simplify the code used by controllers. For example: creating new user account might require to work with several domain objects and mappers. But, by using a service, it will need only one or two lines in the controller.

What you have to remember when making services, is that the whole layer is supposed to be thin. There is no business logic in services. They are only there to juggle domain object, components and mappers.

One of things they all have in common would be that services do not affect the View layer in any direct way, and are autonomous to such an extent, that they can be ( and quit often - are ) used outside the MVC structure itself. Also such self-sustained structures make the migration to a different framework/architecture much easier, because of extremely low coupling between service and the rest of application.

Implementing Roles based access control in php application

Unless you are not a hoster you probably don't want to create database users dynamically via scripts. Think about it:

A database user is a component that interacts with your application and not with your end-users.

So stick with one database for your application and use dedicated tables to build your ACL.
Your database schema could look like this:

users( id:pk, name )
roles( id:pk, name )
permissions( id:pk, key, description )
permission_role( permission_id:fk, role_id:fk )
role_user( role_id:fk, user_id:fk )

You basically have three things here:

  • Users: Nothing special. Just a user that can be uniquely defined by it's id
  • Permissions: Basically just a store of keys that will be queried within your script to check for permissions.
  • Roles: The glue between Users and Permissions. The sum of roles that a User belongs to and it's permissions will define the entirety of what a user is permitted to do and what not.

That's the basic setup. The other tables are just helper tables to join pieces together.

Your code handles the rest. Setup a (or better more) class that do the heavy lifting. Then pass an instance of your ACL to your User class (other implementations are of course possible. See at the bottom of the post).

<?php 

class Acl implements AclInterface {

public function hasPermissionTo( $action )
{
// Query DB and check if a record exists
// in the role_user table where the
// user_id matches with the current user
// and join the role_id with `roles` and then
// with `permission_role` to see if the user
// is permitted to perform a certain action
}

}

class User {

protected $acl;

public function __construct( AclInterface $acl )
{
$this->acl = $acl;
}

public function hasPermissionTo( $action )
{
return $this->acl->hasPermissionTo( $action );
}

}

You should get the basic concept. The actual implementation is up to you. Things you may want to consider:

  • Do you want your ACL to be part of your User or rather a standalone component?
  • How do you want to pass the current user to the ACL? Do you want to pass an instance of the current user to the ACL, or rather only the user ID?

Those question depend on what you like and on your architecture.
Happy Coding!

ACL implementation for databases

Since you mentioned its going to be simple so this is what I can suggest you.

At the time of login get the user id and then run a query with the id user as

select 
a.au_name,a.au_id
from USER_GROUPS ag
inner join SYS_USERS su on su.id = ag.sys_users_id
inner join AUTH a on a.au_id = ag.auth_id
where ag.sys_users_id = {id of the user retrieved after the login validation}

Now Execute the above query and get the au_name and store it in a session variable as

$_SESSION['au_name'] = {auth name from the above query} ;

Create a function as below and execute it after the login.

get_page_access($au_id){
run a query to get all the pages for the auth id you got from previous query
store them in an array and finally to a session variable as
$_SESSION['page_access'] = $array ;
$array will hold all the pages you retrive
}

Now do the redirect based on the $_SESSION['au_name'] firstime after the login.

Now what if user hotlink an URL i.e. a non-admin user try to access a page. So for that create a file called check_access.php and add include it to all the pages other than the login page.

In this page you get the URL using PHP and get the filename from the URL, then check if that filename exists on the array $_SESSION['page_access'] and if yes user is allowed to view the page else show message.

Make sure you do session_start() before the include .

This will be fairly simple in nature

ACL for php applications

Question comes down to how big your system is, how many users do you have, how many different functions do you have, etc. I would recommend doing it the database way, it can be tedious but it allows you to grow and there are many plugins out there that make ACL a lot easier to manage, i.e. http://www.alaxos.net/blaxos/pages/view/plugin_acl_2.0

Simple PHP/MySQL ACL System

I will leave writing the code to you for fun.

Assume you are storing all the previously queried permissions in a variable called $_SESSION['acl']

Your ACL function should:

  1. check the session if you already queried that entity
  2. if it is not set, read it from the db

in short

function..... {
if(!isset($_SESSION['acl'][$entity_id])) {
$_SESSION['acl'][$entity_id] = query here to return to you if he has access or not
}
return $_SESSION['acl'][$entity_id];
}

You can also read the entire array when you log in the user. That might also be appropriate. In that case you should be able to just

return $_SESSION['acl'][$entity_id];

But I would then try and catch an exception in case it is not set.

Role Based Access Control

Brandon Savage gave a presentation on his PHP package "ApplicationACL" that may or may not accomplish role-based access. PHPGACL might work as well, but I can't tell you for sure.

What I can tell you, however, is the Zend_ACL component of the Zend Framework will do role-based setups (however you'll have to subclass to check multiple roles at once). Granted the pain of this is you'll have to pull out Zend_ACL, I do not believe it has any external dependencies, from the monolithic download (or SVN checkout).

The nice thing about Zend_ACL is though its storage agnostic. You can either rebuild it every time or it's designed to be serialized (I use a combination of both, serialize for the cache and rebuild from the DB).

Using the ACL library to manage permissions for action paramaters

Phalcons ACL is based on 'Resources', in which a Resource can technically be anything you desire, you're not limited to controllers only.

http://docs.phalconphp.com/en/latest/api/Phalcon_Acl.html

How to implement acl and authorization in Module.php in zf3

This is how I implemented it. In your Module.php, add a listener on EVENT_DISPATCH, with a closure as callback that will call your middleware class authorization :

class Module implements ConfigProviderInterface
{
public function getConfig()
{
return include __DIR__ . '/../config/module.config.php';
}

public function onBootstrap(MvcEvent $e)
{
$app = $e->getApplication();
$eventManager = $app->getEventManager();
$serviceManager = $app->getServiceManager();

// Register closure on event DISPATCH, call your checkProtectedRoutes() method
$eventManager->attach(MvcEvent::EVENT_DISPATCH, function (MvcEvent $e) use ($serviceManager) {
$match = $e->getRouteMatch();
$auth = $serviceManager->get(Middleware\AuthorizationMiddleware::class);
$res = $auth->checkProtectedRoutes($match);
if ($res instanceof Response) {
return $res;
}
}, 1);

// Init ACL : could be improved
$this->initAcl($e);
}

You should have an AuthorizationMiddlewareFactory (call it as you want):

<?php

namespace MyModule\Factory;

use Interop\Container\ContainerInterface;
use MyModule\Middleware\AuthorizationMiddleware;
use Zend\Authentication\AuthenticationService;
use Zend\ServiceManager\Factory\FactoryInterface;

class AuthorizationMiddlewareFactory implements FactoryInterface
{
public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
{
$authService = $container->get(AuthenticationService::class);
$acl = $container->get('Acl'); // II init it in bootstrap(), could be improved
$response = $container->get('Response');
$baseUrl = $container->get('Request')->getBaseUrl();
$authorization = new AuthorizationMiddleware($authService, $acl, $response, $baseUrl);
return $authorization ;
}
}

And your AuthorizationMiddleware class:

<?php

namespace MyModule\Middleware;

use Symfony\Component\VarDumper\VarDumper;
use Zend\Authentication\AuthenticationService;
use Zend\Http\PhpEnvironment\Response;
use Zend\Permissions\Acl\Acl;
use Zend\Router\RouteMatch;

class AuthorizationMiddleware
{
private $authService ;
private $acl;
private $response;
private $baseUrl;

/**
* AuthorizationMiddleware constructor.
* @param AuthenticationService $authService
* @param Acl $acl
* @param Response $response
* @param $baseUrl
*/
public function __construct(AuthenticationService $authService, Acl $acl, Response $response, $baseUrl)
{
$this->authService = $authService;
$this->acl = $acl;
$this->response = $response;
$this->baseUrl = $baseUrl;
}

public function checkProtectedRoutes(RouteMatch $match)
{
if (! $match) {
// Nothing without a route
return null ;
}
// Do your checks...
}

It can be improved, but you have the idea... See also this Question and the answers: ZF3 redirection after ACL authorization failed



Related Topics



Leave a reply



Submit