aboutsummaryrefslogtreecommitdiff
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>