about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--.guix-channel2
-rw-r--r--gn_libs/jobs/launcher.py2
-rw-r--r--gn_libs/monadic_requests.py2
-rw-r--r--gn_libs/privileges/__init__.py6
-rw-r--r--gn_libs/privileges/authspec.py (renamed from gn_libs/privileges.py)11
-rw-r--r--gn_libs/privileges/resources.py74
-rw-r--r--gn_libs/privileges/system.py18
-rw-r--r--gn_libs/sqlite3.py11
8 files changed, 123 insertions, 3 deletions
diff --git a/.guix-channel b/.guix-channel
index 63a4336..bfc31db 100644
--- a/.guix-channel
+++ b/.guix-channel
@@ -34,7 +34,7 @@
   (channel
    (name guix-bioinformatics)
    (url "https://git.genenetwork.org/guix-bioinformatics")
-   (commit "903465c85c9b2ae28480b236c3364da873ca8f51"))
+   (commit "9b0955f14ec725990abb1f6af3b9f171e4943f77"))
   (channel
    (name guix-past)
    (url "https://codeberg.org/guix-science/guix-past")
diff --git a/gn_libs/jobs/launcher.py b/gn_libs/jobs/launcher.py
index f915b81..fd171b8 100644
--- a/gn_libs/jobs/launcher.py
+++ b/gn_libs/jobs/launcher.py
@@ -41,7 +41,7 @@ def run_job(conn, job, outputs_directory: Path):
                 if bool(_job["metadata"].get("hangup_request")):
                     process.send_signal(signal.SIGHUP)
                     jobs.update_metadata(conn, job_id, "status", "stopped")
-                    break;
+                    break
                 jobs.push_to_stream(conn, job_id, "stdout", stdout_in.read())
                 jobs.push_to_stream(conn, job_id, "stderr", stderr_in.read())
                 time.sleep(1)
diff --git a/gn_libs/monadic_requests.py b/gn_libs/monadic_requests.py
index a09acc5..1db1aa0 100644
--- a/gn_libs/monadic_requests.py
+++ b/gn_libs/monadic_requests.py
@@ -26,6 +26,7 @@ def get(url, params=None, **kwargs) -> Either:
 
     try:
         resp = requests.get(url, params=params, timeout=timeout, **kwargs)
+        resp.raise_for_status()
         if resp.status_code in SUCCESS_CODES:
             return Right(resp.json())
         return Left(resp)
@@ -48,6 +49,7 @@ def post(url, data=None, json=None, **kwargs) -> Either:
 
     try:
         resp = requests.post(url, data=data, json=json, timeout=timeout, **kwargs)
+        resp.raise_for_status()
         if resp.status_code in SUCCESS_CODES:
             return Right(resp.json())
         return Left(resp)
diff --git a/gn_libs/privileges/__init__.py b/gn_libs/privileges/__init__.py
new file mode 100644
index 0000000..0cab6b3
--- /dev/null
+++ b/gn_libs/privileges/__init__.py
@@ -0,0 +1,6 @@
+"""This package contains code useful for checking privileges."""
+from .authspec import (check,
+                       parse,
+                       SpecificationValueError,
+                       privileges_fulfill_specs)
+from . import resources
diff --git a/gn_libs/privileges.py b/gn_libs/privileges/authspec.py
index 32c943d..2819f9d 100644
--- a/gn_libs/privileges.py
+++ b/gn_libs/privileges/authspec.py
@@ -164,3 +164,14 @@ def check(spec: str, privileges: tuple[str, ...]) -> bool:
     """Check that the sequence of `privileges` satisfies `spec`."""
     _spec = parse(spec)
     return _OPERATOR_FUNCTION_[_spec[0]](privileges, *_spec[1:])
+
+
+def privileges_fulfill_specs(
+        resource_privileges: tuple[str, ...],
+        system_privileges: tuple[str, ...],
+        resource_spec: str,
+        system_spec: str
+) -> bool:
+    """Check whether a user's privileges fulfill the given specs."""
+    return (check(resource_spec, resource_privileges) or
+            check(system_spec, system_privileges))
diff --git a/gn_libs/privileges/resources.py b/gn_libs/privileges/resources.py
new file mode 100644
index 0000000..217a57d
--- /dev/null
+++ b/gn_libs/privileges/resources.py
@@ -0,0 +1,74 @@
+"""Privilege checks for resources"""
+import logging
+from functools import partial
+
+from .authspec import check, privileges_fulfill_specs
+
+
+logger = logging.getLogger(__name__)
+
+
+can_view = partial(
+    privileges_fulfill_specs,
+    resource_spec=(
+        "(OR group:resource:view-resource system:inbredset:view-case-attribute "
+        "    system:resource:public-read)"),
+    system_spec="(OR system:system-wide:data:view)")
+
+
+can_edit = partial(
+    privileges_fulfill_specs,
+    resource_spec=(
+        "(OR "
+        "  (AND group:resource:view-resource group:resource:edit-resource) "
+        "  (AND system:inbredset:view-case-attribute "
+        "       system:inbredset:edit-case-attribute))"),
+    system_spec=(
+        "(OR "
+        "  (AND system:system-wide:data:view system:system-wide:data:edit))"))
+
+
+def can_batch_edit(queried_privileges: tuple[str, ...]) -> bool:
+    """Check whether user has batch-editing privileges."""
+    return check(
+        ("(AND system:data:batch-edit system:system-wide:data:view"
+         "  system:system-wide:data:edit)"),
+        queried_privileges)
+
+
+can_create = partial(
+    privileges_fulfill_specs,
+    resource_spec=("(OR group:resource:create-resource "
+                   "    system:inbredset:create-case-attribute)"),
+    system_spec="(OR system:system-wide:data:create)")
+
+
+can_delete = partial(
+    privileges_fulfill_specs,
+    resource_spec=(
+        "(OR "
+        "  (AND group:resource:view-resource "
+        "       group:resource:edit-resource group:resource:delete-resource) "
+        "  (AND system:inbredset:view-case-attribute "
+        "       system:inbredset:edit-case-attribute "
+        "       system:inbredset:delete-case-attribute))"),
+    system_spec=(
+        "(OR "
+        "  (AND system:system-wide:data:view system:system-wide:data:edit "
+        "       system:system-wide:data:delete))"))
+
+
+can_apply_or_reject_edit = partial(
+    privileges_fulfill_specs,
+    resource_spec=(
+        "(AND system:inbredset:view-case-attribute "
+        "     system:inbredset:edit-case-attribute "
+        "     system:inbredset:delete-case-attribute "
+        "     system:inbredset:apply-case-attribute-edit "
+        "     system:inbredset:reject-case-attribute-edit)"),
+    system_spec=(
+        "(AND system:system-wide:inbredset:view-case-attribute "
+        "     system:system-wide:inbredset:edit-case-attribute "
+        "     system:system-wide:inbredset:delete-case-attribute "
+        "     system:system-wide:inbredset:apply-case-attribute-edit "
+        "     system:system-wide:inbredset:reject-case-attribute-edit)"))
diff --git a/gn_libs/privileges/system.py b/gn_libs/privileges/system.py
new file mode 100644
index 0000000..85e62f9
--- /dev/null
+++ b/gn_libs/privileges/system.py
@@ -0,0 +1,18 @@
+"""Checks for privileges for system-level actions."""
+import logging
+from functools import partial
+
+from .authspec import check
+
+
+logger = logging.getLogger(__name__)
+
+
+def can_link_data(system_privileges: tuple[str, ...]) -> bool:
+    """Check whether user is allowed to link data to user groups."""
+    return check("(AND system:data:link-to-group)", system_privileges)
+
+
+def can_masquerade(system_privileges: tuple[str, ...]) -> bool:
+    """Check whether the user is allowed to masquerade as a different user."""
+    return check("(AND system:user:masquerade)", system_privileges)
diff --git a/gn_libs/sqlite3.py b/gn_libs/sqlite3.py
index 78e1c41..c8bef0d 100644
--- a/gn_libs/sqlite3.py
+++ b/gn_libs/sqlite3.py
@@ -2,7 +2,7 @@
 import logging
 import traceback
 import contextlib
-from typing import Callable, Iterator
+from typing import Callable, Iterator, Any
 
 import sqlite3
 
@@ -43,3 +43,12 @@ def cursor(conn: DbConnection) -> Iterator[DbCursor]:
         raise exc
     finally:
         cur.close()
+
+
+def with_db_connection(db_uri: str, func: Callable[[DbConnection], Any]) -> Any:
+    """
+    Call `func`, a function of one argument with the SQLite3 connection created
+    from the connection string `db_uri`.
+    """
+    with connection(db_uri) as conn:
+        return func(conn)