diff --git a/ontology/models/models_ontology.py b/ontology/models/models_ontology.py index 9a8d7e523..7a5b79666 100644 --- a/ontology/models/models_ontology.py +++ b/ontology/models/models_ontology.py @@ -16,6 +16,7 @@ from django.contrib.postgres.fields import ArrayField from django.db import models, connection from django.db.models import PROTECT, CASCADE, QuerySet, Q, Max, TextChoices +from django.http import Http404 from django.urls import reverse from model_utils.models import TimeStampedModel, now from psqlextra.models import PostgresPartitionedModel @@ -288,6 +289,8 @@ def num_part_safe(self) -> int: @staticmethod def normalize(dirty_id: str) -> 'OntologyIdNormalized': + if len(dirty_id) > 200: + raise ValueError(f"Input too long ({len(dirty_id)} chars) to normalize as an ontology ID") parts = re.split("[:|_]", dirty_id) if len(parts) != 2: raise ValueError(f"Can not convert {dirty_id} to a proper id") @@ -431,7 +434,10 @@ def url_safe_id(self): @staticmethod def get_from_slug(slug_pk): pk = slug_pk.replace("_", ":") - return OntologyTerm.objects.get(pk=pk) + try: + return OntologyTerm.objects.get(pk=pk) + except OntologyTerm.DoesNotExist: + raise Http404 @staticmethod def get_gene_symbol(gene_symbol: Union[str, GeneSymbol]) -> 'OntologyTerm': @@ -507,7 +513,7 @@ def get_or_stub(id_str: Union[str, OntologyIdNormalized]) -> 'OntologyTerm': return existing try: index_num_part_value = normal_id.num_part - except Exception: + except ValueError: index_num_part_value = normal_id.num_part_safe # Ontologies like MedGen can have alpha characters in the "index", providing an index of 0 until we update the model return OntologyTerm( id=normal_id.full_id, diff --git a/ontology/panel_app_ontology.py b/ontology/panel_app_ontology.py index 3fc5b4b2c..ade51e660 100644 --- a/ontology/panel_app_ontology.py +++ b/ontology/panel_app_ontology.py @@ -1,4 +1,5 @@ import re +import urllib.parse from collections import defaultdict from dataclasses import dataclass from datetime import timedelta @@ -90,7 +91,7 @@ def is_empty_results(data: Any): try: hgnc_term = OntologyTerm.get_gene_symbol(gene_symbol) panel_app = PanelAppServer.australia_instance() - filename = panel_app.url + PANEL_APP_SEARCH_BY_GENES_BASE_PATH + gene_symbol + filename = panel_app.url + PANEL_APP_SEARCH_BY_GENES_BASE_PATH + urllib.parse.quote(gene_symbol, safe="") ontology_builder = OntologyBuilder(filename=filename, context=str(gene_symbol), import_source=OntologyImportSource.PANEL_APP_AU, processor_version=PANEL_APP_API_PROCESSOR_VERSION, diff --git a/ontology/views.py b/ontology/views.py index c0e80cc73..ed4dcb1f7 100644 --- a/ontology/views.py +++ b/ontology/views.py @@ -1,4 +1,5 @@ from django.contrib import messages +from django.http import Http404 from django.shortcuts import get_object_or_404, redirect from django.views.generic import TemplateView @@ -11,7 +12,10 @@ def ontology_term_text(request, ontology_service, name): """ Occasionally we have service + name but not the ID - this is a way of building an URL for that """ - ontology_service = OntologyService(ontology_service) # Ensure valid + try: + ontology_service = OntologyService(ontology_service) + except ValueError: + raise Http404 term = get_object_or_404(OntologyTerm, name=name, ontology_service=ontology_service) return redirect(term) diff --git a/ontology/views_autocomplete.py b/ontology/views_autocomplete.py index 7933d24db..2b9295fe0 100644 --- a/ontology/views_autocomplete.py +++ b/ontology/views_autocomplete.py @@ -46,7 +46,13 @@ def get_user_queryset(self, user): class OntologyTermAutocompleteView(AbstractOntologyTermAutocompleteView): def _get_ontology_service(self): # Passed ontology_service in forward - return self.forwarded.get('ontology_service') + value = self.forwarded.get('ontology_service') + if value is None: + return None + try: + return OntologyService(value) + except ValueError: + return None @method_decorator(cache_page(HOUR_SECS), name='dispatch') diff --git a/ontology/views_rest.py b/ontology/views_rest.py index 0c6215cf4..0d87cf84c 100644 --- a/ontology/views_rest.py +++ b/ontology/views_rest.py @@ -1,5 +1,6 @@ -import urllib +import re +from django.http import Http404 from django.utils.decorators import method_decorator from django.views.decorators.cache import cache_page from rest_framework.response import Response @@ -14,6 +15,8 @@ from ontology.ontology_matching import OntologyMatching from ontology.serializers import OntologyTermRelationSerializer +_GENE_SYMBOL_RE = re.compile(r'^[A-Za-z0-9\-]+$') + class SearchMondoText(APIView): def get(self, request, **kwargs) -> Response: @@ -21,7 +24,6 @@ def get(self, request, **kwargs) -> Response: search_term = request.GET.get('search_term') or '' gene_symbol = request.GET.get('gene_symbol') - urllib.parse.quote(search_term).replace('/', '%252F') # a regular escape / gets confused for a URL divider selected = [term.strip() for term in (request.GET.get('selected') or '').split(",") if term.strip()] ontology_matches = OntologyMatching.from_search(search_text=search_term, gene_symbol=gene_symbol, selected=selected) @@ -58,8 +60,11 @@ def get(self, request, *args, **kwargs): @method_decorator(cache_page(WEEK_SECS), name='get') class GeneDiseaseRelationshipView(APIView): def get(self, request, *args, **kwargs): + gene_symbol = self.kwargs['gene_symbol'] + if not _GENE_SYMBOL_RE.match(gene_symbol): + raise Http404 data = [] ontology_version = OntologyVersion.latest() - for otr in ontology_version.gene_disease_relations(self.kwargs['gene_symbol'], quality_filter=ONTOLOGY_RELATIONSHIP_MEDIUM_QUALITY_FILTER): + for otr in ontology_version.gene_disease_relations(gene_symbol, quality_filter=ONTOLOGY_RELATIONSHIP_MEDIUM_QUALITY_FILTER): data.append(OntologyTermRelationSerializer(otr).data) return Response(data)