1

So far I've managed to maneuver around user/group management but for a upcoming project I finally have to deal with this.

My gut feeling said the most natural way to implement this would be a hierarchical group management where a group can be part of other groups (children and parents). Child groups inherit all permissions of the parent group (and the permissions of the parent groups of the parent groups etc.).

Database wise this looks nice and intuitive. At least I thought so at the beginning but now since I have to retrieve all the permissions of a given user I'm not so sure about it. The problem is I have to recursively get all the permissions of every group a user is a member of (a user can be member of multiple groups). It's certainly possible to do this, many databases support this somewhat.

However, after implementing this I'm no longer certain that this is the way to go. Especially since I'm supporting multiple databases where it becomes even more bothersome since some don't support recursive CTEs (still doable but even slower).

Other facts:

Users: 1 - 10K (active) and over time this could grow to 100K (including inactive)

Groups: 7 - ? (this can grow fast as well given the environment it gets used in... 5K after 2 years is certainly possible)

Question:

What is the most common and/or recommended way to do this?

Are there some hidden risks despite the increasing performance issue with the increasing amount of groups?

Is there a smart flat alternative or something I haven't thought about yet?

EDIT: I'm only tagging this as PHP since I'm using PHP in conjunction with this and I consider it important to know for certain answers.

halfer
  • 19,824
  • 17
  • 99
  • 186
  • Consider: http://www.artfulsoftware.com/mysqlbook/sampler/mysqled1ch20.html in oracle you use connect by prior to traverse trees, in SQL server it's For XML Path. It doesn't appear MySQL has a nice built in function for this. a Common Table expression may have helped but it doesn't appear to work in mySQL Either. Others indicate: http://stackoverflow.com/questions/14402568/mysql-all-parent-child-relationships or http://stackoverflow.com/questions/7569399/get-a-recursive-parent-list if you have set levels. – xQbert Feb 03 '14 at 20:35
  • 1
    You don't need recursive SQL to hold hierarchical data. Have a look at the nested set algorithm - data is held as a tree and can be manipulated by ordinary `INSERT`s and `UPDATE`s. See [here for an example](http://www.sitepoint.com/hierarchical-data-database-2/) or peruse the [tag:nested-set] tag here. – halfer Feb 03 '14 at 20:47
  • 1
    [Slide 40](http://www.slideshare.net/billkarwin/models-for-hierarchical-data) of Bill Karwin's "Models for Hierarchical Data" might be of interest to you. – Benny Hill Feb 03 '14 at 20:50
  • Lots of stuff to read. Will take me 2-3 days to read it and implement it but it looks promising so far. Feel free to write an answer I can accept when the time comes. –  Feb 03 '14 at 21:02

1 Answers1

0

Below is what I use for permissions

All the actions that need to be restricted...

actions
    id              varchar(50)
    description     varchar(255)

+-------------+--------------------------------------------------------------+
| id          | description                                                  |
+-------------+--------------------------------------------------------------+
| VIEW_SSN    | Allow a user to view a complete Social Security Number (SSN) |
| DELETE_USER | Admin: Allow a user to delete another user                   |
| ........... | ............................................................ |
+-------------+--------------------------------------------------------------+

The roles a user can be associated with. Generally speaking these are usually (but not always) strongly associated with the department a user is in.

roles
    id              unsigned int(P)
    description     varchar(50)

+----+-------------------+
| id | description       |
+----+-------------------+
|  1 | HR: Clerk         |
|  2 | HR: Manager       |
|  3 | HR: Director      |
|  4 | IT: Administrator |
| .. | ................. |
+----+-------------------+

Associate actions with roles. Both columns are foreign keys to their respective table and together they form the primary key. In my example data you see that the HR Clerk, HR Manager and HR Director roles can all view social security numbers while the IT Administrator can delete user accounts.

roles_actions
    role_id         unsigned int(F roles.id)----\__(P)
    action_id       varchar(255)(F actions.id)--/

+---------+-------------+
| role_id | action_id   |
+---------+-------------+
|       1 | VIEW_SSN    |
|       2 | VIEW_SSN    |
|       3 | VIEW_SSN    |
|       4 | DELETE_USER |
| ....... | ........... |
+---------+-------------+

Of course you've got your users...

users
    id                  unsigned int(P)
    username            varchar(32)(U)
    ...

+----+----------+-----+
| id | username | ... |
+----+----------+-----+
|  1 | bob      | ... |
|  2 | mary     | ... |
|  3 | john     | ... |
| .. | ........ | ... |
+----+----------+-----+

And finally we have to associate users with roles. Like the roles_actions table each column is a foreign key to it's respective table and together they form the primary key. In my example data we see that bob is an HR: Manager, mary is an HR: Director and john has both HR: Director and IT: Administrator roles (so he could view social security numbers as well as delete user accounts).

users_roles
    user_id         unsigned int(F users.id)--\__(P)
    role_id         unsigned int(F roles.id)--/

+---------+---------+
| user_id | role_id |
+---------+---------+
|       1 |       2 |
|       2 |       3 |
|       3 |       3 |
|       3 |       4 |
| ....... | ....... |
+---------+---------+

To find out if a user has a particular permission do something like:

SELECT COUNT(r.action_id)
FROM users_roles u
LEFT JOIN roles_actions r ON u.role_id = r.role_id
WHERE u.user_id = :user_id and r.action_id = :action_id

Having 10K active users shouldn't be a problem. Managing 5K roles could be a huge pain but my guess is you'd never have anywhere near that many roles. I'm currently logged into a client's system and they have 2,500+ users and are only using ~35 roles. That's lower than I expected but even for an organization of 10K users I wouldn't think you'd need more than a few hundred roles.

I chose to implement permissions this way rather than through inheriting groups like you're considering because I felt this gave me more flexibility. Every role can have an unlimited number of actions and every user can have an unlimited number of roles. For the clients I've worked with the idea of a role has always coincided very closely with a job function like "Payroll Clerk" or "Accounts Receivable Manager". So if you have "Payroll Clerk", "Accountant" and "Tax Specialist" roles in the accounting department and you have a new Director of Accounting, you just give him/her those three roles and they have permission to do everything his/her employees can do - takes about 10 seconds using a good jQuery interface.

If you really do end up with too many roles to manage effectively you could add another column, group_id, and associate individual roles with particular groups. This would make it easier to find all the roles associated with the "Accounting" group for instance.

Obviously this was far too much to put in a comment :-) This kind of design may or may not work for you in your current environment - it's worked well for me in about a dozen very different businesses.

Benny Hill
  • 6,191
  • 4
  • 39
  • 59