From c166da45c0d7ab079e219f42e0b168e35698a004 Mon Sep 17 00:00:00 2001 From: Emile Sonneveld Date: Tue, 16 Jun 2026 17:14:04 +0200 Subject: [PATCH 1/7] Allow routes to accept nested assets. --- openeo_driver/dummy/dummy_backend.py | 1 + openeo_driver/views.py | 8 ++++---- tests/test_views.py | 19 +++++++++++++++++++ 3 files changed, 24 insertions(+), 4 deletions(-) diff --git a/openeo_driver/dummy/dummy_backend.py b/openeo_driver/dummy/dummy_backend.py index 9301ae3f..3504756b 100644 --- a/openeo_driver/dummy/dummy_backend.py +++ b/openeo_driver/dummy/dummy_backend.py @@ -925,6 +925,7 @@ def get_result_assets(self, job_id: str, user_id: str) -> Dict[str, dict]: "type": "Polygon", "coordinates": [[[0.0, 50.0], [0.0, 55.0], [5.0, 55.0], [5.0, 50.0], [0.0, 50.0]]], }, + "datetime": "1970-01-01T00:00:00Z", }, } elif job_id == "j-26032411111111111111111111111111": diff --git a/openeo_driver/views.py b/openeo_driver/views.py index a4e21363..e10b6707 100644 --- a/openeo_driver/views.py +++ b/openeo_driver/views.py @@ -1489,7 +1489,7 @@ def _stream_from_s3(s3_url, *, filename, mimetype: Optional[str], bytes_range: O raise - @blueprint.route('/jobs//results/items///', methods=['GET']) + @blueprint.route("/jobs//results/items///", methods=["GET"]) def get_job_result_item_signed(job_id, user_base64, secure_key, item_id): expires = request.args.get('expires') signer = get_backend_config().url_signer @@ -1498,7 +1498,7 @@ def get_job_result_item_signed(job_id, user_base64, secure_key, item_id): return _get_job_result_item(job_id, item_id, user_id) @api_endpoint - @blueprint.route('/jobs//results/items11///', methods=['GET']) + @blueprint.route("/jobs//results/items11///", methods=["GET"]) def get_job_result_item11_signed(job_id, user_base64, secure_key, item_id): expires = request.args.get('expires') signer = get_backend_config().url_signer @@ -1506,13 +1506,13 @@ def get_job_result_item11_signed(job_id, user_base64, secure_key, item_id): signer.verify_job_item(signature=secure_key, job_id=job_id, user_id=user_id, item_id=item_id, expires=expires) return _get_job_result_item11(job_id, item_id, user_id) - @blueprint.route('/jobs//results/items/', methods=['GET']) + @blueprint.route("/jobs//results/items/", methods=["GET"]) @auth_handler.requires_bearer_auth def get_job_result_item(job_id: str, item_id: str, user: User) -> flask.Response: return _get_job_result_item(job_id, item_id, user.user_id) @api_endpoint(version=ComparableVersion("1.1.0").or_higher) - @blueprint.route('/jobs//results/items11/', methods=['GET']) + @blueprint.route("/jobs//results/items11/", methods=["GET"]) @auth_handler.requires_bearer_auth def get_job_result_item11(job_id: str, item_id: str, user: User) -> flask.Response: return _get_job_result_item11(job_id, item_id, user.user_id) diff --git a/tests/test_views.py b/tests/test_views.py index ab8a40dd..6cb49eaf 100644 --- a/tests/test_views.py +++ b/tests/test_views.py @@ -4024,6 +4024,25 @@ def test_get_vector_cube_job_result_item( extensions=resp_data.get("stac_extensions", []), ) + def test_get_job_result_item_with_temporal_extent_on_asset_nested(self, flask_app, api110): + with self._fresh_job_registry(): + resp = api110.get( + f"/jobs/j-24111211111111111111111111111111/results/items/subfolder/output.tiff", + headers=self.AUTH_HEADER, + ) + + resp_data = resp.assert_status_code(200).json + assert resp_data["assets"]["subfolder/output.tiff"]["eo:bands"][0]["name"] == "NDVI" + + assert resp.headers["Content-Type"] == "application/geo+json" + + pystac.validation.stac_validator.JsonSchemaSTACValidator().validate( + stac_dict=resp_data, + stac_object_type=pystac.STACObjectType.ITEM, + stac_version=resp_data.get("stac_version", "1.0.0"), + extensions=resp_data.get("stac_extensions", []), + ) + def test_get_job_result_item_with_temporal_extent_on_asset(self, flask_app, api110): with self._fresh_job_registry(): resp = api110.get( From c5009ffd2224b38efc025dbe8944da5a8605b0bb Mon Sep 17 00:00:00 2001 From: Emile Sonneveld Date: Tue, 16 Jun 2026 17:32:56 +0200 Subject: [PATCH 2/7] Test double nested. --- openeo_driver/dummy/dummy_backend.py | 15 +++++++++++++++ tests/test_views.py | 19 +++++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/openeo_driver/dummy/dummy_backend.py b/openeo_driver/dummy/dummy_backend.py index 3504756b..11d5d697 100644 --- a/openeo_driver/dummy/dummy_backend.py +++ b/openeo_driver/dummy/dummy_backend.py @@ -927,6 +927,21 @@ def get_result_assets(self, job_id: str, user_id: str) -> Dict[str, dict]: }, "datetime": "1970-01-01T00:00:00Z", }, + "subfolder/subsubfolder/output.tiff": { + "output_dir": f"{self._output_root()}/{job_id}", + "href": f"{self._output_root()}/{job_id}/subfolder/subsubfolder/output.tiff", + "type": "image/tiff; application=geotiff", + "roles": ["data"], + "bands": [Band(name="NDVI", wavelength_um=1.23)], + "nodata": 123, + "instruments": "MSI", + "bbox": [0.0, 50.0, 5.0, 55.0], + "geometry": { + "type": "Polygon", + "coordinates": [[[0.0, 50.0], [0.0, 55.0], [5.0, 55.0], [5.0, 50.0], [0.0, 50.0]]], + }, + "datetime": "1970-01-01T00:00:00Z", + }, } elif job_id == "j-26032411111111111111111111111111": default_json = { diff --git a/tests/test_views.py b/tests/test_views.py index 6cb49eaf..27b0389c 100644 --- a/tests/test_views.py +++ b/tests/test_views.py @@ -4043,6 +4043,25 @@ def test_get_job_result_item_with_temporal_extent_on_asset_nested(self, flask_ap extensions=resp_data.get("stac_extensions", []), ) + def test_get_job_result_item_with_temporal_extent_on_asset_double_nested(self, flask_app, api110): + with self._fresh_job_registry(): + resp = api110.get( + f"/jobs/j-24111211111111111111111111111111/results/items/subfolder/subsubfolder/output.tiff", + headers=self.AUTH_HEADER, + ) + + resp_data = resp.assert_status_code(200).json + assert resp_data["assets"]["subfolder/subsubfolder/output.tiff"]["eo:bands"][0]["name"] == "NDVI" + + assert resp.headers["Content-Type"] == "application/geo+json" + + pystac.validation.stac_validator.JsonSchemaSTACValidator().validate( + stac_dict=resp_data, + stac_object_type=pystac.STACObjectType.ITEM, + stac_version=resp_data.get("stac_version", "1.0.0"), + extensions=resp_data.get("stac_extensions", []), + ) + def test_get_job_result_item_with_temporal_extent_on_asset(self, flask_app, api110): with self._fresh_job_registry(): resp = api110.get( From 72b4bea1b08ef84a59c620a099ed28bf8f21c11a Mon Sep 17 00:00:00 2001 From: Emile Sonneveld Date: Wed, 17 Jun 2026 09:47:09 +0200 Subject: [PATCH 3/7] Flask uses Werkzeug to resolve to a route. The signed URL gets priority, so put a redirect there to get back to the non-signed URL with full paths. https://github.com/Open-EO/openeo-python-driver/pull/515#discussion_r3421947843 --- openeo_driver/urlsigning.py | 6 ++++++ openeo_driver/users/__init__.py | 2 +- openeo_driver/users/user.py | 8 ++++++++ openeo_driver/views.py | 11 ++++++++++- 4 files changed, 25 insertions(+), 2 deletions(-) diff --git a/openeo_driver/urlsigning.py b/openeo_driver/urlsigning.py index bd4f9829..2c6b2248 100644 --- a/openeo_driver/urlsigning.py +++ b/openeo_driver/urlsigning.py @@ -1,5 +1,6 @@ import logging import operator +import re import time from functools import reduce from hashlib import md5 @@ -10,6 +11,11 @@ _log = logging.getLogger(__name__) +def is_secure_key(s: str) -> bool: + """Return True if s looks like a URL signing key (MD5 hex digest).""" + return bool(re.compile(r"^[0-9a-f]{32}$").match(s)) + + class UrlSigner: def __init__(self, secret: str, expiration: int = None): self._secret = secret diff --git a/openeo_driver/users/__init__.py b/openeo_driver/users/__init__.py index 91eb21d7..187806ab 100644 --- a/openeo_driver/users/__init__.py +++ b/openeo_driver/users/__init__.py @@ -1 +1 @@ -from openeo_driver.users.user import User, user_id_b64_encode, user_id_b64_decode +from openeo_driver.users.user import User, user_id_b64_encode, user_id_b64_decode, is_user_id_b64 diff --git a/openeo_driver/users/user.py b/openeo_driver/users/user.py index 15deea81..03427ede 100644 --- a/openeo_driver/users/user.py +++ b/openeo_driver/users/user.py @@ -1,3 +1,4 @@ +import re from typing import Union, Set, Optional, Iterable import base64 @@ -65,6 +66,13 @@ def user_id_b64_encode(user_id: str) -> str: return base64.urlsafe_b64encode(user_id.encode("utf8")).decode("ascii") +def is_user_id_b64(s: str) -> bool: + """Return True if s looks like a base64url-encoded user id (as produced by user_id_b64_encode).""" + # urlsafe_b64encode always produces characters in [A-Za-z0-9_-] plus '=' padding, + # and the total length is always a multiple of 4. + return bool(re.compile(r"^[A-Za-z0-9_=-]+$").match(s)) and len(s) % 4 == 0 + + def user_id_b64_decode(encoded: str) -> str: """Decode a user id that was encoded with user_id_b64_encode""" return base64.urlsafe_b64decode(encoded.encode("ascii")).decode("utf-8") diff --git a/openeo_driver/views.py b/openeo_driver/views.py index e10b6707..eec2f492 100644 --- a/openeo_driver/views.py +++ b/openeo_driver/views.py @@ -72,13 +72,14 @@ from openeo_driver.jobregistry import PARTIAL_JOB_STATUS from openeo_driver.processgraph import ProcessGraphFlatDict, extract_default_job_options_from_process_graph from openeo_driver.save_result import SaveResult, to_save_result -from openeo_driver.users import User, user_id_b64_decode, user_id_b64_encode +from openeo_driver.users import User, user_id_b64_decode, user_id_b64_encode, is_user_id_b64 from openeo_driver.users.auth import HttpAuthHandler from openeo_driver.util.compat import function_has_argument, filter_supported_kwargs from openeo_driver.util.geometry import BoundingBox, reproject_geometry from openeo_driver.util.logging import ExtraLoggingFilter, FlaskRequestCorrelationIdLogging from openeo_driver.util.stac import sniff_stac_extension_prefix from openeo_driver.util.stac_utils import get_files_from_stac_catalog +from openeo_driver.urlsigning import is_secure_key from openeo_driver.utils import EvalEnv, smart_bool @@ -1491,6 +1492,10 @@ def _stream_from_s3(s3_url, *, filename, mimetype: Optional[str], bytes_range: O @blueprint.route("/jobs//results/items///", methods=["GET"]) def get_job_result_item_signed(job_id, user_base64, secure_key, item_id): + if not is_user_id_b64(user_base64) or not is_secure_key(secure_key): + full_item_id = "/".join([user_base64, secure_key, item_id]) + user = auth_handler.get_user_from_bearer_token(request) + return _get_job_result_item(job_id, full_item_id, user.user_id) expires = request.args.get('expires') signer = get_backend_config().url_signer user_id = user_id_b64_decode(user_base64) @@ -1500,6 +1505,10 @@ def get_job_result_item_signed(job_id, user_base64, secure_key, item_id): @api_endpoint @blueprint.route("/jobs//results/items11///", methods=["GET"]) def get_job_result_item11_signed(job_id, user_base64, secure_key, item_id): + if not is_user_id_b64(user_base64) or not is_secure_key(secure_key): + full_item_id = "/".join([user_base64, secure_key, item_id]) + user = auth_handler.get_user_from_bearer_token(request) + return _get_job_result_item11(job_id, full_item_id, user.user_id) expires = request.args.get('expires') signer = get_backend_config().url_signer user_id = user_id_b64_decode(user_base64) From ed035e9fb69b03695e1a57668fe170c41657356a Mon Sep 17 00:00:00 2001 From: Emile Sonneveld Date: Wed, 17 Jun 2026 09:54:09 +0200 Subject: [PATCH 4/7] Same for download_job_result_signed --- openeo_driver/views.py | 4 ++++ tests/test_views.py | 4 ++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/openeo_driver/views.py b/openeo_driver/views.py index eec2f492..6168f630 100644 --- a/openeo_driver/views.py +++ b/openeo_driver/views.py @@ -1936,6 +1936,10 @@ def download_job_result(job_id, filename, user: User): @blueprint.route("/jobs//results/assets///", methods=["GET"]) def download_job_result_signed(job_id, user_base64, secure_key, filename): + if not is_user_id_b64(user_base64) or not is_secure_key(secure_key): + full_filename = "/".join([user_base64, secure_key, filename]) + user = auth_handler.get_user_from_bearer_token(request) + return _download_job_result(job_id=job_id, filename=full_filename, user_id=user.user_id) expires = request.args.get('expires') signer = get_backend_config().url_signer user_id = user_id_b64_decode(user_base64) diff --git a/tests/test_views.py b/tests/test_views.py index 27b0389c..52ac7ad4 100644 --- a/tests/test_views.py +++ b/tests/test_views.py @@ -3525,12 +3525,12 @@ def test_download_result_nested_path(self, api110, tmp_path): output_root = Path(tmp_path) jobs = {"j-24111211111111111111111111111111": {"status": "finished"}} with self._fresh_job_registry(output_root=output_root, jobs=jobs): - output = output_root / "j-24111211111111111111111111111111/subfolder/output.tiff" + output = output_root / "j-24111211111111111111111111111111/subfolder/subsubfolder/output.tiff" output.parent.mkdir(parents=True) with output.open("wb") as f: f.write(b"tiffdata") resp = api110.get( - "/jobs/j-24111211111111111111111111111111/results/assets/subfolder/output.tiff", + "/jobs/j-24111211111111111111111111111111/results/assets/subfolder/subsubfolder/output.tiff", headers=self.AUTH_HEADER, ) assert resp.assert_status_code(200).data == b"tiffdata" From 19e287fe6b579d8a151e12a96e716262febe84a3 Mon Sep 17 00:00:00 2001 From: Emile Sonneveld Date: Wed, 17 Jun 2026 10:22:35 +0200 Subject: [PATCH 5/7] Use try-catch to be more robust against strings that look like valid user-ids --- openeo_driver/users/__init__.py | 2 +- openeo_driver/users/user.py | 8 -------- openeo_driver/views.py | 24 +++++++++++++++++------- 3 files changed, 18 insertions(+), 16 deletions(-) diff --git a/openeo_driver/users/__init__.py b/openeo_driver/users/__init__.py index 187806ab..91eb21d7 100644 --- a/openeo_driver/users/__init__.py +++ b/openeo_driver/users/__init__.py @@ -1 +1 @@ -from openeo_driver.users.user import User, user_id_b64_encode, user_id_b64_decode, is_user_id_b64 +from openeo_driver.users.user import User, user_id_b64_encode, user_id_b64_decode diff --git a/openeo_driver/users/user.py b/openeo_driver/users/user.py index 03427ede..15deea81 100644 --- a/openeo_driver/users/user.py +++ b/openeo_driver/users/user.py @@ -1,4 +1,3 @@ -import re from typing import Union, Set, Optional, Iterable import base64 @@ -66,13 +65,6 @@ def user_id_b64_encode(user_id: str) -> str: return base64.urlsafe_b64encode(user_id.encode("utf8")).decode("ascii") -def is_user_id_b64(s: str) -> bool: - """Return True if s looks like a base64url-encoded user id (as produced by user_id_b64_encode).""" - # urlsafe_b64encode always produces characters in [A-Za-z0-9_-] plus '=' padding, - # and the total length is always a multiple of 4. - return bool(re.compile(r"^[A-Za-z0-9_=-]+$").match(s)) and len(s) % 4 == 0 - - def user_id_b64_decode(encoded: str) -> str: """Decode a user id that was encoded with user_id_b64_encode""" return base64.urlsafe_b64decode(encoded.encode("ascii")).decode("utf-8") diff --git a/openeo_driver/views.py b/openeo_driver/views.py index 6168f630..1385266d 100644 --- a/openeo_driver/views.py +++ b/openeo_driver/views.py @@ -1,3 +1,4 @@ +import binascii import copy import functools import json @@ -72,7 +73,7 @@ from openeo_driver.jobregistry import PARTIAL_JOB_STATUS from openeo_driver.processgraph import ProcessGraphFlatDict, extract_default_job_options_from_process_graph from openeo_driver.save_result import SaveResult, to_save_result -from openeo_driver.users import User, user_id_b64_decode, user_id_b64_encode, is_user_id_b64 +from openeo_driver.users import User, user_id_b64_decode, user_id_b64_encode from openeo_driver.users.auth import HttpAuthHandler from openeo_driver.util.compat import function_has_argument, filter_supported_kwargs from openeo_driver.util.geometry import BoundingBox, reproject_geometry @@ -1492,26 +1493,32 @@ def _stream_from_s3(s3_url, *, filename, mimetype: Optional[str], bytes_range: O @blueprint.route("/jobs//results/items///", methods=["GET"]) def get_job_result_item_signed(job_id, user_base64, secure_key, item_id): - if not is_user_id_b64(user_base64) or not is_secure_key(secure_key): + try: + user_id = user_id_b64_decode(user_base64) + except (binascii.Error, UnicodeDecodeError): + user_id = None + if user_id is None or not is_secure_key(secure_key): full_item_id = "/".join([user_base64, secure_key, item_id]) user = auth_handler.get_user_from_bearer_token(request) return _get_job_result_item(job_id, full_item_id, user.user_id) expires = request.args.get('expires') signer = get_backend_config().url_signer - user_id = user_id_b64_decode(user_base64) signer.verify_job_item(signature=secure_key, job_id=job_id, user_id=user_id, item_id=item_id, expires=expires) return _get_job_result_item(job_id, item_id, user_id) @api_endpoint @blueprint.route("/jobs//results/items11///", methods=["GET"]) def get_job_result_item11_signed(job_id, user_base64, secure_key, item_id): - if not is_user_id_b64(user_base64) or not is_secure_key(secure_key): + try: + user_id = user_id_b64_decode(user_base64) + except (binascii.Error, UnicodeDecodeError): + user_id = None + if user_id is None or not is_secure_key(secure_key): full_item_id = "/".join([user_base64, secure_key, item_id]) user = auth_handler.get_user_from_bearer_token(request) return _get_job_result_item11(job_id, full_item_id, user.user_id) expires = request.args.get('expires') signer = get_backend_config().url_signer - user_id = user_id_b64_decode(user_base64) signer.verify_job_item(signature=secure_key, job_id=job_id, user_id=user_id, item_id=item_id, expires=expires) return _get_job_result_item11(job_id, item_id, user_id) @@ -1936,13 +1943,16 @@ def download_job_result(job_id, filename, user: User): @blueprint.route("/jobs//results/assets///", methods=["GET"]) def download_job_result_signed(job_id, user_base64, secure_key, filename): - if not is_user_id_b64(user_base64) or not is_secure_key(secure_key): + try: + user_id = user_id_b64_decode(user_base64) + except (binascii.Error, UnicodeDecodeError): + user_id = None + if user_id is None or not is_secure_key(secure_key): full_filename = "/".join([user_base64, secure_key, filename]) user = auth_handler.get_user_from_bearer_token(request) return _download_job_result(job_id=job_id, filename=full_filename, user_id=user.user_id) expires = request.args.get('expires') signer = get_backend_config().url_signer - user_id = user_id_b64_decode(user_base64) signer.verify_job_asset( signature=secure_key, job_id=job_id, user_id=user_id, filename=filename, expires=expires From 9d5f3ed077d01a76a7535fc2b3970ca7d7f60a6c Mon Sep 17 00:00:00 2001 From: Emile Sonneveld Date: Wed, 17 Jun 2026 10:36:04 +0200 Subject: [PATCH 6/7] CredentialsInvalidException --- openeo_driver/views.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/openeo_driver/views.py b/openeo_driver/views.py index 1385266d..c02ac1ff 100644 --- a/openeo_driver/views.py +++ b/openeo_driver/views.py @@ -58,6 +58,7 @@ from openeo_driver.datacube import DriverMlModel from openeo_driver.dry_run import DryRunDataTracer from openeo_driver.errors import ( + CredentialsInvalidException, FeatureUnsupportedException, FilePathInvalidException, InternalException, @@ -1501,6 +1502,8 @@ def get_job_result_item_signed(job_id, user_base64, secure_key, item_id): full_item_id = "/".join([user_base64, secure_key, item_id]) user = auth_handler.get_user_from_bearer_token(request) return _get_job_result_item(job_id, full_item_id, user.user_id) + if not is_secure_key(secure_key): + raise CredentialsInvalidException() expires = request.args.get('expires') signer = get_backend_config().url_signer signer.verify_job_item(signature=secure_key, job_id=job_id, user_id=user_id, item_id=item_id, expires=expires) @@ -1517,6 +1520,8 @@ def get_job_result_item11_signed(job_id, user_base64, secure_key, item_id): full_item_id = "/".join([user_base64, secure_key, item_id]) user = auth_handler.get_user_from_bearer_token(request) return _get_job_result_item11(job_id, full_item_id, user.user_id) + if not is_secure_key(secure_key): + raise CredentialsInvalidException() expires = request.args.get('expires') signer = get_backend_config().url_signer signer.verify_job_item(signature=secure_key, job_id=job_id, user_id=user_id, item_id=item_id, expires=expires) @@ -1951,6 +1956,8 @@ def download_job_result_signed(job_id, user_base64, secure_key, filename): full_filename = "/".join([user_base64, secure_key, filename]) user = auth_handler.get_user_from_bearer_token(request) return _download_job_result(job_id=job_id, filename=full_filename, user_id=user.user_id) + if not is_secure_key(secure_key): + raise CredentialsInvalidException() expires = request.args.get('expires') signer = get_backend_config().url_signer signer.verify_job_asset( From 9c6f79152c484ce04603861b9dafce82196e1256 Mon Sep 17 00:00:00 2001 From: Emile Sonneveld Date: Wed, 17 Jun 2026 10:51:50 +0200 Subject: [PATCH 7/7] fix --- openeo_driver/views.py | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/openeo_driver/views.py b/openeo_driver/views.py index c02ac1ff..1802f76a 100644 --- a/openeo_driver/views.py +++ b/openeo_driver/views.py @@ -1497,13 +1497,9 @@ def get_job_result_item_signed(job_id, user_base64, secure_key, item_id): try: user_id = user_id_b64_decode(user_base64) except (binascii.Error, UnicodeDecodeError): - user_id = None - if user_id is None or not is_secure_key(secure_key): full_item_id = "/".join([user_base64, secure_key, item_id]) user = auth_handler.get_user_from_bearer_token(request) return _get_job_result_item(job_id, full_item_id, user.user_id) - if not is_secure_key(secure_key): - raise CredentialsInvalidException() expires = request.args.get('expires') signer = get_backend_config().url_signer signer.verify_job_item(signature=secure_key, job_id=job_id, user_id=user_id, item_id=item_id, expires=expires) @@ -1515,13 +1511,9 @@ def get_job_result_item11_signed(job_id, user_base64, secure_key, item_id): try: user_id = user_id_b64_decode(user_base64) except (binascii.Error, UnicodeDecodeError): - user_id = None - if user_id is None or not is_secure_key(secure_key): full_item_id = "/".join([user_base64, secure_key, item_id]) user = auth_handler.get_user_from_bearer_token(request) return _get_job_result_item11(job_id, full_item_id, user.user_id) - if not is_secure_key(secure_key): - raise CredentialsInvalidException() expires = request.args.get('expires') signer = get_backend_config().url_signer signer.verify_job_item(signature=secure_key, job_id=job_id, user_id=user_id, item_id=item_id, expires=expires) @@ -1951,13 +1943,9 @@ def download_job_result_signed(job_id, user_base64, secure_key, filename): try: user_id = user_id_b64_decode(user_base64) except (binascii.Error, UnicodeDecodeError): - user_id = None - if user_id is None or not is_secure_key(secure_key): full_filename = "/".join([user_base64, secure_key, filename]) user = auth_handler.get_user_from_bearer_token(request) return _download_job_result(job_id=job_id, filename=full_filename, user_id=user.user_id) - if not is_secure_key(secure_key): - raise CredentialsInvalidException() expires = request.args.get('expires') signer = get_backend_config().url_signer signer.verify_job_asset(