I\'m designing a very simple RBAC (Role Based Access Control) system in my PHP project and after giving it some thought I\'ve came up with a solution, but without knowing much a
Here's my approach to such RBAC systems, first I'd split the application into modules, at least logically.
Think of the modules as entities/resources in your application on which certain actions can be performed on. A resource could be a User, a Membership, a Product, ..
Let's assume we're building a site like Stackoverflow and we have the following modules:
Each of these modules register themselves in the applications database table called modules
which could look like:
# Modules
- id [an application-wide unique module identifier, provided by the module]
- name [string, human readable]
Each of these modules come with a set of actions which are pre-defined by the module, e.g actions to create a question, kick users from chat rooms etc. These actions get installed along with the modules into the applications database table called 'actions', which could look like:
# Actions
- module_id [reference to the modules table, is the namespace for the token]
- action [string / int, action identifier provided by the module, unique in the scope of the module]
- name [string, human readable name]
- Primary Key: [module_id, action]
Let's define the modules and actions for our Stackoverflow clone now:
Modules
+------------------------------------------+
| ID | Name |
+------------------------------------------+
| questions | Question and Answer Module |
| chat | Chat Module |
+------------------------------------------+
Along with the modules, the actions will be installed:
Actions
+-----------------------------------------------+
| Module | Action | Name |
+-----------------------------------------------+
| questions | read | Read Questions |
| questions | create | Create Questions |
| questions | edit | Edit Questions |
| questions | delete | Delete Questions |
| questions | vote | Vote on Questions |
| | | |
| chat | join | Join the Chat |
| chat | kick | Kick users |
| chat | create | Create Chatrooms |
+-----------------------------------------------+
The important thing here is that you can not modify the entries in these tables directly as admin of the systems so there's no GUI to add/remove actions etc.
The next step is to create some roles for our system, this is done over the admin's user interface, roles can be defined arbitrarly.
Let's start off with some basic roles:
Q&A User:
- can read questions
- can create questions
Q&A Moderator:
- can read questions
- can create questions
- can vote on questiosn
- can edit questions
Q&A Admin:
- can read questions
- can create questions
- can vote on questiosn
- can edit questions
- can delete questions
Chat User:
- can join the chat
Chat Moderator:
- can join the chat
- can kick users from the chat
Chat Admin:
- can join the chat
- can kick users from the chat
- can create chat rooms
First, the roles are created in the roles table:
# Roles
- id [auto-increment, unsigned]
- name [string, length:50]
Populated with our custom definitions:
Roles
+-----------------------+
| ID | Name |
+-----------------------+
| 1 | Q&A User |
| 2 | Q&A Moderator |
| 3 | Q&A Admin |
| 4 | Chat User |
| 5 | Chat Moderator |
| 6 | Chat Admin |
+-----------------------+
In our super fancy admin UI we now have a sidebar with a list of all installed modules and their associated actions. Since our typical admin is super lazy and doesn't know anything about programming he can now conveniently assign actions to each role with drag&drop, i.e assigning permissions to roles.
These assigned permissions are stored in our mapping table Roles_Actions
:
# Roles_Actions
- role_id
- module_id
- action
PK: [role_id, module_id, action]
The populated table:
Roles_Actions
+--------------------------------+
| Role ID | Module ID | Action |
+--------------------------------+
| 1 | questions | read |
| 1 | questions | create |
| 2 | questions | read |
| 2 | questions | create |
| 2 | questions | vote |
| 2 | questions | edit |
...
| 6 | chat | join |
| 6 | chat | kick |
| 6 | chat | create |
+--------------------------------+
Now we have to assign the roles to the users in the systems, let's say we have four users initially:
- Chuck Norris which is a Q&A Admin and also a Chat Admin (UID = 1)
- Bruce Willis which is a Q&A Moderator and a Chat User (UID = 2)
- Dilbert which is a Q&A Moderator and a Chat Moderator (UID = 3)
# User_Roles
- user_id [FK:user_id, unsigned]
- role_id [FK:roles_id, unsigned]
The populated Table:
Users_Roles
+---------------------------------+
| User ID | Role ID |
+---------------------------------+
| 1 | 3 (= Q&A Admin) |
| 1 | 6 (= Chat Admin) |
| 2 | 2 (= Q&A Moderator) |
| 2 | 4 (= Chat User) |
| 3 | 2 (= Q&A Moderator) |
| 3 | 5 (= Chat Moderator) |
+---------------------------------+
So one user can have multiple roles, and the permissions (module/action pairs) are then merged together in the application layer. If you want to go one step further you could also provide some sort of inheritance in the role model, e.g the Q&A Moderator could be defined as child of Q&A User, inheriting all permissions from the Q&A User and extending it with moderator rights.
So how could the authorization look like on the application layer?
Let's assume Dilbert which is a Q&A Moderator and Chat Moderator logs into the system, then you collect all his roles and all permissions assigned to those roles. Next you start to build a permission tree and remove all duplicate permissions, the permission tree could be represented as an associative array like:
Dilbert's permission tree:
$dilberts_permissions = array(
"questions" => array("read", "create", "vote", "edit")
"chat" => array("join", "kick")
)
For convenience we create a simple helper function like:
function has_permission($path, $tree) {
list($module, $action) = explode('.', $path);
return (array_key_exists($module, $tree) && in_array($action, $tree[$module]));
}
In our code we can now check if Dilbert is allowed to do certain things with a syntax like:
has_permission("questions.create", $dilberts_permissions); // => TRUE
has_permission("questions.delete", $dilberts_permissions); // => FALSE
has_permission("chat.join", $dilberts_permissions); // => TRUE
has_permission("chat.create", $dilberts_permissions); // => FALSE