Reputation: 1620
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
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