diff --git a/bento_beacon/app.py b/bento_beacon/app.py index 89b0e2c..3abb891 100644 --- a/bento_beacon/app.py +++ b/bento_beacon/app.py @@ -17,7 +17,7 @@ from .utils.beacon_request import save_request_data, validate_request, verify_permissions from .utils.beacon_response import init_response_data from .utils.katsu_utils import katsu_censorship_settings -from .utils.censorship import set_censorship_settings +from .utils.censorship import set_censorship_settings, reject_query_if_not_permitted REQUEST_SPEC_RELATIVE_PATH = "beacon-v2/framework/json/requests/" BEACON_MODELS = ["analyses", "biosamples", "cohorts", "datasets", "individuals", "runs", "variants"] @@ -96,6 +96,7 @@ def before_request(): validate_request() verify_permissions() save_request_data() + reject_query_if_not_permitted() init_response_data() diff --git a/bento_beacon/config_files/config.py b/bento_beacon/config_files/config.py index 2e863ae..93f4b0a 100644 --- a/bento_beacon/config_files/config.py +++ b/bento_beacon/config_files/config.py @@ -169,6 +169,9 @@ class Config: PHENOPACKETS_SCHEMA_REFERENCE = {"entityType": "individual", "schema": "phenopackets v1"} MAX_RETRIES_FOR_CENSORSHIP_PARAMS = 2 + + # don't let anonymous users query arbitrary phenopacket or experiment fields + ANONYMOUS_METADATA_QUERY_USES_DISCOVERY_CONFIG_ONLY = True # ------------------- # gohan diff --git a/bento_beacon/endpoints/individuals.py b/bento_beacon/endpoints/individuals.py index 6932f1e..98295a0 100644 --- a/bento_beacon/endpoints/individuals.py +++ b/bento_beacon/endpoints/individuals.py @@ -2,13 +2,10 @@ P_DOWNLOAD_DATA, P_QUERY_DATA, ) -from flask import Blueprint +from flask import Blueprint, g from functools import reduce from ..authz.middleware import authz_middleware, check_permission -from ..utils.beacon_request import ( - query_parameters_from_request, - summary_stats_requested, -) +from ..utils.beacon_request import summary_stats_requested from ..utils.beacon_response import ( add_info_to_response, add_stats_to_response, @@ -32,7 +29,10 @@ @individuals.route("/individuals", methods=["GET", "POST"]) def get_individuals(): - variants_query, phenopacket_filters, experiment_filters, config_filters = query_parameters_from_request() + variants_query = g.beacon_query_parameters["variants_query"] + phenopacket_filters = g.beacon_query_parameters["phenopacket_filters"] + experiment_filters = g.beacon_query_parameters["experiment_filters"] + config_filters = g.beacon_query_parameters["config_filters"] no_query = not (variants_query or phenopacket_filters or experiment_filters or config_filters) search_sample_ids = variants_query or experiment_filters diff --git a/bento_beacon/endpoints/variants.py b/bento_beacon/endpoints/variants.py index e0ec38a..23bbbda 100644 --- a/bento_beacon/endpoints/variants.py +++ b/bento_beacon/endpoints/variants.py @@ -1,6 +1,5 @@ -from flask import Blueprint +from flask import Blueprint, g from ..authz.middleware import authz_middleware -from ..utils.beacon_request import query_parameters_from_request from ..utils.beacon_response import build_query_response, add_info_to_response, zero_count_response from ..utils.gohan_utils import query_gohan, gohan_total_variants_count, gohan_totals_by_sample_id from ..utils.search import biosample_id_search @@ -12,7 +11,10 @@ @variants.route("/g_variants", methods=["GET", "POST"]) @authz_middleware.deco_public_endpoint # TODO: for now. eventually, return more depending on permissions def get_variants(): - variants_query, phenopacket_filters, experiment_filters, config_filters = query_parameters_from_request() + variants_query = g.beacon_query_parameters["variants_query"] + phenopacket_filters = g.beacon_query_parameters["phenopacket_filters"] + experiment_filters = g.beacon_query_parameters["experiment_filters"] + config_filters = g.beacon_query_parameters["config_filters"] has_filters = phenopacket_filters or experiment_filters or config_filters # if no query, return total count of variants diff --git a/bento_beacon/utils/beacon_request.py b/bento_beacon/utils/beacon_request.py index 416fa8f..0a44588 100644 --- a/bento_beacon/utils/beacon_request.py +++ b/bento_beacon/utils/beacon_request.py @@ -21,9 +21,9 @@ def expand_path(id): return id.replace("/", ".[item].") -def query_parameters_from_request(): - variants_query = g.request_data.get("requestParameters", {}).get("g_variant") or {} - filters = g.request_data.get("filters") or [] +def parse_query_params(request_data): + variants_query = request_data.get("requestParameters", {}).get("g_variant") or {} + filters = request_data.get("filters") or [] phenopacket_filters = list(filter(lambda f: f["id"].startswith("phenopacket."), filters)) experiment_filters = list(filter(lambda f: f["id"].startswith("experiment."), filters)) config_filters = [f for f in filters if f not in phenopacket_filters and f not in experiment_filters] @@ -49,7 +49,12 @@ def query_parameters_from_request(): experiment_filters, ) ) - return variants_query, phenopacket_filters, experiment_filters, config_filters + return { + "variants_query": variants_query, + "phenopacket_filters": phenopacket_filters, + "experiment_filters": experiment_filters, + "config_filters": config_filters, + } # structure GET params so they match the nested structure in POST @@ -130,8 +135,12 @@ def save_request_data(): if request_bento: request_data["bento"] = request_bento + # raw request data, this is echoed in response "meta" field g.request_data = request_data + # parsed query components + g.beacon_query_parameters = parse_query_params(request_data) + def validate_request(): if request.method == "POST": diff --git a/bento_beacon/utils/censorship.py b/bento_beacon/utils/censorship.py index 323a3ca..5fc564d 100644 --- a/bento_beacon/utils/censorship.py +++ b/bento_beacon/utils/censorship.py @@ -70,3 +70,19 @@ def reject_if_too_many_filters(filters): def censored_chart_data(data): t = get_censorship_threshold() # zero with correct permissions return [{"label": d["label"], "value": d["value"]} for d in data if d["value"] > t] + + +def query_has_phenopacket_filter(): + return bool(g.beacon_query_parameters["phenopacket_filters"]) + + +def query_has_experiment_filter(): + return bool(g.beacon_query_parameters["experiment_filters"]) + + +# some anonymous queries are not permitted +def reject_query_if_not_permitted(): + if g.permission_query_data or not current_app.config["ANONYMOUS_METADATA_QUERY_USES_DISCOVERY_CONFIG_ONLY"]: + return + if query_has_phenopacket_filter() or query_has_experiment_filter(): + raise InvalidQuery("anonymous queries should use filters from discovery config only")