From cecb5011a76362605d88f2684d784bda3a87768f Mon Sep 17 00:00:00 2001 From: Avery Date: Tue, 8 Oct 2024 13:10:38 -0700 Subject: [PATCH] Does not execute jpath if it is not sanitized. (#5299) * Does not execute jpath if it is not sanitized. * Do not save invalid jpaths on the server side. * Do not save invalid jpaths on the server side. * Improves error messages and adds new exception handling. * Use JPath library, not json library * Adds jpath test. Handle all errors from parsing a jpath string. --- src/dispatch/entity_type/service.py | 25 ++++++++++++++++--- .../static/dispatch/src/entity_type/utils.js | 19 ++++++++++++++ tests/entity_type/test_entity_type_service.py | 22 ++++++++++++++++ 3 files changed, 63 insertions(+), 3 deletions(-) diff --git a/src/dispatch/entity_type/service.py b/src/dispatch/entity_type/service.py index 3286818a1a51..c4dbe8775fec 100644 --- a/src/dispatch/entity_type/service.py +++ b/src/dispatch/entity_type/service.py @@ -1,13 +1,16 @@ +import logging from typing import Optional from pydantic.error_wrappers import ErrorWrapper, ValidationError from sqlalchemy.orm import Query, Session - +from jsonpath_ng import parse from dispatch.exceptions import NotFoundError from dispatch.project import service as project_service from dispatch.signal import service as signal_service from .models import EntityType, EntityTypeCreate, EntityTypeRead, EntityTypeUpdate +logger = logging.getLogger(__name__) + def get(*, db_session, entity_type_id: int) -> Optional[EntityType]: """Gets a entity type by its id.""" @@ -58,7 +61,9 @@ def create(*, db_session: Session, entity_type_in: EntityTypeCreate) -> EntityTy project = project_service.get_by_name_or_raise( db_session=db_session, project_in=entity_type_in.project ) - entity_type = EntityType(**entity_type_in.dict(exclude={"project", "signals"}), project=project) + entity_type = EntityType( + **entity_type_in.dict(exclude={"project", "signals", "jpath"}), project=project + ) signals = [] for signal in entity_type_in.signals: @@ -66,6 +71,7 @@ def create(*, db_session: Session, entity_type_in: EntityTypeCreate) -> EntityTy signals.append(signal) entity_type.signals = signals + set_jpath(entity_type, entity_type_in) db_session.add(entity_type) db_session.commit() @@ -92,7 +98,7 @@ def update( ) -> EntityType: """Updates an entity type.""" entity_type_data = entity_type.dict() - update_data = entity_type_in.dict(skip_defaults=True) + update_data = entity_type_in.dict(exclude={"jpath"}, skip_defaults=True) for field in entity_type_data: if field in update_data: @@ -105,6 +111,8 @@ def update( entity_type.signals = signals + set_jpath(entity_type, entity_type_in) + db_session.commit() return entity_type @@ -114,3 +122,14 @@ def delete(*, db_session: Session, entity_type_id: int) -> None: entity_type = db_session.query(EntityType).filter(EntityType.id == entity_type_id).one() db_session.delete(entity_type) db_session.commit() + + +def set_jpath(entity_type: EntityType, entity_type_in: EntityTypeCreate): + entity_type.jpath = "" + try: + parse(entity_type_in.jpath) + entity_type.jpath = entity_type_in.jpath + except Exception: + logger.error( + f"Failed to parse jPath: {entity_type_in.jpath}. The jPath field will be skipped." + ) diff --git a/src/dispatch/static/dispatch/src/entity_type/utils.js b/src/dispatch/static/dispatch/src/entity_type/utils.js index 9a1e3c797a89..fe203d80465a 100644 --- a/src/dispatch/static/dispatch/src/entity_type/utils.js +++ b/src/dispatch/static/dispatch/src/entity_type/utils.js @@ -9,8 +9,27 @@ export function isValidRegex(pattern) { } } +function sanitizeString(str) { + return str.replace(/[&<>"'`=/]/g, function (char) { + return { + "&": "&", + "<": "<", + ">": ">", + '"': """, + "'": "'", + "`": "`", + "=": "=", + "/": "/", + }[char] + }) +} + export function isValidJsonPath(jpath) { try { + if (sanitizeString(jpath) !== jpath) { + return false + } + jsonpath.parse(jpath) return true } catch (e) { diff --git a/tests/entity_type/test_entity_type_service.py b/tests/entity_type/test_entity_type_service.py index 942336515c63..693072cfea58 100644 --- a/tests/entity_type/test_entity_type_service.py +++ b/tests/entity_type/test_entity_type_service.py @@ -48,3 +48,25 @@ def test_delete(session, entity_type): delete(db_session=session, entity_type_id=entity_type.id) assert not get(db_session=session, entity_type_id=entity_type.id) + + +def test_set_jpath(entity_type): + from dispatch.entity_type.service import set_jpath + from dispatch.entity_type.models import EntityTypeCreate + + entity_type_in = EntityTypeCreate.from_orm(entity_type) + entity_type_in.jpath = "$.foo.bar[0].foobar" + + set_jpath(entity_type, entity_type_in) + assert entity_type.jpath == "$.foo.bar[0].foobar" + + +def test_set_jpath__fail(entity_type): + from dispatch.entity_type.service import set_jpath + from dispatch.entity_type.models import EntityTypeCreate + + entity_type_in = EntityTypeCreate.from_orm(entity_type) + entity_type_in.jpath = "?" + + set_jpath(entity_type, entity_type_in) + assert entity_type.jpath == ""