Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Group permissions manager #107

Merged
merged 45 commits into from
Feb 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
7c00d81
add core functionality for group permissions manager
philtweir Dec 2, 2023
16412dd
add the group permission manager and related models
philtweir Dec 3, 2023
ddd0537
ensure CELERY url is set
philtweir Dec 6, 2023
a64392e
add permissions concept
philtweir Dec 8, 2023
21bbc59
link in permission concept
philtweir Dec 8, 2023
ad4210b
add logical sets
philtweir Dec 10, 2023
f44a888
update docker
philtweir Dec 10, 2023
60c214f
add arches-orm into requirement - tracking master for now, until semi…
philtweir Dec 10, 2023
fd247b7
add git - ideally we strip this out post-dev
philtweir Dec 10, 2023
a29e0df
enforce startup db wait
philtweir Dec 11, 2023
c57ffe9
adding docstrings
philtweir Dec 11, 2023
244d9c7
update arches_orm
philtweir Dec 11, 2023
ce71c9f
add django-authorization
philtweir Dec 12, 2023
d371055
install all deps during static build also
philtweir Dec 12, 2023
02c1094
install all deps during static build also
philtweir Dec 12, 2023
6047658
build static directly on dynamic container
philtweir Dec 12, 2023
ec61198
build static directly on dynamic container
philtweir Dec 12, 2023
586c228
leave service to setup its own DB
philtweir Dec 12, 2023
dca4678
tidy up to work with arches-orm refactor
philtweir Dec 17, 2023
8fb68b4
arches_orm references at the top level in views cause the database to…
philtweir Dec 17, 2023
bdfcd87
full_name no longer present at top level
philtweir Dec 17, 2023
828b786
lay groundwork for anonymous sets
philtweir Dec 17, 2023
673c738
up timeout for ES
philtweir Dec 17, 2023
4233dcf
fix group assignment
philtweir Dec 17, 2023
0677fff
fix group assignment
philtweir Dec 17, 2023
98aed12
make tasks async
philtweir Dec 17, 2023
c24b48b
groundwork for linking django groups to groups
philtweir Dec 19, 2023
958d881
add django groups back in for non-resource specific permissions
philtweir Dec 21, 2023
06ca944
update readme
philtweir Dec 22, 2023
50ca100
allow use of a view-all permission framework for logged-in users
philtweir Dec 23, 2023
5c16ab9
speed up permission checking
philtweir Dec 27, 2023
912f7d6
remove envfile
philtweir Dec 27, 2023
d8cda2b
keep toolbar for consistency
philtweir Dec 27, 2023
c7046da
turn off debug
philtweir Dec 27, 2023
a7c619f
align DJANGO_DEBUG and DEBUG
philtweir Dec 27, 2023
2503ff1
build for prod
philtweir Dec 27, 2023
1fc79e6
keep toolbar for consistency
philtweir Dec 27, 2023
1f3d38f
reduce size of permission table
philtweir Dec 28, 2023
dac5f65
update location
philtweir Dec 28, 2023
1e81310
make sure graphid is a string when filtering as an admin
philtweir Jan 13, 2024
855c0cc
watch for casbin reload requests on all processes
philtweir Jan 13, 2024
9a5559d
update docker definitions
philtweir Jan 13, 2024
6d047bf
ensure static apt is updated
philtweir Jan 13, 2024
ebc567a
cover case where a user creates a resource of their own, introducing …
philtweir Jan 23, 2024
32f5ee7
cannot load resources before setting up django
philtweir Jan 23, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions .github/workflows/project.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name: Build Arches project
run-name: Build Arches project
on: [push]
env:
ARCHES_BASE: ghcr.io/flaxandteal/arches-base-7.5-dev:feature-django-casbin--testing
ARCHES_BASE: ghcr.io/flaxandteal/arches-base-7.5-dev:feature-django-casbin
ARCHES_PROJECT: coral
jobs:
Build-Arches:
Expand Down Expand Up @@ -189,8 +189,8 @@ jobs:
ESHOST: "elasticsearch"
ESPORT: "9200"
CELERY_BROKER_URL: "amqp://rabbitmq"
DJANGO_MODE: "DEV"
DJANGO_DEBUG: "True"
DJANGO_MODE: "PROD"
DJANGO_DEBUG: "False"
DOMAIN_NAMES: "localhost"
PYTHONUNBUFFERED: "0"
STATIC_URL: "/static/"
Expand Down Expand Up @@ -358,8 +358,8 @@ jobs:
ESHOST: "elasticsearch"
ESPORT: "9200"
CELERY_BROKER_URL: "amqp://rabbitmq"
DJANGO_MODE: "DEV"
DJANGO_DEBUG: "True"
DJANGO_MODE: "PROD"
DJANGO_DEBUG: "False"
DOMAIN_NAMES: "arches"
PYTHONUNBUFFERED: "0"
TZ: "PST"
Expand Down
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ TOOLKIT_REPO = https://github.com/flaxandteal/arches-container-toolkit
TOOLKIT_FOLDER = docker
TOOLKIT_RELEASE = main
ARCHES_PROJECT ?= $(shell ls -1 */__init__.py | head -n 1 | sed 's/\/.*//g')
ARCHES_BASE = ghcr.io/flaxandteal/arches-base-7.5-dev:feature-django-casbin--testing
ARCHES_BASE = ghcr.io/flaxandteal/arches-base-7.5-dev:feature-django-casbin
ARCHES_PROJECT_ROOT = $(shell pwd)/
DOCKER_COMPOSE_COMMAND = ARCHES_PROJECT_ROOT=$(ARCHES_PROJECT_ROOT) ARCHES_BASE=$(ARCHES_BASE) ARCHES_PROJECT=$(ARCHES_PROJECT) docker-compose -p $(ARCHES_PROJECT) -f docker/docker-compose.yml
CMD ?=
Expand Down
9 changes: 5 additions & 4 deletions README.txt
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
# Welcome to the Arches Project!
# Coral Arches

Arches is a new, open-source, web-based, geospatial information system for cultural heritage inventory and management. Arches is purpose-built for the international cultural heritage field, and it is designed to record all types of immovable heritage, including archaeological sites, buildings and other historic structures, landscapes, and heritage ensembles or districts.
This Arches implementation is designed to be run with Kubernetes and Dockerized.

Please see the [project page](http://archesproject.org/) for more information on the Arches project.
In addition, it uses Casbin permissions and a patched version of Arches.

The Arches Installation Guide and Arches User Guide are available [here](http://archesproject.org/documentation/).
This is in-development software: no warranty is made for open source use as regards
the reliability, consistency, security or stability at this point.
64 changes: 64 additions & 0 deletions coral/datatypes/user.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
"""User data type for associating with models.

Datatype to extend possible node values to Django users.
"""


import logging

from arches.app.datatypes.base import BaseDataType
from arches.app.models.models import Widget, Node
from arches.app.models.tile import Tile
from django.contrib.auth.models import User

text: Widget = Widget.objects.get(name="user")

details: dict[str, str | Widget | bool | None] = {
"datatype": "user",
"iconclass": "fa fa-location-arrow",
"modulename": "user.py",
"classname": "UserDataType",
"defaultwidget": text,
"defaultconfig": None,
"configcomponent": None,
"configname": None,
"isgeometric": False,
"issearchable": False,
}

logger: logging.Logger = logging.getLogger(__name__)

class UserDataType(BaseDataType):
"""DataType for a Django User."""

def append_to_document(self, document, nodevalue, nodeid, tile, provisional=False):
document["strings"].append(
{"string": nodevalue, "nodegroup_id": tile.nodegroup_id}
)

def get_search_terms(self, nodevalue, nodeid=None):
if nodevalue:
user = User.objects.get(pk=int(nodevalue))
return [user.email]
return []

def get_display_value(self, tile, node, **kwargs):
if (user := self.get_user(tile, node)):
return user.email
return None

def get_user(self, tile: Tile, node: Node) -> User:
data = self.get_tile_data(tile)
if data:
raw_value = data.get(str(node.nodeid))
if raw_value is not None:
user = User.objects.get(pk=int(raw_value))
return user

def compile_json(self, tile, node, **kwargs):
json = super().compile_json(tile, node, **kwargs)
data = self.get_tile_data(tile)
if data:
raw_value = data.get(str(node.nodeid))
json["userId"] = int(raw_value)
return json
56 changes: 56 additions & 0 deletions coral/management/commands/apply_sets.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
"""
ARCHES - a program developed to inventory and manage immovable cultural heritage.
Copyright (C) 2013 J. Paul Getty Trust and World Monuments Fund

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 <http://www.gnu.org/licenses/>.
"""

import logging
import readline
from django.core.management.base import BaseCommand
from django.contrib.auth.models import User, Group as DjangoGroup

from coral import settings

logging.basicConfig()

class Command(BaseCommand):
"""Recalculate ES resource->set mapping.

"""

print_statistics = False

def add_arguments(self, parser):
parser.add_argument(
"-s",
"--statistics",
action="store_true",
dest="print_statistics",
help="Do extra searches to provide relevant statistics?",
)


def handle(self, *args, **options):
# Cannot be imported until Django ready
from coral.permissions.casbin import CasbinPermissionFramework
from coral.utils.casbin import SetApplicator

print_statistics = True if options["print_statistics"] else False

set_applicator = SetApplicator(print_statistics=print_statistics, wait_for_completion=True)
set_applicator.apply_sets()

framework = CasbinPermissionFramework()
framework.recalculate_table()
152 changes: 152 additions & 0 deletions coral/management/commands/print_permissions_table.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
"""
ARCHES - a program developed to inventory and manage immovable cultural heritage.
Copyright (C) 2013 J. Paul Getty Trust and World Monuments Fund

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 <http://www.gnu.org/licenses/>.
"""

import logging
import readline
from django.core.management.base import BaseCommand
from django.contrib.auth.models import User, Group as DjangoGroup
from arches.app.utils.permission_backend import assign_perm
from arches.app.models.system_settings import settings
from arches.app.models.resource import Resource
from arches_orm.utils import attempt_well_known_resource_model
from arches_orm.models import Group, Set
from coral.permissions.casbin import CasbinPermissionFramework

logging.basicConfig()

class Command(BaseCommand):
"""
Commands for adding arches test users

"""

def add_arguments(self, parser):
... # parser.add_argument("operation", nargs="?")

def handle(self, *args, **options):
table = self.get_table()

def get_table(self):
framework = CasbinPermissionFramework()
enforcer = framework._enforcer
enforcer.load_policy()
logging.disable(logging.NOTSET)
enforcer.model.logger.setLevel(logging.INFO)
framework.recalculate_table()
enforcer.model.print_policy()
print(enforcer.get_implicit_permissions_for_user("u:310"))
#group_tree = {}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove debug

#set_tree = {}
#group_x_set = []
#att = lambda key: attempt_well_known_resource_model(key.split(":", 1)[1])
#for key, ast in enforcer.model["p"].items():
# for policy in ast.policy:
# if isinstance(policy, list) and len(policy) == 3:
# group_x_set.append((att(policy[0]), att(policy[1]), Group.make_concept(policy[2])))

#print("GROUP === ACTION ===> SET")
#for grp, st, act in group_x_set:
# print(grp.name, "----", act.text, "---->", st.title_text)

#for sec in ["g"]:
# if sec not in enforcer.model.keys():
# print("Sec not found", sec)
# continue

# for key, ast in enforcer.model[sec].items():
# for policy in ast.policy:
# prnt = att(policy[1])
# group_tree.setdefault(prnt.id, {"children": [], "ri": prnt, "has_parent": False})
# group_tree[prnt.id].setdefault("children", [])
# prefix = policy[0].split(":", 1)[0]
# if prefix in ("ri", "g", "g2", "g2l"):
# try:
# chld = att(policy[0])
# except Resource.DoesNotExist:
# logging.error("Missing %s", policy[0])
# else:
# group_tree.setdefault(chld.id, {"ri": chld})
# group_tree[chld.id]["has_parent"] = True
# group_tree[prnt.id]["children"].append(group_tree[chld.id])
# elif prefix == "u":
# group_tree[prnt.id]["children"].append(User.objects.get(pk=int(policy[0][2:])))
# else:
# group_tree[prnt.id]["children"].append(policy[0])
#def _print_children(node, level):
# for child in node["children"]:
# if isinstance(child, dict):
# print(" " * 2 * level, child["ri"]._model_name, str(child["ri"]))
# if "children" in child:
# _print_children(child, level + 1)
# elif child["ri"]._model_name not in ("Group", "Set"):
# print(" " * 2 * level, "\_", child["ri"].id)
# elif isinstance(child, User):
# print(" " * 2 * level, "User:", child.email)
# else:
# print(" " * 2 * level, "Unknown:", child)

#print()

#for root in (node for node in group_tree.values() if not node["has_parent"]):
# print("Root:", root["ri"]._model_name, str(root["ri"]))
# _print_children(root, 1)

#last_user = None
#last_permission = None
#last_entity = None
#while (user := input("User: ")) != "q":
# if not user:
# if last_user:
# user = last_user
# else:
# break
# else:
# try:
# user = User.objects.get(email__startswith=user)
# except User.MultipleObjectsReturned:
# print("[Found multiple matches, try again]")
# continue
# print(user, user.email, user.id)
# print(user.is_authenticated)

# entity = input("Entity: ")
# if not entity:
# if last_entity:
# entity = last_entity
# else:
# break
# else:
# try:
# entity = Resource.objects.get(resourceinstanceid__startswith=entity)
# except User.MultipleObjectsReturned:
# print("[Found multiple matches, try again]")
# continue
# ri = attempt_well_known_resource_model(entity.pk)

# permission = input("Permission: ")
# if not permission:
# if last_permission:
# permission = last_permission
# else:
# break
# result = framework.check_resource_instance_permissions(user, ri.id, permission)
# print(result)

# last_user = user
# last_permission = permission
# last_entity = entity
29 changes: 29 additions & 0 deletions coral/media/js/bindings/cytoscape-elk.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
define([
'knockout',
'underscore',
'cytoscape',
'cytoscape-elk'
], function(ko, _, cytoscape, elk) {
cytoscape.use(elk);
ko.bindingHandlers.cytoscape = {
init: function(element, valueAccessor) {
var defaults = {
container: element
};
var config = ko.unwrap(valueAccessor()).config || {};

var viz = cytoscape(
_.defaults(ko.unwrap(config), defaults)
);

ko.utils.domNodeDisposal.addDisposeCallback(element, function() {
viz.destroy();
}, this);

if (typeof ko.unwrap(valueAccessor()).afterRender === 'function') {
ko.unwrap(valueAccessor()).afterRender(viz);
}
},
};
return ko.bindingHandlers.cytoscape;
});
Loading
Loading