限制基于另一个列值的MySQL外键(“视图的列的外键”)?

时间:2019-12-20 09:53:49

标签: mysql access-rights

我的主要目标是拥有一个数据库用户,我可以限制访问权限,以便他们看不到任何机密信息,同时仍然能够使用外键进行完整性检查。因此,必须允许下面的foo用户只查看PUBLIC项,同时仍然能够在其other_data表上创建外键,以便该表不包含任何{{ 1}}值theyr无法看到。

这是设置:

id_item
-- These are items with a visibility
CREATE TABLE items (
    id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
    item_name VARCHAR(50) NOT NULL,
    visibility ENUM('PUBLIC','PRIVATE','DELETED') NOT NULL DEFAULT 'PRIVATE',
    INDEX (visibility),
    UNIQUE INDEX (item_name)
)
ENGINE=INNODB;

INSERT INTO items (id, item_name, visibility) 
    VALUES (1, 'x', 'PUBLIC'), (2, 'y', 'PRIVATE'), (3, 'z', 'DELETED'), (4, 'xprime', 'PUBLIC'), (5, 'yprime', 'PRIVATE'), (6, 'zprime', 'DELETED');

-- This view only shows the public items (not private ones, not deleted ones)
CREATE VIEW public_items AS 
    (SELECT id, item_name FROM items WHERE visibility = 'PUBLIC');

-- How can I make this table definition only allow id_item to be a value from public_items.id?
-- I cannot use the view in the constraint, tho it would be perfect solution
CREATE TABLE other_data (
    id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
    label VARCHAR(50) NOT NULL,
    id_item INT UNSIGNED NOT NULL,
    UNIQUE KEY (label),
    CONSTRAINT FOREIGN KEY (id_item) REFERENCES /*public_*/items (id)
        ON UPDATE CASCADE ON DELETE RESTRICT
)ENGINE=INNODB;


-- Let's create the foo user that I want to restrict access rights
CREATE USER 'foo'@'localhost' IDENTIFIED BY 'bar';
GRANT USAGE ON *.* TO 'foo'@'localhost';
GRANT SELECT ON test_sql.public_items TO 'foo'@'localhost';
GRANT SELECT,INSERT ON test_sql.other_data TO 'foo'@'localhost';
FLUSH PRIVILEGES;

我在这里使用mysql 5.7,因此我可以在-- Now, use foo@localhost user -- This should return (1;4) because foo must be allowed to see the public items => OK SELECT * FROM public_items; -- This should be DENIED because foo is not allowed to see all other items => OK SELECT * FROM items; -- This should be ALLOWED because foo is allowed to insert rows in other_data => OK -- and this row refers to a public item INSERT INTO other_data (id, label, id_item) VALUES (11, 'allowed-public', 1); -- This should be ALLOWED because foo is allowed to see all the other_data => OK SELECT * FROM other_data; -- This should be FORBIDDEN because the item is not public => FAIL, it can be inserted... INSERT INTO other_data (id, label, id_item) VALUES (12, 'forbidden-private', 2); -- This should be FORBIDDEN because the item is not public => FAIL, it can be inserted INSERT INTO other_data (id, label, id_item) VALUES (13, 'forbidden-deleted', 3); -- This should be FORBIDDEN because the item does not exist => OK, it cannot be inserted INSERT INTO other_data (id, label, id_item) VALUES (19, 'forbidden-notexist', 9); 表中使用一个生成的列,就像items一样,将我的FK放在该列上,但是生成的列禁止引用自动-增量(id_if_public INT UNSIGNED GENERATED ALWAYS AS IF(visibility = 'PUBLIC', id, NULL) STORED)列...

我可以使用id之类的CHECK语法移至MySQL 8,就像other_data一样,但似乎CHECK EXISTS(SELECT 1 FROM public_items WHERE public_items.id = other_data.id_item)不允许子查询...

我可以将CHECK转换成表格并使用public_items等对其进行“维护”,但是感觉很繁琐,并且需要TRIGGER额外的存储空间,因此并非完全可扩展(即item_name中的列越多,越重!)

1 个答案:

答案 0 :(得分:0)

拥有这样一个新表public_items并不是一个坏主意,因为您需要使用一些外键。您可以将其与视图结合使用,以从items表中获取元信息,因此无需将它们两次添加到新的public_items表中。这些表应如下所示:

CREATE TABLE items (
    id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
    item_name VARCHAR(50) NOT NULL,
    visibility ENUM('PUBLIC','PRIVATE','DELETED') NOT NULL DEFAULT 'PRIVATE',
    INDEX (visibility),
    UNIQUE INDEX (item_name),
    UNIQUE INDEX (id, visibility) /* new index over 'id' and 'visibility' */
);

现在您创建一个新表public_items,其外键位于(id, visibility)上,但是强制只使用"PUBLIC"个条目。

CREATE TABLE public_items (
    id INT UNSIGNED NOT NULL PRIMARY KEY,
    visibility ENUM('PUBLIC') NOT NULL, /* only allow public items */

    CONSTRAINT FK_public_items_id_visibility FOREIGN KEY (id, visibility) 
                                             REFERENCES items(id, visibility)
);

现在,您只能在items的{​​{1}}中添加公开的行,并且可以通过外键引用这些行。您的public_items表必须将外键更改为此新的other_data表。

显然,这种方法有一些缺点:

  • 您必须使用{source}表public_items中的条目来维护public_items中的公共项目列表。您可以使用TRIGGER或后台进程来使这些条目保持同步,也可以使用其他任何方法。
  • items表中没有public_items列。如果您不想将这些元信息复制到item_name表中(提到了同步/更新过程),可能仍需要一个视图来从items表中获取公共项目及其元信息。以上)。