细粒度访问控制(示例)

在创建数据库时,管理员可能希望确定哪些用户可以访问某些信息。

内置角色和权限中所述,Neo4j 已经提供了配置为特定权限(例如读取、编辑或写入)的预设角色。虽然这些内置角色涵盖了许多常见的日常场景,但也可以根据特定需求创建自定义角色。

此页面包含一个示例,说明了安全性和细粒度访问控制的各个方面。

医疗保健用例

为了演示这些工具的应用,请考虑一个医疗保健数据库的示例,该数据库可能与医疗诊所或医院相关。

为简单起见,仅使用三个标签来表示以下实体

(:Patient)

因出现某些症状而就诊的患者。患者的具体信息可以捕获在属性中

  • name

  • ssn

  • address

  • dateOfBirth

(:Symptom)

在已知疾病目录中发现的一组症状。它们可以使用以下属性进行描述

  • name

  • description

(:Disease)

在数据库中找到的目录中映射的已知疾病。它们可以使用以下属性进行描述

  • name

  • description

这些实体被建模为节点,并通过以下类型的关系连接

(:Patient)-[:HAS]→(:Symptom)

当患者向诊所报告时,他们会向护士或医生描述他们的症状。然后,护士或医生会将此信息以患者节点和已知症状图之间的连接形式输入数据库。此关系上的可能感兴趣属性包括

  • date - 报告症状的日期。

(:Symptom)-[:OF]→(:Disease)

症状是已知疾病图中的一个子图。症状和疾病之间的关系可以包含一个概率因子,表示患有该疾病的人表达该症状的可能性或普遍程度。这将使医生更容易使用统计查询进行诊断。

  • probability - 症状与疾病匹配的概率。

(:Patient)-[:DIAGNOSIS]→(:Disease)

医生可以使用疾病及其症状图对最有可能与患者匹配的疾病进行初步调查。在此基础上,以及他们自己对患者的评估,医生可能会做出诊断,他们会通过添加此关系并附带适当的属性将其持久化到图中

  • by: 医生的姓名

  • date: 诊断日期

  • description: 医生额外的备注

security example
图 1. 医疗保健用例

同一个数据库将被许多不同的用户使用,每个用户都有不同的访问需求

  • 医生需要诊断患者。

  • 护士需要治疗患者。

  • 接待员需要识别和记录患者信息。

  • 研究人员需要对医疗数据进行统计分析。

  • IT 管理员需要管理数据库,例如创建和分配用户。

安全

与通常要求用户在应用程序本身中建模的应用程序不同,数据库提供了用户管理资源,例如角色和权限。这允许用户完全在数据库安全模型中创建,这种策略允许将对数据的访问与数据本身分离。有关更多信息,请参阅身份验证和授权

以下示例展示了两种不同的方法,用于使用 Neo4j 安全功能来支持医疗保健数据库应用程序。第一种方法使用内置角色和权限,而第二种方法使用更高级的资源,并提供用于子图访问控制的细粒度权限。

在此示例中,请考虑医疗保健数据库的五个用户

  • 爱丽丝,医生。

  • 丹尼尔,护士。

  • 鲍勃,接待员。

  • 查理,研究员。

  • 蒂娜,IT 管理员。

可以使用CREATE USER命令(来自system数据库)创建这些用户

CREATE USER charlie SET PASSWORD $secretpassword1 CHANGE NOT REQUIRED;
CREATE USER alice SET PASSWORD $secretpassword2 CHANGE NOT REQUIRED;
CREATE USER daniel SET PASSWORD $secretpassword3 CHANGE NOT REQUIRED;
CREATE USER bob SET PASSWORD $secretpassword4 CHANGE NOT REQUIRED;
CREATE USER tina SET PASSWORD $secretpassword5 CHANGE NOT REQUIRED;

此时,用户无法与数据库交互,因此需要通过使用角色授予这些功能。有两种不同的方法可以做到这一点,要么使用内置角色,要么通过使用权限和自定义角色进行更细粒度的访问控制。

使用内置角色进行访问控制

Neo4j 带有内置角色,可以满足许多常见需求

  • PUBLIC - 所有用户都具有此角色。默认情况下,他们可以访问主数据库、加载数据并运行所有过程和用户定义函数。

  • reader - 可以从所有数据库读取数据。

  • editor - 可以读取和更新所有数据库,但不能使用新的标签、关系类型或属性名称扩展模式。

  • publisher - 可以读取和编辑,以及添加新的标签、关系类型和属性名称。

  • architect - 具有发布者的所有功能,以及管理索引和约束的功能。

  • admin - 可以执行架构操作,以及加载数据和管理数据库、用户、角色和权限。

考虑用户示例中的查理。作为一名研究人员,他们不需要对数据库进行写入访问,因此他们被分配了reader角色。

另一方面,爱丽丝(医生)、丹尼尔(护士)和鲍勃(接待员)都需要使用新的患者信息更新数据库,但不需要使用新的标签、关系类型、属性名称或索引扩展模式。因此,他们都被分配了editor角色。

蒂娜是安装和管理数据库的 IT 管理员,需要分配admin角色。

以下是将角色授予用户的方法

GRANT ROLE reader TO charlie;
GRANT ROLE editor TO alice;
GRANT ROLE editor TO daniel;
GRANT ROLE editor TO bob;
GRANT ROLE admin TO tina;

使用权限进行子图访问控制

前面描述的方法的一个限制是它允许所有用户查看数据库上的所有数据。但在许多现实世界场景中,最好建立一些访问限制。

例如,您可能希望限制研究人员访问患者的个人信息,或限制接待员在数据库上写入新的标签。虽然这些限制可以编码到应用程序层,但可以通过创建自定义角色并向其分配特定权限,在 Neo4j 安全模型中直接强制执行细粒度限制,这是一种更安全的方法。

由于将创建新的自定义角色,因此首先需要撤消分配给它们的用户的当前角色

REVOKE ROLE reader FROM charlie;
REVOKE ROLE editor FROM alice;
REVOKE ROLE editor FROM daniel;
REVOKE ROLE editor FROM bob;
REVOKE ROLE admin FROM tina;

现在,您可以基于权限的概念创建自定义角色,这允许您更好地控制每个用户能够执行的操作。为了正确分配这些权限,请先识别每种类型的用户

医生

应该能够读取和写入大部分图,但应阻止读取患者的地址。有权将诊断保存到数据库,但不能使用新概念扩展模式。

接待员

应该能够读取和写入所有患者数据,但不能查看症状、疾病或诊断。

研究人员

应该能够对所有数据(除了患者的个人信息)进行统计分析,他们应该限制访问这些信息。为了说明设置相同有效权限的两种不同方法,创建了两个角色进行比较。

护士

应该能够执行医生和接待员都能执行的所有任务。将医生和接待员两个角色都授予护士不会按预期工作。这在专门用于创建nurse角色的部分中进行了说明。

初级护士

虽然高级护士能够像医生一样保存诊断,但某些(初级)护士可能不允许这样做。从头开始创建另一个角色是一种选择,但可以通过将nurse角色与一个专门限制该活动的新disableDiagnoses角色结合来实现相同的结果。

IT 管理员

此角色与内置的admin角色非常相似,只是它不允许访问患者的SSN或保存诊断,这项权限仅限于医疗专业人员。为了实现这一点,可以复制并修改内置的admin角色。

用户管理员

此用户应该具有与 IT 管理员类似的访问权限,但限制更多。为了实现这一点,可以从头开始创建一个新角色,并仅向其分配特定的管理功能。

在创建新角色并将它们分配给爱丽丝、鲍勃、丹尼尔、查理和蒂娜之前,务必定义每个角色应该具有的权限。由于所有用户都需要对healthcare数据库具有ACCESS权限,因此可以通过PUBLIC角色而不是所有单独的角色来设置此权限

GRANT ACCESS ON DATABASE healthcare TO PUBLIC;

itadmin的权限

此角色可以作为内置admin角色的副本创建

CREATE ROLE itadmin AS COPY OF admin;

然后,您需要拒绝此角色不允许执行的两个特定操作

  • 读取任何患者的社会安全号码 (SSN)。

  • 提交医疗诊断。

以及itadmin修改自身权限的能力。

DENY READ {ssn} ON GRAPH healthcare NODES Patient TO itadmin;
DENY CREATE ON GRAPH healthcare RELATIONSHIPS DIAGNOSIS TO itadmin;
DENY ROLE MANAGEMENT ON DBMS TO itadmin;
DENY PRIVILEGE MANAGEMENT ON DBMS TO itadmin;

分配了itadmin角色的用户可用的完整权限集可以使用以下命令查看

SHOW ROLE itadmin PRIVILEGES AS COMMANDS;
+-------------------------------------------------------------------------+
| command                                                                 |
+-------------------------------------------------------------------------+
| "GRANT ACCESS ON DATABASE * TO `itadmin`"                               |
| "GRANT MATCH {*} ON GRAPH * NODE * TO `itadmin`"                        |
| "GRANT MATCH {*} ON GRAPH * RELATIONSHIP * TO `itadmin`"                |
| "GRANT WRITE ON GRAPH * TO `itadmin`"                                   |
| "GRANT INDEX MANAGEMENT ON DATABASE * TO `itadmin`"                     |
| "GRANT CONSTRAINT MANAGEMENT ON DATABASE * TO `itadmin`"                |
| "GRANT NAME MANAGEMENT ON DATABASE * TO `itadmin`"                      |
| "GRANT START ON DATABASE * TO `itadmin`"                                |
| "GRANT STOP ON DATABASE * TO `itadmin`"                                 |
| "GRANT TRANSACTION MANAGEMENT (*) ON DATABASE * TO `itadmin`"           |
| "GRANT ALL DBMS PRIVILEGES ON DBMS TO `itadmin`"                        |
| "GRANT LOAD ON ALL DATA TO `itadmin`"                                   |
| "DENY READ {ssn} ON GRAPH `healthcare` NODE Patient TO `itadmin`"       |
| "DENY CREATE ON GRAPH `healthcare` RELATIONSHIP DIAGNOSIS TO `itadmin`" |
| "DENY ROLE MANAGEMENT ON DBMS TO `itadmin`"                             |
| "DENY PRIVILEGE MANAGEMENT ON DBMS TO `itadmin`"                        |
+-------------------------------------------------------------------------+

先前授予或拒绝的权限可以使用REVOKE命令撤消。

要为 IT 管理员tina提供这些权限,必须为其分配新角色itadmin

neo4j@system> GRANT ROLE itadmin TO tina;

为了演示蒂娜无法查看患者的SSN,您可以以tina身份登录到healthcare并运行以下查询

MATCH (n:Patient)
 WHERE n.dateOfBirth < date('1972-06-12')
RETURN n.name, n.ssn, n.address, n.dateOfBirth;
+--------------------------------------------------------------------+
| n.name          | n.ssn | n.address                | n.dateOfBirth |
+--------------------------------------------------------------------+
| "Mary Stone"    | NULL  | "1 secret way, downtown" | 1970-01-15    |
| "Ally Anderson" | NULL  | "1 secret way, downtown" | 1970-08-20    |
| "Sally Stone"   | NULL  | "1 secret way, downtown" | 1970-03-12    |
| "Jane Stone"    | NULL  | "1 secret way, downtown" | 1970-07-21    |
| "Ally Svensson" | NULL  | "1 secret way, downtown" | 1971-08-15    |
| "Jane Svensson" | NULL  | "1 secret way, downtown" | 1972-05-12    |
| "Ally Svensson" | NULL  | "1 secret way, downtown" | 1971-07-30    |
+--------------------------------------------------------------------+

结果看起来好像这些节点甚至没有SSN字段。这是安全模型的一个关键特性:用户无法区分不存在的数据和使用细粒度读取权限隐藏的数据。

现在回想一下,itadmin角色被拒绝了保存诊断的能力(因为这是一项关键的医疗功能,仅限于医生和高级医务人员),您可以通过尝试创建DIAGNOSIS关系来测试这一点

MATCH (n:Patient), (d:Disease)
CREATE (n)-[:DIAGNOSIS]->(d);
Create relationship with type 'DIAGNOSIS' is not allowed for user 'tina' with roles [PUBLIC, itadmin].

读取数据的限制不会导致错误,它们只会使数据看起来不存在。但是,更新图的限制在用户尝试执行他们不允许执行的操作时会输出相应的错误。

researcher的权限

研究员查理以前是只读用户。要为他们分配所需的权限,您可以执行与itadmin角色类似的操作,这次复制并修改reader角色。

另一种方法是从头开始创建一个新角色,然后授予或拒绝权限列表

  • 拒绝权限:

    您可以授予researcher角色查找所有节点和读取所有属性的能力(就像reader角色一样),但拒绝对Patient属性的读取访问权限。这样,研究人员将无法查看患者的信息,例如nameSSNaddress。但是,这种方法存在一个问题:如果在将限制分配给researcher角色之后Patient节点添加了更多属性,则这些新属性将自动对研究人员可见,这可能是不可取的结果。

    为了避免这种情况,您可以改为拒绝特定权限

    // First create the role
    CREATE ROLE researcherB;
    // Then grant access to everything
    GRANT MATCH {*}
        ON GRAPH healthcare
        TO researcherB;
    // And deny read on specific node properties
    DENY READ {name, address, ssn}
        ON GRAPH healthcare
        NODES Patient
        TO researcherB;
    // And finally deny traversal of the doctors diagnosis
    DENY TRAVERSE
        ON GRAPH healthcare
        RELATIONSHIPS DIAGNOSIS
        TO researcherB;
  • 授予权限:

    另一种方法是仅提供研究人员允许查看的属性的特定访问权限。这样,添加新属性(例如,到Patient节点)不会自动使分配了此角色的用户能够查看它们。但是,如果您希望使它们可见,则需要显式授予读取访问权限

// Create the role first
CREATE ROLE researcherW
// Allow the researcher to find all nodes
GRANT TRAVERSE
    ON GRAPH healthcare
    NODES *
    TO researcherW;
// Now only allow the researcher to traverse specific relationships
GRANT TRAVERSE
    ON GRAPH healthcare
    RELATIONSHIPS HAS, OF
    TO researcherW;
// Allow reading of all properties of medical metadata
GRANT READ {*}
    ON GRAPH healthcare
    NODES Symptom, Disease
    TO researcherW;
// Allow reading of all properties of the disease-symptom relationship
GRANT READ {*}
    ON GRAPH healthcare
    RELATIONSHIPS OF
    TO researcherW;
// Only allow reading dateOfBirth for research purposes
GRANT READ {dateOfBirth}
    ON GRAPH healthcare
    NODES Patient
    TO researcherW;

为了测试研究员 Charlie 是否拥有指定的权限,请为其分配 researcherB 角色(具有特定拒绝的权限)。

GRANT ROLE researcherB TO charlie;

您也可以使用 SHOW PRIVILEGES 命令的某个版本来查看 Charlie 的访问权限,这些权限是分配给 researcherBPUBLIC 角色的权限的组合。

neo4j@system> SHOW USER charlie PRIVILEGES AS COMMANDS;
+-----------------------------------------------------------------------+
| command                                                               |
+-----------------------------------------------------------------------+
| "GRANT ACCESS ON HOME DATABASE TO $role"                              |
| "GRANT ACCESS ON DATABASE `healthcare` TO $role"                      |
| "GRANT EXECUTE PROCEDURE * ON DBMS TO $role"                          |
| "GRANT EXECUTE FUNCTION * ON DBMS TO $role"                           |
| "GRANT MATCH {*} ON GRAPH `healthcare` NODE * TO $role"               |
| "GRANT MATCH {*} ON GRAPH `healthcare` RELATIONSHIP * TO $role"       |
| "GRANT LOAD ON ALL DATA TO $role"                                     |
| "DENY TRAVERSE ON GRAPH `healthcare` RELATIONSHIP DIAGNOSIS TO $role" |
| "DENY READ {address} ON GRAPH `healthcare` NODE Patient TO $role"     |
| "DENY READ {name} ON GRAPH `healthcare` NODE Patient TO $role"        |
| "DENY READ {ssn} ON GRAPH `healthcare` NODE Patient TO $role"         |
+-----------------------------------------------------------------------+

现在,当 Charlie 登录到 healthcare 数据库并尝试运行类似于 itadmin 之前使用的命令时,他们将看到不同的结果。

MATCH (n:Patient)
 WHERE n.dateOfBirth < date('1972-06-12')
RETURN n.name, n.ssn, n.address, n.dateOfBirth;
+--------------------------------------------+
| n.name | n.ssn | n.address | n.dateOfBirth |
+--------------------------------------------+
| NULL   | NULL  | NULL      | 1971-05-31    |
| NULL   | NULL  | NULL      | 1971-04-17    |
| NULL   | NULL  | NULL      | 1971-12-27    |
| NULL   | NULL  | NULL      | 1970-02-13    |
| NULL   | NULL  | NULL      | 1971-02-04    |
| NULL   | NULL  | NULL      | 1971-05-10    |
| NULL   | NULL  | NULL      | 1971-02-21    |
+--------------------------------------------+

只有出生日期可用,以便研究员 Charlie 可以执行统计分析,例如。Charlie 可以尝试的另一个查询是找到 25 岁以下患者最可能被诊断出的十大疾病,按概率列出。

WITH datetime() - duration({years:25}) AS timeLimit
MATCH (n:Patient)
WHERE n.dateOfBirth > date(timeLimit)
MATCH (n)-[h:HAS]->(s:Symptom)-[o:OF]->(d:Disease)
WITH d.name AS disease, o.probability AS prob
RETURN disease, sum(prob) AS score ORDER BY score DESC LIMIT 10;
+-------------------------------------------+
| disease               | score             |
+-------------------------------------------+
| "Acute Argitis"       | 95.05395287286318 |
| "Chronic Someitis"    | 88.7220337139605  |
| "Chronic Placeboitis" | 88.43609533058974 |
| "Acute Whatitis"      | 83.23493746472457 |
| "Acute Otheritis"     | 82.46129768949129 |
| "Chronic Otheritis"   | 82.03650063794025 |
| "Acute Placeboitis"   | 77.34207326583929 |
| "Acute Yellowitis"    | 76.34519967465832 |
| "Chronic Whatitis"    | 73.73968070128234 |
| "Chronic Yellowitis"  | 71.58791287376775 |
+-------------------------------------------+

如果撤销 Charlie 的 researcherB 角色,但授予 researcherW 角色,则重新运行这些查询时,将获得相同的结果。

先前授予或拒绝的权限可以使用REVOKE命令撤消。

doctor 的权限

应该授予医生读取和写入几乎所有内容的能力,例如,除了患者的 address 属性。可以通过分配完全的读写访问权限,然后专门拒绝对 address 属性的访问来从头开始构建此角色。

CREATE ROLE doctor;
GRANT TRAVERSE ON GRAPH healthcare TO doctor;
GRANT READ {*} ON GRAPH healthcare TO doctor;
GRANT WRITE ON GRAPH healthcare TO doctor;
DENY READ {address} ON GRAPH healthcare NODES Patient TO doctor;
DENY SET PROPERTY {address} ON GRAPH healthcare NODES Patient TO doctor;

要允许医生 Alice 拥有这些权限,请授予她此新角色。

neo4j@system> GRANT ROLE doctor TO alice;

为了证明 Alice 无法查看患者地址,请以 alice 身份登录到 healthcare 并运行以下查询。

MATCH (n:Patient)
 WHERE n.dateOfBirth < date('1972-06-12')
RETURN n.name, n.ssn, n.address, n.dateOfBirth;
+-------------------------------------------------------+
| n.name          | n.ssn   | n.address | n.dateOfBirth |
+-------------------------------------------------------+
| "Jack Anderson" | 1234647 | NULL      | 1970-07-23    |
| "Joe Svensson"  | 1234659 | NULL      | 1972-06-07    |
| "Mary Jackson"  | 1234568 | NULL      | 1971-10-19    |
| "Jack Jackson"  | 1234583 | NULL      | 1971-05-04    |
| "Ally Smith"    | 1234590 | NULL      | 1971-12-07    |
| "Ally Stone"    | 1234606 | NULL      | 1970-03-29    |
| "Mark Smith"    | 1234610 | NULL      | 1971-03-30    |
+-------------------------------------------------------+

结果,医生拥有预期的权限,包括能够查看患者的 SSN,但不能查看他们的地址。

医生还可以查看所有其他节点类型。

MATCH (n) WITH labels(n) AS labels
RETURN labels, count(*);
+------------------------+
| labels      | count(*) |
+------------------------+
| ["Patient"] | 101      |
| ["Symptom"] | 10       |
| ["Disease"] | 12       |
+------------------------+

此外,医生可以遍历图,查找与患者相关的症状和疾病。

MATCH (n:Patient)-[:HAS]->(s:Symptom)-[:OF]->(d:Disease)
  WHERE n.ssn = 1234657
RETURN n.name, d.name, count(s) AS score ORDER BY score DESC;

结果表显示了基于症状最可能的诊断。医生可以使用此表来促进对患者的进一步询问和检查,以便确定最终诊断。

+--------------------------------------------------+
| n.name           | d.name                | score |
+--------------------------------------------------+
| "Sally Anderson" | "Chronic Otheritis"   | 4     |
| "Sally Anderson" | "Chronic Yellowitis"  | 3     |
| "Sally Anderson" | "Chronic Placeboitis" | 3     |
| "Sally Anderson" | "Acute Whatitis"      | 2     |
| "Sally Anderson" | "Acute Yellowitis"    | 2     |
| "Sally Anderson" | "Chronic Someitis"    | 2     |
| "Sally Anderson" | "Chronic Argitis"     | 2     |
| "Sally Anderson" | "Chronic Whatitis"    | 2     |
| "Sally Anderson" | "Acute Someitis"      | 1     |
| "Sally Anderson" | "Acute Argitis"       | 1     |
| "Sally Anderson" | "Acute Otheritis"     | 1     |
+--------------------------------------------------+

一旦医生进行了进一步的调查,他们就可以确定诊断并将结果保存到数据库中。

WITH datetime({epochmillis:timestamp()}) AS now
WITH now, date(now) as today
MATCH (p:Patient)
  WHERE p.ssn = 1234657
MATCH (d:Disease)
  WHERE d.name = "Chronic Placeboitis"
MERGE (p)-[i:DIAGNOSIS {by: 'Alice'}]->(d)
  ON CREATE SET i.created_at = now, i.updated_at = now, i.date = today
  ON MATCH SET i.updated_at = now
RETURN p.name, d.name, i.by, i.date, duration.between(i.created_at, i.updated_at) AS updated;

这允许医生记录他们的诊断以及记录以前的诊断。

+----------------------------------------------------------------------------------------+
| p.name           | d.name                | i.by    | i.date     | updated              |
+----------------------------------------------------------------------------------------+
| "Sally Anderson" | "Chronic Placeboitis" | "Alice" | 2020-05-29 | P0M0DT213.076000000S |
+----------------------------------------------------------------------------------------+

首次创建 DIAGNOSIS 关系需要创建新类型的权限。属性名称 doctorcreated_atupdated_at 也适用。可以通过授予医生 NAME MANAGEMENT 权限或预先创建缺失的类型来解决此问题。后者将更加精确,可以通过以管理员身份运行 db.createRelationshipTypedb.createProperty 过程并使用适当的参数来实现。

receptionist 的权限

接待员应该只能管理患者信息。他们不允许查找或读取图的任何其他部分。此外,他们应该能够创建和删除患者,但不能创建任何其他节点。

CREATE ROLE receptionist;
GRANT MATCH {*} ON GRAPH healthcare NODES Patient TO receptionist;
GRANT CREATE ON GRAPH healthcare NODES Patient TO receptionist;
GRANT DELETE ON GRAPH healthcare NODES Patient TO receptionist;
GRANT SET PROPERTY {*} ON GRAPH healthcare NODES Patient TO receptionist;

授予接待员 Bob 全局 WRITE 权限本来会更简单。但是,这将具有不幸的副作用,即允许他们创建其他节点,例如新的 Symptom 节点,即使他们随后无法查找或读取这些相同的节点。虽然在某些用例中,希望拥有能够创建他们无法读取的数据的角色,但这并非此模型的情况。

考虑到这一点,请为接待员 Bob 授予他们的新 receptionist 角色。

neo4j@system> GRANT ROLE receptionist TO bob;

使用这些权限,如果 Bob 尝试读取整个数据库,他们仍然只能看到患者。

MATCH (n) WITH labels(n) AS labels
RETURN labels, count(*);
+------------------------+
| labels      | count(*) |
+------------------------+
| ["Patient"] | 101      |
+------------------------+

但是,Bob 能够查看患者记录的所有字段。

MATCH (n:Patient)
 WHERE n.dateOfBirth < date('1972-06-12')
RETURN n.name, n.ssn, n.address, n.dateOfBirth;
+----------------------------------------------------------------------+
| n.name          | n.ssn   | n.address                | n.dateOfBirth |
+----------------------------------------------------------------------+
| "Mark Stone"    | 1234666 | "1 secret way, downtown" | 1970-08-04    |
| "Sally Jackson" | 1234633 | "1 secret way, downtown" | 1970-10-21    |
| "Bob Stone"     | 1234581 | "1 secret way, downtown" | 1972-02-16    |
| "Ally Anderson" | 1234582 | "1 secret way, downtown" | 1970-05-13    |
| "Mark Svensson" | 1234594 | "1 secret way, downtown" | 1970-01-16    |
| "Bob Anderson"  | 1234597 | "1 secret way, downtown" | 1970-09-23    |
| "Jack Svensson" | 1234599 | "1 secret way, downtown" | 1971-02-13    |
| "Mark Jackson"  | 1234618 | "1 secret way, downtown" | 1970-03-28    |
| "Jack Jackson"  | 1234623 | "1 secret way, downtown" | 1971-04-02    |
+----------------------------------------------------------------------+

使用 receptionist 角色,Bob 可以删除他们刚刚创建的任何新的患者节点,但他们无法删除已经收到诊断的患者,因为这些患者与 Bob 无法查看的图的一部分相连接。这是一个演示两种情况的示例。

CREATE (n:Patient {
  ssn:87654321,
  name: 'Another Patient',
  email: 'another@example.com',
  address: '1 secret way, downtown',
  dateOfBirth: date('2001-01-20')
})
RETURN n.name, n.dateOfBirth;
+-----------------------------------+
| n.name            | n.dateOfBirth |
+-----------------------------------+
| "Another Patient" | 2001-01-20    |
+-----------------------------------+

接待员能够修改任何患者记录。

MATCH (n:Patient)
WHERE n.ssn = 87654321
SET n.address = '2 streets down, uptown'
RETURN n.name, n.dateOfBirth, n.address;
+--------------------------------------------------------------+
| n.name            | n.dateOfBirth | n.address                |
+--------------------------------------------------------------+
| "Another Patient" | 2001-01-20    | "2 streets down, uptown" |
+--------------------------------------------------------------+

接待员还可以删除这个最近创建的患者,因为它没有连接到任何其他记录。

MATCH (n:Patient)
 WHERE n.ssn = 87654321
DETACH DELETE n;

但是,如果接待员尝试删除具有现有诊断的患者,则会失败。

MATCH (n:Patient)
 WHERE n.ssn = 1234610
DETACH DELETE n;
org.neo4j.graphdb.ConstraintViolationException: Cannot delete node<42>, because it still has relationships. To delete this node, you must first delete its relationships.

此查询失败的原因是,虽然 Bob 可以找到 (:Patient) 节点,但他们没有足够的遍历权限来查找或删除其传出的关系。他们要么需要请求 Tina(itadmin)帮助完成此任务,要么可以向 receptionist 角色添加更多权限。

GRANT TRAVERSE ON GRAPH healthcare NODES Symptom, Disease TO receptionist;
GRANT TRAVERSE ON GRAPH healthcare RELATIONSHIPS HAS, DIAGNOSIS TO receptionist;
GRANT DELETE ON GRAPH healthcare RELATIONSHIPS HAS, DIAGNOSIS TO receptionist;

先前授予或拒绝的权限可以使用REVOKE命令撤消。

nurse 的权限

护士应该同时具备医生和接待员的能力,但是为他们分配 doctorreceptionist 角色可能无法达到预期的效果。如果这两个角色仅使用 GRANT 权限创建,则组合它们将仅仅是累加的。但是,如果 doctor 角色包含一些 DENY 权限,则这些权限始终会覆盖 GRANT。这意味着护士仍然会与医生一样受到相同的限制,这并非此处预期的结果。

为了演示这一点,您可以将 doctor 角色分配给护士 Daniel。

neo4j@system> GRANT ROLE doctor, receptionist TO daniel;

Daniel 现在应该拥有组合的权限集。

SHOW USER daniel PRIVILEGES AS COMMANDS;
+---------------------------------------------------------------------------+
| command                                                                   |
+---------------------------------------------------------------------------+
| "GRANT ACCESS ON HOME DATABASE TO $role"                                  |
| "GRANT ACCESS ON DATABASE `healthcare` TO $role"                          |
| "GRANT EXECUTE PROCEDURE * ON DBMS TO $role"                              |
| "GRANT EXECUTE FUNCTION * ON DBMS TO $role"                               |
| "GRANT LOAD ON ALL DATA TO $role"                                         |
| "GRANT TRAVERSE ON GRAPH `healthcare` NODE * TO $role"                    |
| "GRANT TRAVERSE ON GRAPH `healthcare` RELATIONSHIP * TO $role"            |
| "GRANT READ {*} ON GRAPH `healthcare` NODE * TO $role"                    |
| "GRANT READ {*} ON GRAPH `healthcare` RELATIONSHIP * TO $role"            |
| "GRANT MATCH {*} ON GRAPH `healthcare` NODE Patient TO $role"             |
| "GRANT WRITE ON GRAPH `healthcare` TO $role"                              |
| "GRANT SET PROPERTY {*} ON GRAPH `healthcare` NODE Patient TO $role"      |
| "GRANT CREATE ON GRAPH `healthcare` NODE Patient TO $role"                |
| "GRANT DELETE ON GRAPH `healthcare` NODE Patient TO $role"                |
| "DENY READ {address} ON GRAPH `healthcare` NODE Patient TO $role"         |
| "DENY SET PROPERTY {address} ON GRAPH `healthcare` NODE Patient TO $role" |
+---------------------------------------------------------------------------+

先前授予或拒绝的权限可以使用REVOKE命令撤消。

现在,目的是让护士能够执行接待员的操作,这意味着他们应该能够读取和写入 Patient 节点的 address 字段。为此,护士可以运行以下查询。

MATCH (n:Patient)
 WHERE n.dateOfBirth < date('1972-06-12')
RETURN n.name, n.ssn, n.address, n.dateOfBirth;

这将返回这些结果。

+-------------------------------------------------------+
| n.name          | n.ssn   | n.address | n.dateOfBirth |
+-------------------------------------------------------+
| "Jane Anderson" | 1234572 | NULL      | 1971-05-26    |
| "Mark Stone"    | 1234586 | NULL      | 1972-06-07    |
| "Joe Smith"     | 1234595 | NULL      | 1970-12-28    |
| "Joe Jackson"   | 1234603 | NULL      | 1970-08-31    |
| "Jane Jackson"  | 1234628 | NULL      | 1972-01-31    |
| "Mary Anderson" | 1234632 | NULL      | 1971-01-07    |
| "Jack Svensson" | 1234639 | NULL      | 1970-01-06    |
+-------------------------------------------------------+

如预期的那样,address 字段对护士不可见。这是因为,如前所述,DENY 权限始终会覆盖 GRANT。由于 doctorreceptionist 两个角色都已分配给护士,因此 doctor 角色的 DENIED 权限正在覆盖 receptionist 角色的 GRANTED 权限。即使护士尝试写入地址字段,他们也会收到错误,这不是这里期望的结果。为了纠正这一点,您可以

  • 仅使用授予权限重新定义 doctor 角色,并定义医生应该能够读取的每个 Patient 属性。

  • 使用实际的预期行为重新定义 nurse 角色。

如果您认为护士本质上是没有任何 address 限制的医生,则第二个选项更简单。在这种情况下,您需要从头开始创建一个 nurse 角色。

CREATE ROLE nurse
GRANT TRAVERSE ON GRAPH healthcare TO nurse;
GRANT READ {*} ON GRAPH healthcare TO nurse;
GRANT WRITE ON GRAPH healthcare TO nurse;

现在,您将 nurse 角色分配给护士 Daniel,但请记住撤销 doctorreceptionist 角色,以便没有权限被覆盖。

REVOKE ROLE doctor FROM daniel;
REVOKE ROLE receptionist FROM daniel;
GRANT ROLE nurse TO daniel;

这次,当护士 Daniel 再次查看患者记录时,他们将看到 address 字段。

MATCH (n:Patient)
 WHERE n.dateOfBirth < date('1972-06-12')
RETURN n.name, n.ssn, n.address, n.dateOfBirth;
+----------------------------------------------------------------------+
| n.name          | n.ssn   | n.address                | n.dateOfBirth |
+----------------------------------------------------------------------+
| "Jane Anderson" | 1234572 | "1 secret way, downtown" | 1971-05-26    |
| "Mark Stone"    | 1234586 | "1 secret way, downtown" | 1972-06-07    |
| "Joe Smith"     | 1234595 | "1 secret way, downtown" | 1970-12-28    |
| "Joe Jackson"   | 1234603 | "1 secret way, downtown" | 1970-08-31    |
| "Jane Jackson"  | 1234628 | "1 secret way, downtown" | 1972-01-31    |
| "Mary Anderson" | 1234632 | "1 secret way, downtown" | 1971-01-07    |
| "Jack Svensson" | 1234639 | "1 secret way, downtown" | 1970-01-06    |
+----------------------------------------------------------------------+

nurse 角色应该能够执行的另一个主要操作是保存诊断到数据库中的主要 doctor 操作。

WITH date(datetime({epochmillis:timestamp()})) AS today
MATCH (p:Patient)
  WHERE p.ssn = 1234657
MATCH (d:Disease)
  WHERE d.name = "Chronic Placeboitis"
MERGE (p)-[i:DIAGNOSIS {by: 'Daniel'}]->(d)
  ON CREATE SET i.date = today
RETURN p.name, d.name, i.by, i.date;
+------------------------------------------------------------------+
| p.name           | d.name                | i.by     | i.date     |
+------------------------------------------------------------------+
| "Sally Anderson" | "Chronic Placeboitis" | "Daniel" | 2020-05-29 |
+------------------------------------------------------------------+

执行此操作(否则保留给 doctor 角色)会给 nurse 带来更多责任。可能有些护士不应该被委托此选项,这就是为什么例如可以将 nurse 角色划分为高级初级护士的原因。目前,Daniel 是一名高级护士。

初级 nurse 的权限

之前,通过组合 doctorreceptionist 角色创建 nurse 角色导致了不希望出现的情况,因为 doctor 角色的 DENIED 权限覆盖了 receptionist 角色的 GRANTED 权限。在这种情况下,目标是增强高级护士的权限,但是当涉及到初级护士时,他们应该能够执行与高级护士相同的操作,除了向数据库添加诊断。

为了实现这一点,您可以创建一个专门包含附加限制的角色。

CREATE ROLE disableDiagnoses;
DENY CREATE ON GRAPH healthcare RELATIONSHIPS DIAGNOSIS TO disableDiagnoses;

然后将此新角色分配给护士 Daniel,以便您可以测试行为。

GRANT ROLE disableDiagnoses TO daniel;

如果您检查 Daniel 现在拥有的权限,它将是 nursedisableDiagnoses 两个角色的组合。

neo4j@system> SHOW USER daniel PRIVILEGES AS COMMANDS;
+---------------------------------------------------------------------+
| command                                                             |
+---------------------------------------------------------------------+
| "GRANT ACCESS ON HOME DATABASE TO $role"                            |
| "GRANT ACCESS ON DATABASE `healthcare` TO $role"                    |
| "GRANT EXECUTE PROCEDURE * ON DBMS TO $role"                        |
| "GRANT EXECUTE FUNCTION * ON DBMS TO $role"                         |
| "GRANT LOAD ON ALL DATA TO $role"                                   |
| "GRANT TRAVERSE ON GRAPH `healthcare` NODE * TO $role"              |
| "GRANT TRAVERSE ON GRAPH `healthcare` RELATIONSHIP * TO $role"      |
| "GRANT READ {*} ON GRAPH `healthcare` NODE * TO $role"              |
| "GRANT READ {*} ON GRAPH `healthcare` RELATIONSHIP * TO $role"      |
| "GRANT WRITE ON GRAPH `healthcare` TO $role"                        |
| "DENY CREATE ON GRAPH `healthcare` RELATIONSHIP DIAGNOSIS TO $role" |
+---------------------------------------------------------------------+

Daniel 仍然可以看到地址字段,甚至可以执行 doctor 可以执行的诊断调查。

MATCH (n:Patient)-[:HAS]->(s:Symptom)-[:OF]->(d:Disease)
WHERE n.ssn = 1234650
RETURN n.ssn, n.name, d.name, count(s) AS score ORDER BY score DESC;
+--------------------------------------------------------+
| n.ssn   | n.name       | d.name                | score |
+--------------------------------------------------------+
| 1234650 | "Mark Smith" | "Chronic Whatitis"    | 3     |
| 1234650 | "Mark Smith" | "Chronic Someitis"    | 3     |
| 1234650 | "Mark Smith" | "Acute Someitis"      | 2     |
| 1234650 | "Mark Smith" | "Chronic Otheritis"   | 2     |
| 1234650 | "Mark Smith" | "Chronic Yellowitis"  | 2     |
| 1234650 | "Mark Smith" | "Chronic Placeboitis" | 2     |
| 1234650 | "Mark Smith" | "Acute Otheritis"     | 2     |
| 1234650 | "Mark Smith" | "Chronic Argitis"     | 2     |
| 1234650 | "Mark Smith" | "Acute Placeboitis"   | 2     |
| 1234650 | "Mark Smith" | "Acute Yellowitis"    | 1     |
| 1234650 | "Mark Smith" | "Acute Argitis"       | 1     |
| 1234650 | "Mark Smith" | "Acute Whatitis"      | 1     |
+--------------------------------------------------------+

但是,当他们尝试将诊断保存到数据库时,将被拒绝该操作。

WITH date(datetime({epochmillis:timestamp()})) AS today
MATCH (p:Patient)
  WHERE p.ssn = 1234650
MATCH (d:Disease)
  WHERE d.name = "Chronic Placeboitis"
MERGE (p)-[i:DIAGNOSIS {by: 'Daniel'}]->(d)
  ON CREATE SET i.date = today
RETURN p.name, d.name, i.by, i.date;
Create relationship with type 'DIAGNOSIS' is not allowed for user 'daniel' with roles [PUBLIC, disableDiagnoses, nurse].

要将 Daniel 提升回高级护士,请撤销引入限制的角色。

REVOKE ROLE disableDiagnoses FROM daniel;

构建自定义管理员角色

itadmin 角色最初是通过复制内置的 admin 角色并添加限制来创建的。但是,在某些情况下,仅拥有 GRANT 比拥有 DENY 更方便。相反,您可以从头开始构建管理员角色。

IT 管理员 Tina 能够创建新用户并将其分配到产品角色作为 itadmin,但您可以创建一个更受限制的角色,称为 userManager,并仅授予其相应的权限。

CREATE ROLE userManager;
GRANT USER MANAGEMENT ON DBMS TO userManager;
GRANT ROLE MANAGEMENT ON DBMS TO userManager;
GRANT SHOW PRIVILEGE ON DBMS TO userManager;

通过撤销 Tina 的 itadmin 角色并改为授予她 userManager 角色来测试新行为。

REVOKE ROLE itadmin FROM tina
GRANT ROLE userManager TO tina

这些是授予 userManager 的权限。

  • USER MANAGEMENT 允许创建、更新和删除用户。

  • ROLE MANAGEMENT 允许创建、更新和删除角色,以及将角色分配给用户。

  • SHOW PRIVILEGE 允许列出用户的权限。

列出 Tina 的新权限现在应该显示比她拥有更强大的 itadmin 角色管理员权限时短得多的列表。

neo4j@system> SHOW USER tina PRIVILEGES AS COMMANDS;
+--------------------------------------------------+
| command                                          |
+--------------------------------------------------+
| "GRANT ACCESS ON HOME DATABASE TO $role"         |
| "GRANT ACCESS ON DATABASE `healthcare` TO $role" |
| "GRANT EXECUTE PROCEDURE * ON DBMS TO $role"     |
| "GRANT EXECUTE FUNCTION * ON DBMS TO $role"      |
| "GRANT LOAD ON ALL DATA TO $role"                |
| "GRANT ROLE MANAGEMENT ON DBMS TO $role"         |
| "GRANT USER MANAGEMENT ON DBMS TO $role"         |
| "GRANT SHOW PRIVILEGE ON DBMS TO $role"          |
+--------------------------------------------------+

此处未授予任何其他权限管理权限。此角色应该拥有多少权力将取决于系统的需求。有关要考虑的完整权限列表,请参阅admin 角色部分。

现在,Tina 应该能够创建新用户并将他们分配到角色。

CREATE USER sally SET PASSWORD 'secretpassword' CHANGE REQUIRED;
GRANT ROLE receptionist TO sally;
SHOW USER sally PRIVILEGES AS COMMANDS;
+----------------------------------------------------------------------+
| command                                                              |
+----------------------------------------------------------------------+
| "GRANT ACCESS ON HOME DATABASE TO $role"                             |
| "GRANT ACCESS ON DATABASE `healthcare` TO $role"                     |
| "GRANT EXECUTE PROCEDURE * ON DBMS TO $role"                         |
| "GRANT EXECUTE FUNCTION * ON DBMS TO $role"                          |
| "GRANT MATCH {*} ON GRAPH `healthcare` NODE Patient TO $role"        |
| "GRANT LOAD ON ALL DATA TO $role"                                    |
| "GRANT SET PROPERTY {*} ON GRAPH `healthcare` NODE Patient TO $role" |
| "GRANT CREATE ON GRAPH `healthcare` NODE Patient TO $role"           |
| "GRANT DELETE ON GRAPH `healthcare` NODE Patient TO $role"           |
+----------------------------------------------------------------------+