MichaelH
MichaelH

Reputation: 1620

Is There A Better Way Of Doing This? MySQL

I am in the middle of moving a permission system from PHP to MySQL stored procedures so I can put the site across different devices.

My permission system works like this: An action is something a user can do. (Etc Upload Photo) A user can have individual permissions, permissions can be set by roles or just global permissions. A deny permission takes over an allow permission. A permission set for a user will overwrite all other permissions.

My Tables Look Like This

For Every Action

ACL_Actions: 
   ActionID | Default_DOA

For Every Role

ACL_Roles: 
   RoleID | Name_Of_Role

To Set The Permission Of An Action For Users In A Role

ACL_Role_Actions: 
   ID | RoleID | ActionID | Role_DOA

To Put Users In A Role

ACL_Role_Users:
   ID | RoleID | UserID

For Every User

ACL_Users:
   UserID | Details

Allows A User Defined Permission For An Action

ACL_User_Actions:
   ID | ActionID | UserID | User_DOA

These are just parts of a the tables, and DOA stands for DenyOrAllow.

DOA Fields are bits. 0 = Deny, 1 = Allow

Now onto my question. Is this stored procedure the best way I can do this? I am trying to load a users permissions for the actions.

BEGIN
DECLARE current_doa INT;
DECLARE action_id INT;
DECLARE done INT DEFAULT 0;

DECLARE cur1 CURSOR FOR SELECT ActionID, User_DOA FROM ACL_User_Actions WHERE UserID = euser_id;
DECLARE cur2 CURSOR FOR SELECT ActioNID, Role_DOA FROM ACL_Role_Actions WHERE RoleID IN (SELECT RoleID FROM ACL_Role_Users WHERE UserID = euser_id);
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = 1;

DROP TEMPORARY TABLE IF EXISTS user_roles;
CREATE TEMPORARY TABLE user_roles
SELECT ACL_Actions.ActionID as ActionID, ACL_Actions.Default_DOA as DOA
From ACL_Actions;

OPEN cur1;
    read_loop: LOOP
        FETCH cur1 INTO action_id, current_doa;
        IF done THEN
            LEAVE read_loop;
        END IF;
        IF current_doa = 0 THEN
            UPDATE user_roles SET DOA = current_doa WHERE ActionID = action_id;
        END IF;     
    END LOOP;
CLOSE cur1;

SET done = 0;

OPEN cur2;
    read_loop: LOOP
        FETCH cur2 INTO action_id, current_doa;
        IF done THEN
            LEAVE read_loop;
        END IF;
        UPDATE user_roles SET DOA = current_doa WHERE ActionID = action_id;
    END LOOP;
CLOSE cur2;


SELECT * FROM user_roles;
END

Upvotes: 1

Views: 133

Answers (1)

bobflux
bobflux

Reputation: 11581

Note that the following tables do not need an id column so get rid of them. At least make the relevant columns UNIQUE NOT NULL. With duplicates, obviously, the data is meaningless, since you can have both allow and deny lines for the same user/action, for instance.

ACL_Role_Actions: : ID | RoleID | ActionID | Role_DOA

=> PK is (RoleID,ActionID)

ACL_Role_Users: ID | RoleID | UserID

=> PK is (UserID,RoleID)

ACL_User_Actions: ID | ActionID | UserID | User_DOA

=> PK is (UserID,ActionID)

Since you got a users-to-roles table this means a user can be a member of several roles, but you do not specify a priority among roles. If a user is a member of role A which allows action X, and role B which denies action X, the behaviour is undefined. So you need to add a "priority" column to the roles table, I'll define this as a positive integer, with 0 having the highest priority. priority must be UNIQUE in the Roles table.

My first thought was to use a FULL OUTER JOIN between both rights tables, but mysql doesn't support that.

My second thought was to UNION the results of the users/roles rights queries (putting the users rights first) and use a windowing function (FIRST() OVER()) to grab the first row overriding others, but MySQL doesn't support that.

Try #1:

SELECT ActionID, GROUP_CONCAT(doa ORDER BY priority) FROM (
  SELECT NULL as priority, ActionID, User_DOA doa FROM ACL_User_Actions WHERE UserID = euser_id;
  UNION ALL
  SELECT priority, ActioNID, Role_DOA doa FROM ACL_Role_Actions JOIN ACL_Role_Users USING (RoleID) WHERE UserID = euser_id
) foo GROUP BY ActioNID

GROUP_CONCAT(User_DOA) will return "01" if user=deny and role=allow, just substr() the first char out of it. Note that ORDER BY priority puts NULLs first, which means the User permissions will appear first.

Upvotes: 2

Related Questions