Using RBAC in a multi-instance scenario

I'm building a combined management interface for various website components (newsletters, contact/feedback forms, …) which should be available to all of them through a single management-instance. As using a dedicated application per website is tedious and involves additional drawbacks, I've made the routing and models site-aware (i.e. /newsletter/ While this part works like a charm, I'm somewhat stuck for implementing access control.

Unless I'm mistaken, it looks like RBAC is geared towards single-instance access control and can't easily be made multi-site aware, as I'd need ("username", "website") pairs for identification rather than the "username" avaiblable in CAuthManager.

One possible option would be modifying the AuthManager to accept one or more additional parameters in order to filter the items (like changing the $userId to an array, with a possible convention that a field not filled-in is ignored). Before undertaking this task, I'd like to hear the opinions of other people or whether someone already did such changes.

Another (doable?) way would be the use of database views to avoid the above coding and have the DB do the hard work. Unfortunately I'm not quite sure how to manage the inserts/updates in that scenario. Furthermore, it'd be heavily database-dependent as not every RDBMS supports writeable views.

Or did I miss another solution that works out-of-the-box?

You could use 'userID@domain' to represent users and use that to check access. To do so, you will need to override CWebUser::checkAccess().

Yes, but not quite (or, said differently: not in a single call to checkAccess() ). It is indeed an elegant solution to manage strictly separate accesses to the different site but doesn't allow global access to all of the sites, achievable by ignoring some of the filters if they aren't set in the auth backend.

I'd have to call checkAccess() twice:

$var->checkAccess('userid') || $var->checkAccess('userid@website')

My current idea, as an example with 3 users, 2 sites 2 modules (roles):

login | site     | access


UserA | websiteA | module1

UserB | websiteB | module2

UserC |          | module1

In this case:

  • UserA can only access module1 of websiteA
  • while only UserB can access module2 of websiteB
  • and finally, UserC has access to module1 of any website, in this case both websiteA and websiteB

Query-wise, I'd be tending to add an additional restriction to the WHERE clause, so either:

WHERE name=itemname AND userid=:userid AND (website=:website OR website IS NULL)

or (less intrusive, but also less flexible):

WHERE name=itemname AND (userid=:userid OR userid=:useridwithdomain)

I think you don't need to modify the current implementation.

You just need to add a bizRule to each role assignment.

For example, for UserA, when you assign "access to module1" to him, you can add a bizRule which checks if the site is websiteA.

For UserC, you assign "access to module1" to him without any bizRule.

I cant't really agree with your proposed approach.

The business rules shouldn't serve this purpose but rather be kept for more useful tests, such as the "updateOwnPost" task from the documentation. Yes, bizRules can be defined at any level, but the one place I'd be adding it to may require another snippet at some time. Not good. I don't want to micro-manage code generation when assigning roles to users: merging code snippets is going to be a maintenance nightmare I'm readily avoiding as much as possible.

Furthermore, this kind of filtering should be done by the database. After all, the information required to figure out whether a user may access a given resource or not will be present inside the AuthAssignment table in either case (be it serialized inside the data or in a dedicated column). No need to fetch the row and evaluate some PHP code in order to come to the same conclusion than the database would knows right away.

The RBAC overhead is already quite big in my case (7-15 queries for the menus alone). I don't want to trigger a PHP code evaluation for most of these rows. For that same reason, I'd avoid executing checkAccess() twice (once with the domain suffix and once without). It just wouldn't scale and can't be done with numeric userId's.

So, I guess I'm going to try my initial idea and modify the first few methods of CDbAuthManager and add dynamic filtering, although I fear it's going to be quite a mess.