about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--wqflask/wqflask/__init__.py4
-rw-r--r--wqflask/wqflask/app_errors.py11
-rw-r--r--wqflask/wqflask/decorators.py56
-rw-r--r--wqflask/wqflask/metadata_edits.py21
-rw-r--r--wqflask/wqflask/templates/authorisation_error.html19
-rw-r--r--wqflask/wqflask/templates/show_trait_details.html4
6 files changed, 97 insertions, 18 deletions
diff --git a/wqflask/wqflask/__init__.py b/wqflask/wqflask/__init__.py
index 13685324..140e94ad 100644
--- a/wqflask/wqflask/__init__.py
+++ b/wqflask/wqflask/__init__.py
@@ -78,6 +78,10 @@ app.register_blueprint(group_management, url_prefix="/group-management")
 app.register_blueprint(jobs_bp, url_prefix="/jobs")
 app.register_blueprint(oauth2, url_prefix="/oauth2")
 
+from wqflask.decorators import AuthorisationError
+from wqflask.app_errors import handle_authorisation_error
+app.register_error_handler(AuthorisationError, handle_authorisation_error)
+
 server_session = Session(app)
 
 @app.before_request
diff --git a/wqflask/wqflask/app_errors.py b/wqflask/wqflask/app_errors.py
new file mode 100644
index 00000000..18b29841
--- /dev/null
+++ b/wqflask/wqflask/app_errors.py
@@ -0,0 +1,11 @@
+"""Handle errors at the application's top-level"""
+
+from flask import current_app, render_template
+
+from wqflask.decorators import AuthorisationError
+
+def handle_authorisation_error(exc: AuthorisationError):
+    """Handle AuthorisationError if not handled anywhere else."""
+    current_app.logger.error(exc)
+    return render_template(
+        "authorisation_error.html", error_type=type(exc).__name__, error=exc)
diff --git a/wqflask/wqflask/decorators.py b/wqflask/wqflask/decorators.py
index 41d23084..b281e9bd 100644
--- a/wqflask/wqflask/decorators.py
+++ b/wqflask/wqflask/decorators.py
@@ -1,15 +1,19 @@
 """This module contains gn2 decorators"""
+import json
+import requests
+from functools import wraps
+from urllib.parse import urljoin
+from typing import Dict, Callable
+
 import redis
+from flask import g, flash, request, url_for, redirect, current_app
 
-from flask import current_app, g, redirect, request, url_for
-from typing import Dict
-from urllib.parse import urljoin
-from functools import wraps
 from gn3.authentication import AdminRole
 from gn3.authentication import DataRole
 
-import json
-import requests
+from wqflask.oauth2 import client
+from wqflask.oauth2.session import session_info
+from wqflask.oauth2.request_utils import process_error
 
 
 def login_required(f):
@@ -78,3 +82,43 @@ def edit_admins_access_required(f):
             return redirect(url_for("no_access_page"))
         return f(*args, **kwargs)
     return wrap
+
+class AuthorisationError(Exception):
+    """Raise when there is an authorisation issue."""
+    def __init__(self, description, user):
+        self.description = description
+        self.user = user
+        super().__init__(self, description, user)
+
+def required_access(access_levels: tuple[str, ...],
+                    dataset_key: str = "dataset_name",
+                    trait_key: str = "name") -> Callable:
+    def __build_access_checker__(func: Callable):
+        @wraps(func)
+        def __checker__(*args, **kwargs):
+            def __error__(err):
+                error = process_error(err)
+                raise AuthorisationError(
+                    f"{error['error']}: {error['error_description']}",
+                    session_info()["user"])
+
+            def __success__(priv_info):
+                if all(priv in priv_info[0]["privileges"] for priv in access_levels):
+                    return func(*args, **kwargs)
+                missing = tuple(f"'{priv}'" for priv in access_levels
+                                if priv not in priv_info[0]["privileges"])
+                raise AuthorisationError(
+                    f"Missing privileges: {', '.join(missing)}",
+                    session_info()["user"])
+            dataset_name = kwargs.get(
+                dataset_key,
+                request.args.get(dataset_key, request.form.get(dataset_key, "")))
+            trait_name = kwargs.get(
+                trait_key,
+                request.args.get(trait_key, request.form.get(trait_key, "")))
+            return client.post(
+                "oauth2/data/authorisation",
+                json={"traits": [f"{dataset_name}::{trait_name}"]}).either(
+                    __error__, __success__)
+        return __checker__
+    return __build_access_checker__
diff --git a/wqflask/wqflask/metadata_edits.py b/wqflask/wqflask/metadata_edits.py
index 64fc3f19..df52fdc6 100644
--- a/wqflask/wqflask/metadata_edits.py
+++ b/wqflask/wqflask/metadata_edits.py
@@ -20,9 +20,9 @@ from flask import request
 from flask import url_for
 
 from wqflask.database import database_connection
-from wqflask.decorators import edit_access_required
-from wqflask.decorators import edit_admins_access_required
 from wqflask.decorators import login_required
+from wqflask.decorators import required_access
+from wqflask.decorators import edit_admins_access_required
 
 from gn3.authentication import AdminRole
 from gn3.authentication import get_highest_user_access_role
@@ -126,8 +126,8 @@ def edit_probeset(conn, name):
 
 
 @metadata_edit.route("/<dataset_id>/traits/<name>")
-@edit_access_required
-@login_required
+@required_access(
+    ("group:resource:view-resource", "group:resource:edit-resource"))
 def display_phenotype_metadata(dataset_id: str, name: str):
     with database_connection() as conn:
         _d = edit_phenotype(conn=conn, name=name, dataset_id=dataset_id)
@@ -144,8 +144,8 @@ def display_phenotype_metadata(dataset_id: str, name: str):
 
 
 @metadata_edit.route("/traits/<name>")
-@edit_access_required
-@login_required
+@required_access(
+    ("group:resource:view-resource", "group:resource:edit-resource"))
 def display_probeset_metadata(name: str):
     with database_connection() as conn:
         _d = edit_probeset(conn=conn, name=name)
@@ -160,8 +160,8 @@ def display_probeset_metadata(name: str):
 
 
 @metadata_edit.route("/<dataset_id>/traits/<name>", methods=("POST",))
-@edit_access_required
-@login_required
+@required_access(
+    ("group:resource:view-resource", "group:resource:edit-resource"))
 def update_phenotype(dataset_id: str, name: str):
     data_ = request.form.to_dict()
     TMPDIR = current_app.config.get("TMPDIR")
@@ -369,8 +369,9 @@ View the diffs <a href='{url}' target='_blank'>here</a>", "success")
 
 
 @metadata_edit.route("/traits/<name>", methods=("POST",))
-@edit_access_required
-@login_required
+@required_access(
+    ("group:resource:view-resource", "group:resource:edit-resource"),
+    dataset_key="dataset_id", trait_key="name")
 def update_probeset(name: str):
     with database_connection() as conn:
         data_ = request.form.to_dict()
diff --git a/wqflask/wqflask/templates/authorisation_error.html b/wqflask/wqflask/templates/authorisation_error.html
new file mode 100644
index 00000000..3dce8b52
--- /dev/null
+++ b/wqflask/wqflask/templates/authorisation_error.html
@@ -0,0 +1,19 @@
+{%extends "base.html"%}
+{%block title%}{{error_type}}: ...{%endblock%}
+{%block content%}
+<div class="container">
+  <div class="page-header">
+    <h3>Access Error: {{error_type}}</h3>
+  </div>
+  <p>
+  <span class="glyphicon glyphicon-exclamation-sign text-danger"></span>
+  <strong>{{error_type}}:</strong>&nbsp;
+  <small class="text-danger">{{error.description}}</small>
+  </p>
+  <p>
+    Please contact the data's owner or GN administrators if you believe you
+    should have access to these data.
+  </p>
+</div>
+
+{%endblock%}
diff --git a/wqflask/wqflask/templates/show_trait_details.html b/wqflask/wqflask/templates/show_trait_details.html
index cce76082..cd8671bb 100644
--- a/wqflask/wqflask/templates/show_trait_details.html
+++ b/wqflask/wqflask/templates/show_trait_details.html
@@ -237,11 +237,11 @@
         <button type="button" id="view_in_gn1" class="btn btn-primary" title="View Trait in GN1" onclick="window.open('http://gn1.genenetwork.org/webqtl/main.py?cmd=show&db={{ this_trait.dataset.name }}&probeset={{ this_trait.name }}', '_blank')">Go to GN1</button>
 	{%if "group:resource:edit-resource" in trait_privileges%}
         {% if this_trait.dataset.type == 'Publish' %}
-        <button type="button" id="edit_resource" class="btn btn-success" title="Edit Resource" onclick="window.open('/datasets/{{ this_trait.dataset.group.id }}/traits/{{ this_trait.name }}?resource-id={{ resource_id }}', '_blank')">Edit</button>
+        <button type="button" id="edit_resource" class="btn btn-success" title="Edit Resource" onclick="window.open('/datasets/{{ this_trait.dataset.group.id }}/traits/{{ this_trait.name }}?resource-id={{ resource_id }}&dataset_name={{this_trait.dataset.name}}', '_blank')">Edit</button>
         {% endif %}
 
         {% if this_trait.dataset.type == 'ProbeSet' %}
-        <button type="button" id="edit_resource" class="btn btn-success" title="Edit Resource" onclick="window.open('/datasets/traits/{{ this_trait.name }}?resource-id={{ resource_id }}', '_blank')">Edit</button>
+        <button type="button" id="edit_resource" class="btn btn-success" title="Edit Resource" onclick="window.open('/datasets/traits/{{ this_trait.name }}?resource-id={{ resource_id }}&dataset_name={{this_trait.dataset.name}}', '_blank')">Edit</button>
         {% endif %}
         {% endif %}
     </div>