From d3fd64fb5237febb9628c4ccbd259969327ab2ec Mon Sep 17 00:00:00 2001 From: Frederick Muriuki Muriithi Date: Mon, 29 Jul 2024 14:38:32 -0500 Subject: Put endpoints behind an authorisation check Put all endpoints that cause data changes behind authorisation. --- uploader/dbinsert.py | 8 ++++++++ uploader/entry.py | 3 +++ uploader/parse.py | 3 +++ uploader/samples.py | 5 +++++ uploader/upload/rqtl2.py | 19 +++++++++++++++++++ 5 files changed, 38 insertions(+) diff --git a/uploader/dbinsert.py b/uploader/dbinsert.py index 88d16ef..66b0c41 100644 --- a/uploader/dbinsert.py +++ b/uploader/dbinsert.py @@ -11,6 +11,7 @@ from flask import ( flash, request, url_for, Blueprint, redirect, render_template, current_app as app) +from uploader.authorisation import require_login from uploader.db_utils import with_db_connection, database_connection from uploader.db import species, species_by_id, populations_by_species @@ -90,6 +91,7 @@ def tissues() -> tuple: return tuple() @dbinsertbp.route("/platform", methods=["POST"]) +@require_login def select_platform(): "Select the platform (GeneChipId) used for the data." job_id = request.form["job_id"] @@ -113,6 +115,7 @@ def select_platform(): return render_error("Unknown error") @dbinsertbp.route("/study", methods=["POST"]) +@require_login def select_study(): "View to select/create the study (ProbeFreeze) associated with the data." form = request.form @@ -142,6 +145,7 @@ def select_study(): return render_error(f"Missing data: {aserr.args[0]}") @dbinsertbp.route("/create-study", methods=["POST"]) +@require_login def create_study(): "Create a new study (ProbeFreeze)." form = request.form @@ -218,6 +222,7 @@ def dataset_datascales() -> tuple: return tuple() @dbinsertbp.route("/dataset", methods=["POST"]) +@require_login def select_dataset(): "Select the dataset to add the file contents against" form = request.form @@ -238,6 +243,7 @@ def select_dataset(): return render_error(f"Missing data: {aserr.args[0]}") @dbinsertbp.route("/create-dataset", methods=["POST"]) +@require_login def create_dataset(): "Select the dataset to add the file contents against" form = request.form @@ -317,6 +323,7 @@ def selected_keys(original: dict, keys: tuple) -> dict: return {key: value for key,value in original.items() if key in keys} @dbinsertbp.route("/final-confirmation", methods=["POST"]) +@require_login def final_confirmation(): "Preview the data before triggering entry into the database" form = request.form @@ -352,6 +359,7 @@ def final_confirmation(): return render_error(f"Missing data: {aserr.args[0]}") @dbinsertbp.route("/insert-data", methods=["POST"]) +@require_login def insert_data(): "Trigger data insertion" form = request.form diff --git a/uploader/entry.py b/uploader/entry.py index 941200a..82034ed 100644 --- a/uploader/entry.py +++ b/uploader/entry.py @@ -16,6 +16,7 @@ from flask import ( send_from_directory) from uploader.db import species +from uploader.authorisation import require_login from uploader.db_utils import with_db_connection from uploader.oauth2.client import user_logged_in @@ -91,6 +92,7 @@ def index(): return render_template("index.html" if user_logged_in() else "login.html") @entrybp.route("/upload", methods=["GET", "POST"]) +@require_login def upload_file(): """Enables uploading the files""" if request.method == "GET": @@ -123,6 +125,7 @@ def upload_file(): filetype=request.form["filetype"])) @entrybp.route("/data-review", methods=["GET"]) +@require_login def data_review(): """Provide some help on data expectations to the user.""" return render_template("data_review.html") diff --git a/uploader/parse.py b/uploader/parse.py index 865dae2..dea4f95 100644 --- a/uploader/parse.py +++ b/uploader/parse.py @@ -11,6 +11,7 @@ from quality_control.errors import InvalidValue, DuplicateHeading from uploader import jobs from uploader.dbinsert import species_by_id from uploader.db_utils import with_db_connection +from uploader.authorisation import require_login parsebp = Blueprint("parse", __name__) @@ -23,6 +24,7 @@ def isduplicateheading(item): return isinstance(item, DuplicateHeading) @parsebp.route("/parse", methods=["GET"]) +@require_login def parse(): """Trigger file parsing""" errors = False @@ -160,6 +162,7 @@ def fail(job_id: str): return render_template("no_such_job.html", job_id=job_id) @parsebp.route("/abort", methods=["POST"]) +@require_login def abort(): """Handle user request to abort file processing""" job_id = request.form["job_id"] diff --git a/uploader/samples.py b/uploader/samples.py index 9c95770..7a80336 100644 --- a/uploader/samples.py +++ b/uploader/samples.py @@ -22,6 +22,7 @@ from functional_tools import take from uploader import jobs from uploader.files import save_file +from uploader.authorisation import require_login from uploader.input_validation import is_integer_input from uploader.db_utils import ( with_db_connection, @@ -37,6 +38,7 @@ from uploader.db import ( samples = Blueprint("samples", __name__) @samples.route("/upload/species", methods=["GET", "POST"]) +@require_login def select_species(): """Select the species.""" if request.method == "GET": @@ -58,6 +60,7 @@ def select_species(): @samples.route("/upload/species//create-population", methods=["POST"]) +@require_login def create_population(species_id: int): """Create new grouping/population.""" if not is_integer_input(species_id): @@ -100,6 +103,7 @@ def create_population(species_id: int): @samples.route("/upload/species//population", methods=["GET", "POST"]) +@require_login def select_population(species_id: int): """Select from existing groupings/populations.""" if not is_integer_input(species_id): @@ -233,6 +237,7 @@ def build_sample_upload_job(# pylint: disable=[too-many-arguments] @samples.route("/upload/species//populations//samples", methods=["GET", "POST"]) +@require_login def upload_samples(species_id: int, population_id: int):#pylint: disable=[too-many-return-statements] """Upload the samples.""" samples_uploads_page = redirect(url_for("samples.upload_samples", diff --git a/uploader/upload/rqtl2.py b/uploader/upload/rqtl2.py index 6aed1f7..ff7556d 100644 --- a/uploader/upload/rqtl2.py +++ b/uploader/upload/rqtl2.py @@ -32,6 +32,7 @@ from uploader.files import save_file, fullpath from uploader.dbinsert import species as all_species from uploader.db_utils import with_db_connection, database_connection +from uploader.authorisation import require_login from uploader.db.platforms import platform_by_id, platforms_by_species from uploader.db.averaging import averaging_methods, averaging_method_by_id from uploader.db.tissues import all_tissues, tissue_by_id, create_new_tissue @@ -53,8 +54,10 @@ from uploader.db.datasets import ( rqtl2 = Blueprint("rqtl2", __name__) + @rqtl2.route("/", methods=["GET", "POST"]) @rqtl2.route("/select-species", methods=["GET", "POST"]) +@require_login def select_species(): """Select the species.""" if request.method == "GET": @@ -72,6 +75,7 @@ def select_species(): @rqtl2.route("/upload/species//select-population", methods=["GET", "POST"]) +@require_login def select_population(species_id: int): """Select/Create the population to organise data under.""" with database_connection(app.config["SQL_URI"]) as conn: @@ -101,6 +105,7 @@ def select_population(species_id: int): @rqtl2.route("/upload/species//create-population", methods=["POST"]) +@require_login def create_population(species_id: int): """Create a new population for the given species.""" population_page = redirect(url_for("upload.rqtl2.select_population", @@ -143,6 +148,7 @@ class __RequestError__(Exception): #pylint: disable=[invalid-name] @rqtl2.route(("/upload/species//population/" "/rqtl2-bundle"), methods=["GET", "POST"]) +@require_login def upload_rqtl2_bundle(species_id: int, population_id: int): """Allow upload of R/qtl2 bundle.""" with database_connection(app.config["SQL_URI"]) as conn: @@ -241,6 +247,7 @@ def chunks_directory(uniqueidentifier: str) -> Path: @rqtl2.route(("/upload/species//population/" "/rqtl2-bundle-chunked"), methods=["GET"]) +@require_login def upload_rqtl2_bundle_chunked_get(# pylint: disable=["unused-argument"] species_id: int, population_id: int @@ -285,6 +292,7 @@ def __merge_chunks__(targetfile: Path, chunkpaths: tuple[Path, ...]) -> Path: @rqtl2.route(("/upload/species//population/" "/rqtl2-bundle-chunked"), methods=["POST"]) +@require_login def upload_rqtl2_bundle_chunked_post(species_id: int, population_id: int): """ Extension to the `upload_rqtl2_bundle` endpoint above that allows large @@ -343,6 +351,7 @@ def upload_rqtl2_bundle_chunked_post(species_id: int, population_id: int): @rqtl2.route("/upload/species/rqtl2-bundle/qc-status/", methods=["GET", "POST"]) +@require_login def rqtl2_bundle_qc_status(jobid: UUID): """Check the status of the QC jobs.""" with (Redis.from_url(app.config["REDIS_URL"], decode_responses=True) as rconn, @@ -564,6 +573,7 @@ def with_errors(endpointthunk: Callable, *checkfns): @rqtl2.route(("/upload/species//population/" "/rqtl2-bundle/select-geno-dataset"), methods=["POST"]) +@require_login def select_geno_dataset(species_id: int, population_id: int): """Select from existing geno datasets.""" with database_connection(app.config["SQL_URI"]) as conn: @@ -602,6 +612,7 @@ def select_geno_dataset(species_id: int, population_id: int): @rqtl2.route(("/upload/species//population/" "/rqtl2-bundle/create-geno-dataset"), methods=["POST"]) +@require_login def create_geno_dataset(species_id: int, population_id: int): """Create a new geno dataset.""" with database_connection(app.config["SQL_URI"]) as conn: @@ -671,6 +682,7 @@ def create_geno_dataset(species_id: int, population_id: int): @rqtl2.route(("/upload/species//population/" "/rqtl2-bundle/select-tissue"), methods=["POST"]) +@require_login def select_tissue(species_id: int, population_id: int): """Select from existing tissues.""" with database_connection(app.config["SQL_URI"]) as conn: @@ -701,6 +713,7 @@ def select_tissue(species_id: int, population_id: int): @rqtl2.route(("/upload/species//population/" "/rqtl2-bundle/create-tissue"), methods=["POST"]) +@require_login def create_tissue(species_id: int, population_id: int): """Add new tissue, organ or biological material to the system.""" form = request.form @@ -745,6 +758,7 @@ def create_tissue(species_id: int, population_id: int): @rqtl2.route(("/upload/species//population/" "/rqtl2-bundle/select-probeset-study"), methods=["POST"]) +@require_login def select_probeset_study(species_id: int, population_id: int): """Select or create a probeset study.""" with database_connection(app.config["SQL_URI"]) as conn: @@ -780,6 +794,7 @@ def select_probeset_study(species_id: int, population_id: int): @rqtl2.route(("/upload/species//population/" "/rqtl2-bundle/select-probeset-dataset"), methods=["POST"]) +@require_login def select_probeset_dataset(species_id: int, population_id: int): """Select or create a probeset dataset.""" with database_connection(app.config["SQL_URI"]) as conn: @@ -820,6 +835,7 @@ def select_probeset_dataset(species_id: int, population_id: int): @rqtl2.route(("/upload/species//population/" "/rqtl2-bundle/create-probeset-study"), methods=["POST"]) +@require_login def create_probeset_study(species_id: int, population_id: int): """Create a new probeset study.""" errorclasses = "alert-error error-rqtl2 error-rqtl2-create-probeset-study" @@ -882,6 +898,7 @@ def create_probeset_study(species_id: int, population_id: int): @rqtl2.route(("/upload/species//population/" "/rqtl2-bundle/create-probeset-dataset"), methods=["POST"]) +@require_login def create_probeset_dataset(species_id: int, population_id: int):#pylint: disable=[too-many-return-statements] """Create a new probeset dataset.""" errorclasses = "alert-error error-rqtl2 error-rqtl2-create-probeset-dataset" @@ -973,6 +990,7 @@ def create_probeset_dataset(species_id: int, population_id: int):#pylint: disabl @rqtl2.route(("/upload/species//population/" "/rqtl2-bundle/dataset-info"), methods=["POST"]) +@require_login def select_dataset_info(species_id: int, population_id: int): """ If `geno` files exist in the R/qtl2 bundle, prompt user to provide the @@ -1065,6 +1083,7 @@ def select_dataset_info(species_id: int, population_id: int): @rqtl2.route(("/upload/species//population/" "/rqtl2-bundle/confirm-bundle-details"), methods=["POST"]) +@require_login def confirm_bundle_details(species_id: int, population_id: int): """Confirm the details and trigger R/qtl2 bundle processing...""" redisuri = app.config["REDIS_URL"] -- cgit v1.2.3