Is my Role Based Access Control a feasible solutio

2020-05-21 06:23发布

问题:

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 about building business systems I'm not sure if there are, or would be, any major design flaws with my solution.

So basically, I want to give a user a set of 'roles'. I will use these roles to allow or deny access to certain functionality on the application.

Here is the roles table:

# Roles
    - id [auto-increment, unsigned]
    - role [string, length:50]

# User_Roles
    - user_id [FK:user_id, unsigned]
    - role_id [FK:roles_id, unsigned]

Note: user_id and role_id to be unique index

The problem that concerned me was there was no information in the database about what the role actually does. But then I started to think if that was important. Because if the roles had meaningful names, and a structure, then you could query the user_roles table to get all the users roles and then within code something like:

# Fetch user with ID 1 from database
$user = User::find(1);

# Fetch the roles the user has from the database
# @returns : Array of roles
$userRoles = $user->roles()

# $userRoles = ['newmember', 'member.post', 'member.chat']

# Can the user send a message?
if(in_array('member.message', $userRoles)
{
    # User can send a message
}
else
{
    # User can not send a message
}

The roles can be managed, and can have whatever meaning they like within the organisation. I'm just concerned that the roles in the database have no meaning, I can't help but think there's probably a better way to achieve this.

Would my solution be feasible long term?

Thanks

回答1:

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:

  • Questions (Q&A section)
  • Chat

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


标签: php rbac