RSS
 

Posts Tagged ‘Authorization’

ACL – Access Control Lists – revised

06 Jan

With a focus on CakePHP application development.

Authentication vs Authorization

Those two are often confused. So here a little preface:

Authentication always comes first. It is about whether you are logged in or not. At this point it is not yet relevant who has access to what.
It is merely checked if the specific action/URL can be publicly accessed or needs authentication first.

After the authentication process, usually completed through some kind of login, the authorization decides on who can do what.
Here either roles or specific users are checked upon and the access is then either granted or denied.

Role Based Access Control (RBAC)

The easiest form of authorization is usually "role based access control", where you assign each other a role, and all that needs to be done is comparing
the user’s role (or roles in multi-role setups) to the required role for a specific action.
This can be realized with a single "roles" table and "role_id" in "users" table (or for multi-roles a "roles" and "user_roles" table) along with some table or file to store the
role/action relations.

Row Level Permissions

ACL is part of Authorization, the second step.
It is called "row level" access control, as it can go beyond just roles/actions combinations. Depending on the implementation it can also allow or deny specific users access to certain actions or records and make inheritable tree structures possible. So if you deny a certain user access to a node in the ACL tree(s), the sub-nodes are also prohibited by inheritance.
In its complex form (CRUD) it has even a flag for each type of action.
Such ACL trees can be quite complex to handle manually, and as such the tools provide you usually with some methods and CLI commands to make things easier.
So for the CakePHP core ACL there is even a plugin around some extra utilities for it.

ACO vs ARO

Read in the docs how ACL works regarding ACO (Access Control Object) / ARO (Access Request Object).
Essentially, ACLs are used to decide when an ARO can have access to an ACO.
While this post mainly focuses on controlling access to actions and records, ACOs could be anything you want to control, from a controller action, to a web service, to a line in your grandma’s online diary. So it allows you to make anything "accessible".

Why ACL?

First of all you have to assess what kind of authorization is needed for your application. In 95% of all cases, a full stack row level ACL is not even needed, and simple roles per user would suffice.

The main questions boil down to:
a) Does it need row level permissions as in "I need to allow/deny certain actions – or even specific records – to certain users"? Sure this can’t this be done with more roles?
If so, then go for some ACL implementation. If not (and this often times cuts down the complexity quite a bit) use a role based one.

b) Does it have to be dynamic? For row level this usually has to be some DB driven solution. For roles it is sure nice to have some backend, but in my experience
it is often not really necessary, at least if it is not some kind of CMS, but a web application that has developers close by.

So it results in 3 different outcomes:

  • Row level + dynamic (=ACL)
  • Role based + dynamic (=RBAC dyn)
  • Role based + static (=RBAC stat)

Note that the 2.x core ACL also provides an INI based approach, but that is not recommended and somewhat useless.

Problems with ACL

When working with ACL back in 1.x and 2.0/2.1/2.2 one large problem with it was speed.
It is both slow in generating the aco/aro tree from all actions available throughout the project as well as checking them.
Depending on the implementation this can sometimes be the bottleneck of some controllers and their actions.
Especially with a larger user base and a not so small app this can probably get out of hand very soon.

When building the aro/aco trees the resulting tree structure table always seemed quite fragile to me. At least a few times it got, for some reason,
broken, and repair tools had to recover the tree structure. It is also very difficult to debug due to its complexity.
Additionally, you have to be very carefully with handling the data: You cannot just delete a user manually, or reset/truncate a table. It always has to go through the ACL channels for the tree to stay valid and filled with the right data.

Another problem I encountered when working with ACL:
It is locked to the DB, instead of the code base. So reproducing some issues with accessing actions locally can become tedious as you have a totally different user-base, controlled records and thus ACL entries. And if you deploy, but forget to update the ACL (or some action is not added for some reason) people will be locked out without indication/feedback. If you upload new actions, the permissions to it will not always be deployed along with it which feels counter-intuitive to me.
That applies for any dynamic (DB driven) permission setup, of course. So role based ones with an admin backend wouldn’t be off the hook here.
Using static and "file commited" permissions here assert that those always fit to the current code base. I found that a lot easier to maintain and debug.

Static RBACs ftw

For the last 6 years I have never needed ACL for any of my applications or have it found useful for a lot of others I have examined.
Defining some good roles was enough. And it was simple. Additionally, by keeping it static I neutralized the issue with the "not-in-sync" issues above.

I see a lot of people over-engineering that part without the (immediate) need for it.
So I would really keep it as simple as possible until the use case for more complex mechanisms arises.

ACL definitely has its use cases, and I wouldn’t dare to call it evil, but it needs to be thoroughly thought through and not applied blindly.
In case you need to use it, check here, if the ActionsAuthorize (Uses the AclComponent to check for permissions on an action level) suffices over CrudAuthorize (action -> CRUD mappings).

Alternatives to the CakePHP core ACL

Alternatives on row level

Often times you can’t get around some basic row level checking, e.g. when a user may only edit/delete his own posts.

The book has examples on how to add the currently logged in user to the data upon add action, and how to check for edit/delete if that user is the owner of that record.
This can be done using the controller’s isAuthorized() method. A little bit of hardcoding saves a lot of trouble here.

Alternatives to ACL in general

Role based

  • TinyAuth – static INI files by default but could be easily made DB driven or at least backend adjustable.

Other

  • Controller based: ControllerAuthorize Calls isAuthorized() on the active controller, and uses the return of that to authorize a user. This is often the most simple way to authorize users. You can even mix role based with row level based auth if required.

Let me know if I forgot to list one. The full list of available plugins can be found at awesome-cakephp.

Last notes

In CakePHP3 ACL has been moved into a separate plugin, as it is just too much overhead for the core (since not so many actually use it) and doesn’t quite fit the 80%-rule therefore.

 
2 Comments

Posted in CakePHP

 

Auth – inline authorization the easy way

07 Apr

I wrote a wrapper class to make inline authorization easier.
Often times you want to check on certain roles inside an action or view and depending on the result display specific content or execute specific code.
As an example we only want to display the "admin infos" box on the home screen for an admin. All other users should not see this box.

Status quo

We would need to check the session manually against the roles we want to grant access to. This can get pretty hairy with more than one role allowed (if admins and moderators are allowed to see this box for example).

Preparations

We first need to make the class usable by putting this in our /Config/bootstrap.php:

// these IDs match the role_ids in the DB
define('ROLE_SUPERADMIN', '1');
define('ROLE_ADMIN', '2');
define('ROLE_MOD', '3');
define('ROLE_USER', '4');
// enable the Auth class
App::uses('Auth', 'Tools.Lib');

I like to use constants as they are shorter than Configure::read('admin') etc. But Configure would work just as fine.

Be aware: Since we already try to access the Tools plugin you need to assert first, that we enabled the Tools Plugin (using CakePlugin::loadAll() or a specific load call).

Then we need to decide whether we use single role (cake default) or multi role Authorization. I usually always use multi-roles. Therefore the default case for the Auth class is exactly this.
The session then contains:

Auth.User.Role (with Role being an array of role ids)

If you use single roles, you’re Session array should look like this:

Auth.User.role_id (with role_id being the single role we want to check against)

In this case you should set the following constant manually in your bootstrap:

define('USER_ROLE_KEY', 'role_id');

"Former" usage

For comparison I will outline the manual and "outdated" way of authorization first:

# we want to make sure that piece is only visible to admins and moderators
if ($this->Session->read('Auth.User.role_id') == ROLE_ADMIN || $this->Session->read('Auth.User.role_id') == ROLE_MOD) {}
# or with multi-role
if (in_array(ROLE_ADMIN, (array)$this->Session->read('Auth.User.Role')) || in_array(ROLE_MOD, (array)$this->Session->read('Auth.User.Role'))) {}

Quite a lot to write…

Note: This also only works in controller/component and view/helper scope. You would have to use the static CakeSession::read() in order to make this work in the model/behavior one etc.

Usage

Now the fun part. The wrapper class can be found in the Tools Plugin.

# Same thing as above
if (Auth::hasRoles(array(ROLE_ADMIN, ROLE_MOD)) {}

Now isn’t that nicer to write and read?

The default case is that if one of the roles is matched it will return true right away. If you want to connect them with AND instead of OR, you need to make the second param false:

# This only passed if the user has both roles!
if (Auth::hasRoles(array(ROLE_ADMIN, ROLE_MOD), false) {}

If we only want to check against a single role we could also use the shorthand:

if (Auth::hasRole(ROLE_MOD) {}

Advanced usage

You can also pass in the roles you want to check against. This can be useful if you want to check somebody else’s roles (and not your session roles). This can come in handy in CLI (command line / shell) environment and also in the admin backend.

if (Auth::hasRole(ROLE_MOD, $rolesOfThisUser) {}

And

if (Auth::hasRoles(array(ROLE_MOD, ROLE_USER), true, $rolesOfThisUser) {}

And there is more

There are also some convenience methods available.

Instead of $uid = $this->Session->read('Auth.User.id') you can just write

$uid = Auth::id(); // anywhere in your application

The roles can be fetched like this:

$myRoles = Auth::roles(); // string in single-role and array in multi-role context

Last but not least the user data:

$user = Auth::user(); // complete user array
$username = Auth::user('username'); // string: current username
...

Final notes

Although this wrapper can be used about anywhere in your application with ease does not mean one should do that.
Try to avoid using the auth (and therefore session) data in the model layer, for instance. Those should be kept state-less.
But I also know that there are cases where it is pretty convenient to ignore this warning 🙂

Disclaimer

Just for clarification: This class does not provide you with the Authentication or Authorization. This has to be up and running already. It is only a wrapper to check user roles a more efficient and cleaner way.

So if you need to setup a fresh authentication, you can just use the cake Form authenticate for example. If you want multi-role authorization you would want to throw in some additional spices:
You would need to write the Role array to your session upon successful login for the authorization module to work. See the above notes on how the session data array should look like.

For a new authorization take a look at my TinyAuth. It can handle single and multi role auth and those classes work well together (yeah – they have been designed to do that, of course^^).

 
No Comments

Posted in CakePHP