You: What do I know? I: also the following two points. Understand table design ii based on RBAC idea. How are the data used in actual development scenarios You: I think it should be a little bit awfully awfully me: I don’t want you to feel, AWFULLY AWFULLY ͜ feeling

Daniel: Hi, egghead, I recently received a request to add permissions related functions to existing projects. I have been concentrating on the front end for a long time, and haven’t touched watch design for a long time. Do you know anything about this?

Mr. Egg: [] ( ̄▽ ̄) * slightly understand slightly understand ~! You can’t “do what you want” when you have a project. Go ahead, some information about the DB of the existing project

Daniel: The database is MySQL, and the database is connected using Sequelize, an ORM node.js library.

Mr. Egg: OK, the suggested process for this combination is to design the table structure with EER graph tool (such as MySQLWorkbench), then export the SQL, and then automatically generate the Model via Sequelize-Auto

Daniel: Yes, dude, automatic SQL generation, automatic Model generation. Long time no see. You are still so lazy. When you say that, you’ve solved my first problem. Let’s move on to permission design


RBAC table design

Daniel: Permission design, is that a complicated part?

Mr. Egg: It can be as complicated as you want. What level of difficulty do you want? < ~ ~ ~ /

Daniel: No, no, no, I want something simple and flexible, which can be easily expanded ʅ (´◔౪◔) ʃ

Mr. Egg: That’s a lot to ask. Now this industry is used more RBAC (role-based access control) ideas, that is, role-based access control. So without further ado, LET me get right to the picture

You only need to assign a role to the user, and the role determines which Resource can be operated on. Operation is usually CRUD

Daniel: Why is there no password in the Users table? Why is code always varchar(45)

Mr. Egg: Hey, don’t worry about the details, okay? ヘ (_ |

Daniel: Ok, ok. The design looks simple, isn’t it?

Mr. Egg: Come on, according to your actual situation, please make a move


Functional authority

Daniel: Suppose you have users A and B; The system has two functions: project management and user management. User A is the administrator and can access both functions. User B is an ordinary user and can only access project management.

Mr. Egg: No problem. ┏ (^ ^) omega = ☞

1. Create data

  • Create resource data: Project management and user management are resources at the function module level. The data is as follows:
// resources: 
{ code: 'projects', name: 'projects', type: 'module' },
{ code: 'users', name: 'users', type: 'module' },
Copy the code
  • Create a role and assign related operation rights to it
// roles: 
{ code: 'admin', name: 'admin' },
{ code: 'guess', name: 'guess' },

// role_permissions:
{ roleCode: 'admin', resourceCode: 'projects', operation: 'C' },
{ roleCode: 'admin', resourceCode: 'projects', operation: 'D' },
{ roleCode: 'admin', resourceCode: 'projects', operation: 'R' },
{ roleCode: 'admin', resourceCode: 'projects', operation: 'U' },
{ roleCode: 'admin', resourceCode: 'users', operation: 'C' },
{ roleCode: 'admin', resourceCode: 'users', operation: 'D' },
{ roleCode: 'admin', resourceCode: 'users', operation: 'R' },
{ roleCode: 'admin', resourceCode: 'users', operation: 'U' },
{ roleCode: 'guess', resourceCode: 'projects', operation: 'R' },
Copy the code
  • Create a user and assign roles to the user
// users:
{ code: 'user_a', name: 'user_a' },
{ code: 'user_b', name: 'user_b' },

// user_role:
{ userCode: 'user_a', roleCode: 'admin' },
{ userCode: 'user_b', roleCode: 'guess' },
Copy the code

2. Consumption data

Now let’s provide data to the front end to determine which functional modules can be seen by user A and whether to display the create, delete and other buttons

SELECT 
    u.code userCode,
    res.code resourceCode,
    GROUP_CONCAT(DISTINCT rp.operation) operations
FROM
    resources res,
    role_permissions rp,
    roles r,
    user_role ur,
    users u
WHERE
    res.code = rp.resource_code
        AND rp.role_code = r.code
        AND r.code = ur.role_code
        AND ur.user_code = u.code
        AND res.type = 'module'
        AND u.code = 'user_a'
GROUP BY u.code , res.code
Copy the code

The resulting user_A permissions are as follows:

userCode resourceCode operations
user_a projects R,D,U,C
user_a users R,D,U,C

In this way, the front end only needs to determine whether projects have operation of R to decide whether to display the project function menu. If C is present, the create button is displayed; If there is D, the delete button is displayed. With U, the edit button is displayed

3. View simplification

Daniel: The problem is solved, but the SQL, isn’t it a bit complicated? (~ ~ ▽ ~) ~

Mr. Egg: Well, exactly. So let’s simplify it.

Use the following SQL to create a view view of user function module permissions

CREATE VIEW `user_module_view` AS
    SELECT 
        ur.user_code,
        rp.resource_code,
        CONCAT('|',
                GROUP_CONCAT(DISTINCT rp.operation
                    SEPARATOR '|'),
                '|') operation
    FROM
        user_role ur,
        role_permissions rp,
        resources rs
    WHERE
        ur.role_code = rp.role_code
            AND rs.code = rp.resource_code
            AND rs.type = 'module'
    GROUP BY rp.resource_code , ur.user_code
Copy the code

Now we can simplify the long SQL from above into the following single-table operations:

SELECT 
    *
FROM
    user_module_view
WHERE
    user_code = 'user_a'
Copy the code

Note: above CONCAT(….) The result of the operation in the format as follows: | | R | C U | D |, so in order to accurately through the like (e.g., like ‘% % | R |’) to check if you have a certain privileges


Data access

Daniel: Then I will continue to work out the questions. Although both user A and user B have read permission for the project management function, user B is an ordinary user. Suppose user B belongs to OrgB, then he can only view the project under OrgB.

Mr. Egg: Then you can extend operation. Now let’s modify the data for role_Permission

{ roleCode: 'guess', resourceCode: 'projects', operation: 'R' } 
=>
{ roleCode: 'guess', resourceCode: 'projects', operation: 'R_ORG' },
Copy the code

This means that the GUESS role has org-wide read permission on projects resources. In this way, when the server interface is fetching item list data, it can determine the filtering conditions of the list data according to the operation flag bit of R_ORG


Data item level permission

Daniel: There seems to be nothing wrong with the regular needs. But NOW I have a permission related requirement, I don’t know if your set can be used

Egg: come on, I’ll play it to the end today (~) ~)

Daniel: Then I don’t mind. In my project management feature, each project is created with a view/Edit/admin role by default. The above example can only do the same for projects with a specified scope (such as org), but it seems impossible to specify different operations for different projects

Mr. Egg: [] (~ ▽ ~) * Then change the Angle, how about treating every project as a resource?

Daniel: Can you be more specific? Would it be nice to talk about what you should do in the permissions section when you create a project

Mr. Egg: Ahem, ahem, no problem, come on

When you create your project, you need to initialize the corresponding built-in roles so that users can be assigned roles. Here’s what data needs to be added to which tables, assuming you create project Project_A

// 1. add resource:  
{ code: 'project_a'.name: 'project_a'.type: 'project' }


// 2. add roles: 
{ code: 'pro_a_view'.name: 'pro_a_view' },
{ code: 'pro_a_edit'.name: 'pro_a_edit' },
{ code: 'pro_a_admin'.name: 'pro_a_admin' },

// 3. add role_permission:
{ roleCode: 'pro_a_view'.resourceCode: 'project_a'.operation: 'R' },
{ roleCode: 'pro_a_edit'.resourceCode: 'project_a'.operation: 'R' },
{ roleCode: 'pro_a_edit'.resourceCode: 'project_a'.operation: 'U' },
{ roleCode: 'pro_a_admin'.resourceCode: 'project_a'.operation: 'R' },
{ roleCode: 'pro_a_admin'.resourceCode: 'project_a'.operation: 'U' },
{ roleCode: 'pro_a_admin'.resourceCode: 'project_a'.operation: 'D' },
Copy the code

In this way, you only need to add the pro_a_view role to user B, and user B has read permission to project_A

Note that operation does not have a C here, because resources refer to individual projects, so where does a single project get create? Is it (^ del ^)

Daniel: Well, it looks exactly the same as the whole project function as a resource. But I found a problem, if each project as a resource, then I have to query user B can see which projects, seems to be very cumbersome. You can’t just find them one by one and put them together

Mr. Egg: Of course, remember the view we used above? Now let’s also create a view for a resource of type Project

CREATE VIEW 'user_project_view' AS 
    SELECT 
        ur.user_code,
        rp.resource_code,
        CONCAT('|',
                GROUP_CONCAT(DISTINCT rp.operation
                    SEPARATOR '|'),
                '|') operation
    FROM
        user_role ur,
        role_permissions rp,
        resources rs
    WHERE
        ur.role_code = rp.role_code
            AND rs.code = rp.resource_code
            AND rs.type = 'project'
    GROUP BY rp.resource_code , ur.user_code
Copy the code

This also requires a single table to query the list of items that user B can view and the permissions for each item

SELECT 
    *
FROM
    user_project_view
WHERE
    user_code = 'user_b'
        AND operation LIKE '%|R|%'
Copy the code

Special permission requirements

Daniel: Ouch! If user B has the edit role for project_a, he can only delete his own image resources, not others. I don’t want to record images as projects anymore

Mr. Egg :(LLL ¬ω¬) well…

Daniel: I guess you got it, haha

Mr. Egg: No, no, no. Powerful Operation is not a vegetarian. I simply add the limited modifier to the Update permission of the Edit role. Such as U_LIMITED

Daniel: That’s all right. It makes sense. Since Operation can be extended, as long as we specify its behavior, it’s as if everything is ok, right

Mr. Egg: All right. Scalability is a must, and operation is the key to it. Operation defines the operation identifier. The developer can implement the specified logic according to the convention of the operation identifier

Daniel: I see. Thanks, egghead. See you later

Mr. Egg: You’re welcome. Can’t you see me off?


This is the end, thanks for watching! (๑ ¯ ∀ ¯ ๑)