好吧,我有一个模仿NT DACL的访问控制列表系统。基本上,我有用户,组,它们之间的成员映射,以及具有任意数量的ACE引用用户或组的ACL。
(例如,这让“营销部门”小组可以访问某些内容,但是在营销方面工作的Joe是一个有问题的孩子,所以你可以拒绝他访问那些东西,他会被拒绝,但其他人都在小组将被允许)
我需要枚举由给定ACL控制的对象列表 - 在这种情况下,“受控对象”是用户对象本身。例如,假设用户Bob(带有uid=1
)想要从系统中删除另一个用户,我想要一个用户列表来显示他可以执行该操作的Bob。如果用户(此处由WHERE usr.id = 1
表示(“1”将由嵌入的PHP应用程序缓存))可以访问给定对象,我想显示它,以及是否(s)他没有,那么它不应该存在于结果集中。
这是迄今为止我提出的最好的方法:
SELECT `acelist`.id, `acelist`.first_name, `acelist`.last_name, `acelist`.acl
FROM
(
(
SELECT `usResult`.id, `usResult`.first_name, `usResult`.last_name, `usResult`.acl, `ace`.`allowed`
FROM `user` usResult
INNER JOIN access_control_list acl ON usResult.acl = acl.id
INNER JOIN group_access_control_entry ace ON acl.id = ace.acl
INNER JOIN `group` gp ON ace.gid = gp.id
INNER JOIN group_membership ON gp.id = group_membership.gid
INNER JOIN `user` usr ON group_membership.uid = usr.id
WHERE usr.id = 1
)
UNION ALL
(
SELECT `usResult`.id, `usResult`.first_name, `usResult`.last_name, `usResult`.acl, `ace`.`allowed`
FROM `user` usResult
INNER JOIN access_control_list acl ON usResult.acl = acl.id
INNER JOIN user_access_control_entry ace ON acl.id = ace.acl
INNER JOIN `user` usr ON ace.uid = usr.id
WHERE usr.id = 1
)
) AS acelist
GROUP BY `acelist`.id
HAVING COUNT(acelist.allowed) = SUM(acelist.allowed)
这是我正在使用的架构:
# Generated by Propel ORM
# This is a fix for InnoDB in MySQL >= 4.1.x
# It "suspends judgement" for fkey relationships until are tables are set.
SET FOREIGN_KEY_CHECKS = 0;
-- ---------------------------------------------------------------------
-- user
-- ---------------------------------------------------------------------
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user`
(
`id` INTEGER NOT NULL,
`first_name` VARCHAR(255) NOT NULL,
`last_name` VARCHAR(255) NOT NULL,
`direct_login` INTEGER,
`acl` INTEGER NOT NULL,
PRIMARY KEY (`id`),
INDEX `user_FI_1` (`acl`),
CONSTRAINT `user_FK_1`
FOREIGN KEY (`acl`)
REFERENCES `access_control_list` (`id`)
) ENGINE=InnoDB;
-- ---------------------------------------------------------------------
-- case_id_user
-- ---------------------------------------------------------------------
DROP TABLE IF EXISTS `case_id_user`;
CREATE TABLE `case_id_user`
(
`uid` INTEGER NOT NULL,
`case_id` VARCHAR(8),
PRIMARY KEY (`uid`),
UNIQUE INDEX `case_id_user_U_1` (`case_id`),
CONSTRAINT `case_id_user_FK_1`
FOREIGN KEY (`uid`)
REFERENCES `user` (`id`)
ON UPDATE CASCADE
ON DELETE CASCADE
) ENGINE=InnoDB;
-- ---------------------------------------------------------------------
-- direct_login_user
-- ---------------------------------------------------------------------
DROP TABLE IF EXISTS `direct_login_user`;
CREATE TABLE `direct_login_user`
(
`uid` INTEGER NOT NULL,
`passhash` CHAR(60) NOT NULL,
`email` VARCHAR(255) NOT NULL,
`user_name` VARCHAR(45) NOT NULL,
PRIMARY KEY (`uid`),
UNIQUE INDEX `direct_login_user_U_1` (`user_name`),
CONSTRAINT `direct_login_user_FK_1`
FOREIGN KEY (`uid`)
REFERENCES `user` (`id`)
ON UPDATE CASCADE
ON DELETE CASCADE
) ENGINE=InnoDB;
-- ---------------------------------------------------------------------
-- group
-- ---------------------------------------------------------------------
DROP TABLE IF EXISTS `group`;
CREATE TABLE `group`
(
`id` INTEGER NOT NULL,
`name` VARCHAR(45) NOT NULL,
`description` TEXT NOT NULL,
`acl` INTEGER NOT NULL,
PRIMARY KEY (`id`),
UNIQUE INDEX `group_U_1` (`name`),
INDEX `group_FI_1` (`acl`),
CONSTRAINT `group_FK_1`
FOREIGN KEY (`acl`)
REFERENCES `access_control_list` (`id`)
) ENGINE=InnoDB;
-- ---------------------------------------------------------------------
-- privilege
-- ---------------------------------------------------------------------
DROP TABLE IF EXISTS `privilege`;
CREATE TABLE `privilege`
(
`id` INTEGER NOT NULL AUTO_INCREMENT,
`name` VARCHAR(45) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE INDEX `privilege_U_1` (`name`)
) ENGINE=InnoDB;
-- ---------------------------------------------------------------------
-- access_control_list
-- ---------------------------------------------------------------------
DROP TABLE IF EXISTS `access_control_list`;
CREATE TABLE `access_control_list`
(
`id` INTEGER NOT NULL AUTO_INCREMENT,
PRIMARY KEY (`id`)
) ENGINE=InnoDB;
-- ---------------------------------------------------------------------
-- user_access_control_entry
-- ---------------------------------------------------------------------
DROP TABLE IF EXISTS `user_access_control_entry`;
CREATE TABLE `user_access_control_entry`
(
`acl` INTEGER NOT NULL,
`uid` INTEGER NOT NULL,
`privilege_id` INTEGER NOT NULL,
`allowed` TINYINT NOT NULL,
PRIMARY KEY (`acl`,`uid`,`privilege_id`,`allowed`),
INDEX `user_access_control_entry_FI_1` (`privilege_id`),
INDEX `user_access_control_entry_FI_2` (`uid`),
CONSTRAINT `user_access_control_entry_FK_1`
FOREIGN KEY (`privilege_id`)
REFERENCES `privilege` (`id`)
ON UPDATE CASCADE
ON DELETE CASCADE,
CONSTRAINT `user_access_control_entry_FK_2`
FOREIGN KEY (`uid`)
REFERENCES `user` (`id`)
ON UPDATE RESTRICT
ON DELETE CASCADE,
CONSTRAINT `user_access_control_entry_FK_3`
FOREIGN KEY (`acl`)
REFERENCES `access_control_list` (`id`)
ON UPDATE RESTRICT
ON DELETE CASCADE
) ENGINE=InnoDB;
-- ---------------------------------------------------------------------
-- group_access_control_entry
-- ---------------------------------------------------------------------
DROP TABLE IF EXISTS `group_access_control_entry`;
CREATE TABLE `group_access_control_entry`
(
`acl` INTEGER NOT NULL,
`gid` INTEGER NOT NULL,
`privilege_id` INTEGER NOT NULL,
`allowed` TINYINT NOT NULL,
PRIMARY KEY (`acl`,`gid`,`privilege_id`,`allowed`),
INDEX `group_access_control_entry_FI_1` (`privilege_id`),
INDEX `group_access_control_entry_FI_2` (`gid`),
CONSTRAINT `group_access_control_entry_FK_1`
FOREIGN KEY (`privilege_id`)
REFERENCES `privilege` (`id`)
ON UPDATE CASCADE
ON DELETE CASCADE,
CONSTRAINT `group_access_control_entry_FK_2`
FOREIGN KEY (`gid`)
REFERENCES `group` (`id`)
ON UPDATE RESTRICT
ON DELETE CASCADE,
CONSTRAINT `group_access_control_entry_FK_3`
FOREIGN KEY (`acl`)
REFERENCES `access_control_list` (`id`)
ON UPDATE RESTRICT
ON DELETE CASCADE
) ENGINE=InnoDB;
-- ---------------------------------------------------------------------
-- group_membership
-- ---------------------------------------------------------------------
DROP TABLE IF EXISTS `group_membership`;
CREATE TABLE `group_membership`
(
`uid` INTEGER NOT NULL,
`gid` INTEGER NOT NULL,
PRIMARY KEY (`uid`,`gid`),
INDEX `group_membership_FI_2` (`gid`),
CONSTRAINT `group_membership_FK_1`
FOREIGN KEY (`uid`)
REFERENCES `user` (`id`)
ON UPDATE RESTRICT
ON DELETE CASCADE,
CONSTRAINT `group_membership_FK_2`
FOREIGN KEY (`gid`)
REFERENCES `group` (`id`)
ON UPDATE RESTRICT
ON DELETE CASCADE
) ENGINE=InnoDB;
# This restores the fkey checks, after having unset them earlier
SET FOREIGN_KEY_CHECKS = 1;
这里的任何一个SQL专家都会看到我可能会做些什么来简化查询,或者让它更快地执行?
编辑:理想情况下,我能够以某种方式为任何ACL对象制作这项工作的复杂部分,以便我可以避免为每个ACL编写这样的查询' d数据库中的对象....
答案 0 :(得分:1)
当然不是更漂亮,但可能会更快,因为可以减少数据。
SELECT `acelist`.id, `acelist`.first_name, `acelist`.last_name, `acelist`.acl
FROM `usResult` acl
INNER JOIN (
SELECT `usResult`.id
FROM (
SELECT `usResult`.id
, SUM(`ace`.`allowed`) AS SumAllowed
, COUNT(`ace`.`allowed`) AS CountAllowed
FROM `user` usResult
INNER JOIN access_control_list acl ON usResult.acl = acl.id
INNER JOIN group_access_control_entry ace ON acl.id = ace.acl
INNER JOIN `group` gp ON ace.gid = gp.id
INNER JOIN group_membership ON gp.id = group_membership.gid
INNER JOIN `user` usr ON group_membership.uid = usr.id
WHERE usr.id = 1
GROUP BY
`usResult`.id
UNION ALL
SELECT `usResult`.id
, SUM(`ace`.`allowed`) AS SumAllowed
, COUNT(`ace`.`allowed`) AS CountAllowed
FROM `user` usResult
INNER JOIN access_control_list acl ON usResult.acl = acl.id
INNER JOIN user_access_control_entry ace ON acl.id = ace.acl
INNER JOIN `user` usr ON ace.uid = usr.id
WHERE usr.id = 1
GROUP BY
`usResult`.id
) results
GROUP BY
results.id
HAVING SUM(results.SumAllowed) = SUM(results.CountAllowed)
) r ON r.id = acl.id
答案 1 :(得分:0)
根据您的用户数量和授权操作的频率,您可能需要考虑为特定用户缓存整个权限子树。
您可以通过跟踪上次更新权限表的时间来坚持ACID缓存,这样可以快速查询是否可以检测是否需要运行较慢的缓存。
否则你的查询看起来不错;我不喜欢COUNT(允许)= SUM(允许)来确定没有零。与此方法相比,NOT EXIST变体应该能够进行优化,必须计算所有记录的COUNT()和SUM()。但是假设数据分布合理,我希望缓存权限的获胜因素要大得多。