Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 15 additions & 2 deletions stac_fastapi/eodag/extensions/data_download.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,9 @@
from eodag.api.core import EODataAccessGateway
from eodag.api.product._product import EOProduct
from eodag.api.product.metadata_mapping import ONLINE_STATUS, STAGING_STATUS, get_metadata_path_value
from eodag.utils.exceptions import EodagError
from fastapi import APIRouter, FastAPI, Path, Request
from fastapi.responses import StreamingResponse
from fastapi.responses import RedirectResponse, StreamingResponse
from stac_fastapi.api.errors import NotFoundError
from stac_fastapi.api.routes import create_async_endpoint
from stac_fastapi.types.extension import ApiExtension
Expand Down Expand Up @@ -100,7 +101,7 @@ def get_data(
item_id: str,
asset_name: Optional[str],
request: Request,
) -> StreamingResponse:
) -> StreamingResponse | RedirectResponse:
"""Download an asset"""

dag = cast(EODataAccessGateway, request.app.state.dag) # type: ignore
Expand Down Expand Up @@ -181,6 +182,18 @@ def get_data(
raise NotFoundError(f"Item {item_id} does not exist. Please order it first") from e
raise NotFoundError(e) from e

if product.downloader_auth and asset_name and asset_name != "downloadLink":
asset_values = product.assets[asset_name]
# return presigned url if available
try:
presigned_url = product.downloader_auth.presign_url(asset_values)
headers = {"content-disposition": f"attachment; filename={asset_name}"}
return RedirectResponse(presigned_url, status_code=302, headers=headers)
except NotImplementedError:
logger.info("Presigned urls not supported for %s with auth %s", product.downloader, auth)
except EodagError:
logger.info("Presigned url could not be fetched for %s", asset_name)

try:
s = product.downloader._stream_download_dict(
product,
Expand Down
20 changes: 18 additions & 2 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
from eodag.api.product.metadata_mapping import OFFLINE_STATUS, ONLINE_STATUS
from eodag.api.search_result import SearchResult
from eodag.config import PluginConfig
from eodag.plugins.authentication.aws_auth import AwsAuth
from eodag.plugins.authentication.base import Authentication
from eodag.plugins.authentication.openid_connect import OIDCRefreshTokenBase
from eodag.plugins.authentication.token import TokenAuth
Expand Down Expand Up @@ -337,7 +338,7 @@ def mock_http_base_stream_download_dict(mocker):
@pytest.fixture(scope="function")
def mock_order(mocker):
"""
Mocks the `HTTPDownload` method of the `HTTPDownload` download plugin.
Mocks the `order` method of the `HTTPDownload` download plugin.
"""
return mocker.patch.object(HTTPDownload, "order")

Expand Down Expand Up @@ -374,6 +375,14 @@ def mock_oidc_token_exchange_auth_authenticate(mocker):
return mocker.patch.object(OIDCTokenExchangeAuth, "authenticate")


@pytest.fixture(scope="function")
def mock_aws_authenticate(mocker, app):
"""
Mocks the `authenticate` method of the `AwsAuth` plugin.
"""
return mocker.patch.object(AwsAuth, "authenticate")


@pytest.fixture(scope="function")
def tmp_dir():
"""
Expand All @@ -396,6 +405,7 @@ async def _request_valid_raw(
search_call_count: Optional[int] = None,
search_result: Optional[SearchResult] = None,
expected_status_code: int = 200,
follow_redirects: bool = True,
):
if search_result:
mock_search.return_value = search_result
Expand All @@ -406,7 +416,7 @@ async def _request_valid_raw(
method,
url,
json=post_data,
follow_redirects=True,
follow_redirects=follow_redirects,
headers={"Content-Type": "application/json"} if method == "POST" else {},
)

Expand Down Expand Up @@ -586,6 +596,12 @@ async def _request_accepted(url: str):
return _request_accepted


@pytest.fixture(scope="function")
def mock_presign_url(mocker):
"""Fixture for the presign_url function"""
return mocker.patch.object(AwsAuth, "presign_url")


@dataclass
class TestDefaults:
"""
Expand Down
34 changes: 34 additions & 0 deletions tests/test_download.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
from eodag import SearchResult
from eodag.api.product import EOProduct
from eodag.config import PluginConfig
from eodag.plugins.authentication.aws_auth import AwsAuth
from eodag.plugins.download.aws import AwsDownload
from eodag.plugins.download.http import HTTPDownload

from stac_fastapi.eodag.config import get_settings
Expand Down Expand Up @@ -104,3 +106,35 @@ async def test_download_auto_order_whitelist(

# restore the original auto_order_whitelist setting
get_settings().auto_order_whitelist = auto_order_whitelist


async def test_download_redirect_response(request_valid_raw, mock_search, mock_presign_url, mock_aws_authenticate):
"""test that a reponse with status code 302 is returned if presigned urls are used"""
product_type = "MO_GLOBAL_ANALYSISFORECAST_PHY_001_024"
product = EOProduct(
"cop_marine",
dict(
geometry="POINT (0 0)",
title="dummy_product",
id="dummy",
),
productType=product_type,
)
product.assets.update({"a1": {"href": "https://s3.waw3-1.cloudferro.com/b1/a1/a1.json"}})
product.assets.update({"a2": {"href": "https://s3.waw3-1.cloudferro.com/b1/a2/a2.json"}})

config = PluginConfig()
config.priority = 0
downloader = AwsDownload("cop_marine", config)
download_auth = AwsAuth("cop_marine", config)
product.register_downloader(downloader=downloader, authenticator=download_auth)
mock_search.return_value = SearchResult([product])

mock_presign_url.return_value = "s3://s3.abc.com/a1/b1?AWSAccesskeyId=123&expires=1543649"

await request_valid_raw(
f"data/cop_marine/{product_type}/foo/a1",
search_result=SearchResult([product]),
expected_status_code=302,
follow_redirects=False,
)
Loading