Setting permissions for a curator role

The following example shows how to create and set permissions for a curator role that is allowed to insert, update, or delete any entity apart from a set of RecordTypes and properties that define a “core data model” which can only be altered with administration permissions.

In the following, you’ll learn how to

  1. create the curator role.

  2. configure the global_entity_permissions.xml s.th. the curator role is allowed to insert, update, or delete any entity by default.

  3. use a Python script to override the above configuration for the entities in the externally defined core data model.

Prerequisites

This example needs some preparations regarding your LinkAhead setup that have to (or, for the sake of simplicity, should) be done outside the actual Python example script.

The curator role

First, a curator role is created with a meaningful description. We’ll use linkahead_admin.py for this which leads to the following command:

$ linkahead_admin.py create_role "curator" "A user who is permitted to create new Records, Properties, and RecordTypes but who is not allowed to change the core data model."

To actually see how this role’s permissions change, we also need a user with this role. Assume you already have created and activated (see Administration) a test_curator user, then linkahead_admin.py is used again to assign it the correct role:

$ linkahead_admin.py add_user_roles test_curator curator

Note

The test_curator user shouldn’t have administration privileges, otherwise the below changes won’t have any effect.

The core data model and linkahead-advanced-user-tools

In principle, the following script works with any data model defined in a json or yaml file (just adapt lines 39-42 accordingly). In this example, we’ll use the metadata schema that was developed by J. Schmidt at the Leibniz Centre for Tropical Marine Research.

Clone the schemata into the same directory containing the below script via

$ git clone https://github.com/leibniz-zmt/zmt-metadata-schema.git

Furthermore, we’ll need the LinkAhead Advanced User Tools for loading the metadata schemata from the json files, so install them via

$ pip install caosadvancedtools

The global entity permissions file

Users with the curator role should be able to have any permission for all entities by default. The exceptions for the core data model entities will be set with the script below. These default settings are best done via the global_entities_permissions.xml config file (see the server documentation). Simply add the following line to the file

<Grant priority="true" role="curator"><Permission name="*"/></Grant>

This means that, by default, all users with the curator role are granted all entity permissions (including insert, update, and delete as specified in the beginning) with priority. This ensures, that no normal user is allowed to overrule these permissions (since it is granted with priority), but it can still be denied for the core data model entities by a deny rule with priority. See the server documentation on permission calculation for more information on which permission rules can or can’t be overruled.

Your complete global_entities_permissions.xml might then look like

<globalPermissions>
    <Grant priority="false" role="?OWNER?"><Permission name="*"/></Grant>
    <Grant priority="false" role="?OTHER?"><Permission name="RETRIEVE:*"/></Grant>
    <Grant priority="false" role="?OTHER?"><Permission name="USE:*"/></Grant>
    <Grant priority="false" role="anonymous"><Permission name="RETRIEVE:*"/></Grant>
    <Grant priority="true" role="curator"><Permission name="*"/></Grant>
    <Deny priority="false" role="?OTHER?"><Permission name="UPDATE:*"/></Deny>
    <Deny priority="false" role="?OTHER?"><Permission name="DELETE"/></Deny>
    <Deny priority="true" role="?OTHER?"><Permission name="EDIT:ACL"/></Deny>
</globalPermissions>

Note

Note that you have to restart your LinkAhead server after modifying the global_entities_permissions.xml.

The code

After having applied all of the above prerequisites and restarting your LinkAhead server, execute the following code.

Download full code

#!/usr/bin/env python3
# encoding: utf-8
#
# This file is a part of the LinkAhead Project.
#
# Copyright (C) 2022 Indiscale GmbH <info@indiscale.com>
# Copyright (C) 2022 Florian Spreckelsen <f.spreckelsen@indiscale.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
#

import os
import sys

import linkahead as db
from caosadvancedtools.models.parser import parse_model_from_json_schema
from linkahead import administration as admin

CURATOR = "curator"


def main():
    """Set curator role permissions: Is allowed to edit all Records; is allowed
    to create new RTs and Properties and change them, but is not allowed to
    change anything defined in the core data model, i.e., in the schemas.

    """
    dataspace_definitions = parse_model_from_json_schema(
        "zmt-metadata-schema/schemas/dataspace.schema.json")
    dataset_definitions = parse_model_from_json_schema(
        "zmt-metadata-schema/schemas/dataset.schema.json")

    # Set general permissions. The curator users should be allowed to perform
    # any transaction.
    perms = admin._get_permissions(CURATOR)
    general_grant_perms = [
        "TRANSACTION:*"
    ]

    for p in general_grant_perms:

        g = admin.PermissionRule(action="Grant", permission=p, priority=True)
        d = admin.PermissionRule(action="Deny", permission=p, priority=True)

        if g in perms:
            perms.remove(g)
        if d in perms:
            perms.remove(d)
        perms.add(g)

    admin._set_permissions(CURATOR, permission_rules=perms)

    # Deny all permissions that could change the data model ...
    core_model_deny_permissions = [
        "DELETE",
        "UPDATE:*",
        "EDIT:ACL"
    ]
    # ... but allow read-access and of course using the entities as parents,
    # properties, ...
    core_model_grant_permissions = [
        "RETRIEVE:*",
        "USE:*",
    ]

    # Iterate over all entities defined in the schemas and update their access control list (ACL) accordingly.
    updates = db.Container()
    for model in [dataspace_definitions, dataset_definitions]:

        for ent in model.values():
            if ent.name in [u.name for u in updates]:
                # Skip entities that have been updated already
                continue
            # The entity needs to be retrieved with the ACL flag to update the
            # ACL down the road
            ent.retrieve(flags={"ACL": None})
            for d in core_model_deny_permissions:
                ent.deny(role=CURATOR, priority=True, permission=d)
            ent.update_acl()
            ent.retrieve(flags={"ACL": None})
            for g in core_model_grant_permissions:
                ent.grant(role=CURATOR, priority=True, permission=g)
            updates.append(ent)
            ent.update_acl()


if __name__ == "__main__":

    sys.exit(main())