Yii Access Control Lists [unstable]

I’m new to Yii and therefore I would appreciate any comments/remarks on this extension. Espesially: Is this “Yii-style”?

Contents (excerpt from the Readme):

  1. Preliminaries: What does this extension do?

    • How does ACL work?

    • Features of this extension

    • Limitations of this extension

    • How does ACL differ from RBAC?

  2. What does this extension do?


This extension provides a full-fledged implementation of ACL-based access

control. It’s ideas are heavily leaned on the way cakePHP implements ACL, but extended

both in convenience and power.

How does ACL work?


---------------------





In the following I'll only elucidate the really important concepts inherent in ACL - and I'll            


try to be as brief as possible. For detailed description of how ACL works, delve into specialized 


texts.





Every ACL-system consists of at least two types: the ACOs (Access Control Objects) and AROs (Access 


Request Objects).


ACOs are all the objects which are accessed (=> passive behavior), in particular on which specific 


actions are performed. Their counterpart are the AROs. Those objects take the active part, they try 


to perform specific actions on the ACOs. 


                                                                                               


Each single ARO-node can be linked to several ACOs. All connections between AROs and ACOs also 


indicate what actions may be performed by the involved ARO-node on the involved ACO-node.  In 


addition to that, both ACOs and AROs may be located in a hierarchy. Thereupon they can have parents, 


inheriting the permissions of the parent node.





If an action is to be performed, a connection between the ACO and ARO is sought, which has to grant 


the specified action. If no such connection is found, or if the connection does not grant the desired


 action, the access is denied.











Features:


----------------------





- multiple groups for each node (both ACO and ARO) 


- Unlimited depth in hierarchy


- non-recursive permission lookup 


    (complexity: ~ a * n * log(n) where a >= 4 and n is the number of nodes)


- convenient, integrated interface


- transactions as well as exceptions upon crucial errors


- integrated caching for an improved performance





Limitations:


----------------------


- this extension supports solely positive rights-management


  => the access check only checks if you are explicitely allowed to do something, it doesn't check


     if you are explicitely _denied_ access


- Due to it's strategy (see "Internals") this extension can perform lookups very fast. On the other


  hand, changes of the permissions (granting, denying, grouping) are very expensive operations.


  If you are changing permissions very often in compared to lookups, this is not what you want.








How does ACL differ from RBAC?


-------------------------------





As the name already states, Role-based Access Control grants or denies permissions through roles. 


Access Control Lists uses a more general approach using generalized objects and their relations. In 


general, ACL is more suited if the access control has to be fine-grained at the 


object-/document-level. That is, if specific permissions to specific ACOs attached to AROs take 


precedence over specific permissions to types of ACOs attached to specific users. 





The gist is that the builtin Yii RBAC-system only allows so-called business-rules to check if a given


object "belongs" to the given user (or one of his groups).


This has several drawbacks:





1) In order to check the business-rules Yii has to load every single rule out of the database and 


    execute it (it's plain PHP-code)





2) Those rules are fixed (PHP-expressions). Therefore one is not able to alter the permissions 


    dynamically without rewriting all the rules.





3) Those rules tend to be broader and thus there is no really convient "fine-grained" control 


    possible. Of course it's possible to achieve the same things possible with ACL using RBAC. Even 


    with a Smart you can reach 100 km/h.

Regards,

zeroByte

  • fixed minor bugs

  • added inAttendance facility

  • added weak/strict mode

As this does obviously not float your boat, I’ll not keep this post up to date. However, development is still ongoing as I actively use it in my projects.

Regards

Hi zeroByte,

I has missed this topic and it’s seems very intresting intresting … You should publish it as an extension because some other may have missed it ;)

8)

Hi,

I’ve tried to use your module but found some errors :

[list=1][]RestrictedActiveRecord.byPassCheck : begins with lower case in the declaration but refered as ‘ByPassCheck’ in method RestrictedActiveRecord.[b]generateAccessCheck/b[]several syntax errors when calling model() in a static way in ACLObjectCollection (e.g. line 45, 115, etc…)[/list]

I’m using the version downloaded from your first post.

Maybe it works with another version of PHP, but with 5.2.2 it doesn’t… or maybe I missed something ;)

B)

Hi,

sorry for the inconvenience.

  1. seems to already been fixed in my working version

  2. removed from project as it was deprecated anyway

This extension relies on late static binding and such features. It has been developed with PHP 5.3.5 ^^

Would you try the new version from the extension-repository and tell me if it doesn’t work anyway? I’ll then add version requirements on the page.

ACL in the repository

  • removed deprecated malfunction
  • fixed major bug related to moving hierarchies within the tree

  • added convenience class: RestrictedTreeActiveRecord

Regards,

Hi,

thanks for the update.

I confirm that parametrized static method call is available since PHP 5.3.0


$classname = 'Foo';

print $classname::$my_static ; 

ref

ciao

8)

My problem is that Active Records are accessed using Class::model()->[…].

Now, if I invoke generic classes which can be used by arbitrary ActiveRecord-Classes they have to do that dynamically, which results in $Class::model().

I didn’t look it all up but I think that’s the main point.

If you know any better way, just let me know ^^

I’ll add the requirement on the page. Thanks for your report.

Regards,

Has any more work been done on this?

Also, can this be worked into user-management models?

Yes - I’ve just uploaded a new version :D .

What is your definition of user-management models?

Regards,

Hey, I’m trying to use your module. Here are problems that I’ve discovered so far:

RestrictedActiveRecord.php:





    public static function getAutoPermissions($obj){

        if(!isset($obj->autoPermissions))

            return Strategy::get('autoPermissions');

        return $obj->autoPermissions;

    }



isset($obj->autoPermissions) due to overridden __isset tries to call getAutoPermission recursively and dies. Renaming method to _getAutoPermissions helps a lot.

  1. None of is(), join(), leave() are available to AROs and ACOs

  2. CGroup/RGroup classes are only available after ARO is saved to database

Also, I guess there should be a better documentation about initial setup / bootstrap admin entity, because until any ARO(user in my case) has appropriate record in database, it can’t do anything.

I cannot bootstrap a project using your doc.

Example:




        Yii::app()->user->id = 1;


        $sc = new StaffingCompany();

        $sc->name = 'SomeName';

        $sc->save();



Result:

  1. UserTest::testApprove

RuntimeException: Invalid Aro

Hi,

thanks for giving it a try. And I’m sorry for your problems.

@getAutoPermission: I’ve never run into this problem so far, I’ve changed it as you suggested.

  1. Oh. Here you see what happens if you’re maintaining several different interfaces (with an adapter-pattern) and using a language without multiple inheritance ;D. I’m sorry - I’ve added the adaption-methods now - I’m sorry for my human failure.

  2. You’re right, I should mention that in the documentation - explicitely.

I’ve added the opportunity to save without an aro at all now - this can make sense if you employ business-rules or if you have enabled the generalPermissions. However, it doesn’t always have to.

You can find the new version on the ext-page.

Thank you very much for your report

Regards,

Thanks for your fixes, will give them a try. Are you using this extension somewhere in production ?

I’m on my way. I’m currently developing yet another image-hoster. The Image-Hoster itself is not very special, it’s rather a by-product of a bigger project (=> all media from the bigger project is stored there, using SOAP-interfaces). I needed a permission-system anyway, so why not write a full-fledged one instead of wrangling with RBAC’s shortcomings.

I know you’re currently unsure if this is really stable. For an earlier version I wrote an integration-test running for a few minutes and penetrating the ext with requests, validating nearly every possibility. However, as the features grew this is not reasonable any more. If you have any ideas on how to test this complex setup with “full” coverage, please let me know.

May I know in which type of project you are going to use this?

Regards,

If I can make it running, I can contribute back to documentation ) For now I just need some bootstrap. I’ve installed acl (with default untouched config), created tables, and trying to create a RestrictedActiveRecord object. Recursion is still not fixed in 0.4.2:




Missing argument 1 for RestrictedActiveRecord::getAutoPermissions(), called in /htdocs/framework/base/CComponent.php on line 189 and defined


/htdocs/protected/modules/acl/models/RestrictedActiveRecord.php:283

/htdocs/framework/base/CComponent.php:189

/htdocs/framework/db/ar/CActiveRecord.php:182

/htdocs/protected/modules/acl/models/RestrictedActiveRecord.php:284

/htdocs/protected/modules/acl/models/RestrictedActiveRecord.php:279

/htdocs/framework/db/ar/CActiveRecord.php:1034

/htdocs/framework/db/ar/CActiveRecord.php:787

/htdocs/protected/tests/unit/UserTest.php:47



Here is the patch that fixes this issue:




--- /home/ag/Downloads/acl/models/RestrictedActiveRecord.php	2012-07-11 01:48:07.000000000 +0300

+++ RestrictedActiveRecord.php	2012-07-19 17:21:50.019276299 +0300

@@ -276,11 +276,11 @@

                 throw new RuntimeException('Unable to create corresponding Aco for new '.get_class($this));

             }

             

-            $aro->grant($aco, self::getAutoPermissions($this), true);

+            $aro->grant($aco, self::_getAutoPermissions($this), true);

         }

     }

     

-    public static function getAutoPermissions($obj){

+    public static function _getAutoPermissions($obj){

         if(!isset($obj->autoPermissions))

             return Strategy::get('autoPermissions');

         return $obj->autoPermissions;



Then I run the following code (empty ARO/ACO tables):




        Yii::app()->user->id = 1; // User exists in database tbl_user


        $sc = new StaffingCompany(); // Restricted

        $sc->save();



And I get:




RuntimeException: Invalid Aro


/htdocs/protected/modules/acl/models/RestrictedActiveRecord.php:317

/htdocs/protected/modules/acl/models/RestrictedActiveRecord.php:217

/htdocs/framework/db/ar/CActiveRecord.php:1008

/htdocs/framework/db/ar/CActiveRecord.php:787

/htdocs/protected/tests/unit/UserTest.php:47



Then I’m trying to create ARO first:




        Yii::app()->user->id = 1; // User exists in database tbl_user


        $admin = new User(); // Requesting

        $admin->save();

        Yii::app()->user->id = $admin->id;


        $sc = new StaffingCompany(); // Restricted

        $sc->save();



It fails at $sc->save() :




Exception: Unknown ACL-Object specification


/htdocs/protected/modules/acl/models/AclObject.php:296

/htdocs/protected/modules/acl/models/AclObject.php:185

/htdocs/protected/modules/acl/models/AclObject.php:362

/htdocs/protected/modules/acl/models/AclObject.php:285

/htdocs/protected/modules/acl/models/AclObject.php:185

/htdocs/protected/modules/acl/components/strategies/nestedSet/pathMaterialization/models/PmAro.php:28

/htdocs/protected/modules/acl/models/RestrictedActiveRecord.php:279

/htdocs/framework/db/ar/CActiveRecord.php:1034

/htdocs/framework/db/ar/CActiveRecord.php:787

/htdocs/protected/tests/unit/UserTest.php:47




However, database is filled ok (permissions are empty though, since grant fails):




mysql> select * from tbl_aro_collection;

+----+-------+-------+-------------+---------+

| id | alias | model | foreign_key | created |

+----+-------+-------+-------------+---------+

|  1 |       | User  |          30 |       0 |

+----+-------+-------+-------------+---------+

1 row in set (0.00 sec)


mysql> select * from tbl_aro;

+----+---------------+------+

| id | collection_id | path |

+----+---------------+------+

|  1 |             1 | /    |

+----+---------------+------+

1 row in set (0.00 sec)


mysql> select * from tbl_aco_collection;

+----+-----------------+-----------------+-------------+---------+

| id | alias           | model           | foreign_key | created |

+----+-----------------+-----------------+-------------+---------+

|  1 | StaffingCompany |                 |           0 |       0 |

|  2 |                 | StaffingCompany |           8 |       0 |

+----+-----------------+-----------------+-------------+---------+

2 rows in set (0.00 sec)


mysql> select * from tbl_aco;

+----+---------------+------+

| id | collection_id | path |

+----+---------------+------+

|  1 |             1 | /    |

|  2 |             2 | /    |

+----+---------------+------+

2 rows in set (0.00 sec)


mysql> select * from tbl_permission;

Empty set (0.00 sec)



The following fix seems to make things work, but I’m not sure whether it is proper:




--- /home/ag/Downloads/acl/models/AclObject.php	2012-07-11 01:32:53.000000000 +0300

+++ AclObject.php	2012-07-19 18:29:36.657119505 +0300

@@ -359,7 +359,8 @@

       * @param AclObject $obj 

       */

      protected function assureSafety(&$obj = NULL){

-         $obj = $this->loadObject($obj);

+         if ($obj)

+             $obj = $this->loadObject($obj);

          //Assure that objects have been saved

          $this->assureSaved($this, $obj);

      }



This is my current version:




protected function assureSafety(&$obj = NULL){

         if($obj !== NULL){

            $obj = $this->loadObject($obj);

            //Assure that objects have been saved

            $this->assureSaved($this, $obj);

         }

         else{

             $this->assureSaved($this);

         }

     }



I’m currently moving the entire ACL-extension to github to avoid just those things ^^

Before I began to do that I introduced a bunch of new features - as it’s very cumbersome to “downgrade” acl, I’ll first finish them and then put the acl in all it’s glory on github.

(Example: If you use business rules, it’s currently only possible to replace one “side” with business rules. So you currently cannot establish a permission with both the aro- and aco-group granting the permission using Busines-Rules. This is fixed in the upcoming version. And it’s much faster. )

I’ve fixed a critical issue:

acl/components/strategies/nestedSet/pathMaterialization/PmPathManager in line 61, replace the $condition with that one:




$condition .= " (".$field." REGEXP CONCAT('^', '".$path."') ". 

                        ($additionalCondition ? ' AND '.str_replace(':path', $path, $additionalCondition) : '')

                        ." ) ";



It’s simply a mistake aka “a regexp b” vs. “b regexp a”.

Do you still have any direct problems (errors, unexpected behavior…?)

Regards,

EDIT:

Another fix I forgot yesterday:

acl/models/behaviors/RestrictedActiveRecordBerhavior::beforeFind




public function beforeFind(&$event){

        parent::beforeFind($event);


        

        $this->owner->dbCriteria =  $this->generateAccessCheck();

    }



It’s not that the current version is wrong itself - it just dosen’t work in the current yii version.

Regards,

The new version is on github now.

You can take a glance at the full documentation here

Changes:

Regards,