From 99471359ff787afc0f06697e97c818b20e00f4b7 Mon Sep 17 00:00:00 2001 From: Benjamin-bc-gov Date: Wed, 19 Aug 2026 22:56:44 -0600 Subject: [PATCH 1/5] fixed bug download bug --- .../v2/business/business_filings/business_documents.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/legal-api/src/legal_api/resources/v2/business/business_filings/business_documents.py b/legal-api/src/legal_api/resources/v2/business/business_filings/business_documents.py index 61fb8bd556..2551225a78 100644 --- a/legal-api/src/legal_api/resources/v2/business/business_filings/business_documents.py +++ b/legal-api/src/legal_api/resources/v2/business/business_filings/business_documents.py @@ -29,6 +29,7 @@ from business_common.utils.legislation_datetime import LegislationDatetime from business_model.models import Business, Document, UserRoles from business_model.models import Filing as FilingModel +from business_account import AccountService from legal_api.core import Filing from legal_api.exceptions import ErrorCode, get_error_message from legal_api.reports import get_pdf @@ -40,6 +41,7 @@ from legal_api.utils.auth import jwt from legal_api.utils.util import cors_preflight + DOCUMENTS_BASE_ROUTE: Final[str] = "//filings//documents" PARAM_REPORT_TYPE: Final[str] = "reportType" PARAM_DOC_CLASS: Final[str] = "documentClass" @@ -223,8 +225,8 @@ def _get_receipt(business: Business, filing: Filing, token): filing.filing_type == "noticeOfWithdrawal" ): effective_date = LegislationDatetime.format_as_report_string(filing.storage.effective_date) - - headers = {"Authorization": "Bearer " + token} + service_token = AccountService.get_bearer_token() + headers = {"Authorization": "Bearer " + service_token} add_account_linking_key_header(headers) corp_name = _get_corp_name(business, filing.storage) From f58a215b04c0be38d56c7fb7ec479a2a1a127469 Mon Sep 17 00:00:00 2001 From: Benjamin-bc-gov Date: Wed, 19 Aug 2026 23:17:06 -0600 Subject: [PATCH 2/5] wrote test for the fix --- .../test_filing_documents.py | 51 +++++++++++++++++-- 1 file changed, 48 insertions(+), 3 deletions(-) diff --git a/legal-api/tests/unit/resources/v2/test_business_filings/test_filing_documents.py b/legal-api/tests/unit/resources/v2/test_business_filings/test_filing_documents.py index 26b2f56cdf..94ec982077 100644 --- a/legal-api/tests/unit/resources/v2/test_business_filings/test_filing_documents.py +++ b/legal-api/tests/unit/resources/v2/test_business_filings/test_filing_documents.py @@ -1962,7 +1962,7 @@ def test_temp_document_list_for_various_filing_states(app, mocker, session, clie assert rv_data == expected -def test_get_receipt(session, client, jwt, requests_mock): +def test_get_receipt(session, client, jwt, requests_mock, mocker): """Assert that a receipt is generated.""" from legal_api.resources.v2.business.business_filings.business_documents import _get_receipt @@ -1990,6 +1990,7 @@ def test_get_receipt(session, client, jwt, requests_mock): json={'foo': 'bar'}, status_code=HTTPStatus.CREATED) + mocker.patch('business_account.AccountService.get_bearer_token', return_value='service-token') token = helper_create_jwt(jwt, roles=[STAFF_ROLE], username='username') content, status_code = _get_receipt(business, filing_core, token) @@ -1998,7 +1999,7 @@ def test_get_receipt(session, client, jwt, requests_mock): assert requests_mock.called_once -def test_get_receipt_request_mock(session, client, jwt, requests_mock): +def test_get_receipt_request_mock(session, client, jwt, requests_mock, mocker): """Assert that a receipt is generated.""" from legal_api.resources.v2.business.business_filings.business_documents import _get_receipt @@ -2025,6 +2026,8 @@ def test_get_receipt_request_mock(session, client, jwt, requests_mock): json={'foo': 'bar'}, status_code=HTTPStatus.CREATED) + mocker.patch('business_account.AccountService.get_bearer_token', return_value='service-token') + rv = client.get(f'/api/v2/businesses/{identifier}/filings/{filing.id}/documents/receipt', headers=create_header(jwt, [STAFF_ROLE], @@ -2036,7 +2039,7 @@ def test_get_receipt_request_mock(session, client, jwt, requests_mock): assert requests_mock.called_once -def test_get_receipt_forwards_account_linking_key(session, client, jwt, requests_mock): +def test_get_receipt_forwards_account_linking_key(session, client, jwt, requests_mock, mocker): """Assert that the receipt call forwards the Account-Linking-Key header when present on the request.""" identifier = 'CP7654321' business = factory_business(identifier) @@ -2060,6 +2063,8 @@ def test_get_receipt_forwards_account_linking_key(session, client, jwt, requests json={'foo': 'bar'}, status_code=HTTPStatus.CREATED) + mocker.patch('business_account.AccountService.get_bearer_token', return_value='service-token') + rv = client.get(f'/api/v2/businesses/{identifier}/filings/{filing.id}/documents/receipt', headers=create_header(jwt, [STAFF_ROLE], @@ -2193,6 +2198,46 @@ def test_temp_document_list_for_now(app, mocker, session, client, jwt, monkeypat assert rv_data == expected +def test_receipt_download_uses_service_token(session, client, jwt, mocker): + """Assert receipt download uses the service account token, not the caller's JWT token. + + Reproduces issue #34460: Staff-created filings with waiveFees=true have a payment_token + owned by the Staff account in Pay API. External users were getting 403 because their token + was forwarded to Pay API. The fix uses AccountService.get_bearer_token() instead. + """ + from unittest.mock import MagicMock + from legal_api.resources.v2.business.business_filings import business_documents + + identifier = "BC7654321" + business = factory_business(identifier, entity_type=Business.LegalTypes.BCOMP.value) + + filing_json = copy.deepcopy(FILING_HEADER) + filing_json["filing"]["header"]["name"] = "consentContinuationOut" + filing_json["filing"]["consentContinuationOut"] = copy.deepcopy(CONSENT_CONTINUATION_OUT) + filing_date = datetime.now(UTC) + filing = factory_completed_filing(business, filing_json, filing_date=filing_date, + payment_token="staff-payment-token-999") + + service_token = "service-account-token-abc" + mocker.patch("business_account.AccountService.get_bearer_token", return_value=service_token) + mocker.patch.object(business_documents, "_is_document_available", return_value=True) + + mock_pay_response = MagicMock() + mock_pay_response.status_code = HTTPStatus.CREATED + mock_pay_response.content = b"%PDF-receipt" + mock_post = mocker.patch("requests.post", return_value=mock_pay_response) + + rv = client.get( + f"/api/v2/businesses/{identifier}/filings/{filing.id}/documents/receipt", + headers=create_header(jwt, [STAFF_ROLE], identifier, **{"accept": "application/pdf"}), + ) + + assert rv.status_code == HTTPStatus.CREATED + # Assert Pay API was called with the service token, not the caller's token + call_headers = mock_post.call_args.kwargs["headers"] + assert call_headers["Authorization"] == f"Bearer {service_token}" + + def test_get_static_document_by_file_key_shape(session, client, jwt, mocker): """Assert static documents are served from DRS.""" from unittest.mock import MagicMock, patch From b1d3e36cf327c8a15d42ade3b0a08f41478489a8 Mon Sep 17 00:00:00 2001 From: Benjamin-bc-gov Date: Wed, 19 Aug 2026 23:18:47 -0600 Subject: [PATCH 3/5] version bumped --- legal-api/pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/legal-api/pyproject.toml b/legal-api/pyproject.toml index 2222275628..cb119ee4ea 100644 --- a/legal-api/pyproject.toml +++ b/legal-api/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "legal-api" -version = "3.1.14" +version = "3.1.15" description = "" authors = [ {name = "thor",email = "1042854+thorwolpert@users.noreply.github.com"} From a148931516171efa4bbae31b4ea67a3e0fd5cfd3 Mon Sep 17 00:00:00 2001 From: Benjamin-bc-gov Date: Wed, 19 Aug 2026 23:19:25 -0600 Subject: [PATCH 4/5] fixed lint --- .../v2/business/business_filings/business_documents.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/legal-api/src/legal_api/resources/v2/business/business_filings/business_documents.py b/legal-api/src/legal_api/resources/v2/business/business_filings/business_documents.py index 2551225a78..ebf0fb5d44 100644 --- a/legal-api/src/legal_api/resources/v2/business/business_filings/business_documents.py +++ b/legal-api/src/legal_api/resources/v2/business/business_filings/business_documents.py @@ -26,10 +26,10 @@ from flask_pydantic import validate as pydantic_validate from pydantic import BaseModel +from business_account import AccountService from business_common.utils.legislation_datetime import LegislationDatetime from business_model.models import Business, Document, UserRoles from business_model.models import Filing as FilingModel -from business_account import AccountService from legal_api.core import Filing from legal_api.exceptions import ErrorCode, get_error_message from legal_api.reports import get_pdf @@ -41,7 +41,6 @@ from legal_api.utils.auth import jwt from legal_api.utils.util import cors_preflight - DOCUMENTS_BASE_ROUTE: Final[str] = "//filings//documents" PARAM_REPORT_TYPE: Final[str] = "reportType" PARAM_DOC_CLASS: Final[str] = "documentClass" @@ -225,7 +224,7 @@ def _get_receipt(business: Business, filing: Filing, token): filing.filing_type == "noticeOfWithdrawal" ): effective_date = LegislationDatetime.format_as_report_string(filing.storage.effective_date) - service_token = AccountService.get_bearer_token() + service_token = AccountService.get_bearer_token() headers = {"Authorization": "Bearer " + service_token} add_account_linking_key_header(headers) From 7539de47c331bbaf84017ab321aa21d68c2d2cd3 Mon Sep 17 00:00:00 2001 From: Benjamin-bc-gov Date: Thu, 20 Aug 2026 14:21:58 -0600 Subject: [PATCH 5/5] made PR changes --- .../business_filings/business_documents.py | 4 +- .../test_filing_documents.py | 53 +++---------------- 2 files changed, 10 insertions(+), 47 deletions(-) diff --git a/legal-api/src/legal_api/resources/v2/business/business_filings/business_documents.py b/legal-api/src/legal_api/resources/v2/business/business_filings/business_documents.py index ebf0fb5d44..7cd095e3a3 100644 --- a/legal-api/src/legal_api/resources/v2/business/business_filings/business_documents.py +++ b/legal-api/src/legal_api/resources/v2/business/business_filings/business_documents.py @@ -119,7 +119,7 @@ def get_documents(identifier: str, # noqa: PLR0911, PLR0912 if legal_filing_name: if legal_filing_name.lower().startswith("receipt"): - return _get_receipt(business, filing, jwt.get_token_auth_header()) + return _get_receipt(business, filing) return get_pdf(filing.storage, legal_filing_name) elif file_key and (document := Document.find_by_file_key(file_key)): @@ -207,7 +207,7 @@ def _get_document_list(business: Business, filing: Filing): return jsonify(document_list), HTTPStatus.OK -def _get_receipt(business: Business, filing: Filing, token): +def _get_receipt(business: Business, filing: Filing): """Get the receipt for the filing.""" if filing.status not in ( Filing.Status.COMPLETED, diff --git a/legal-api/tests/unit/resources/v2/test_business_filings/test_filing_documents.py b/legal-api/tests/unit/resources/v2/test_business_filings/test_filing_documents.py index 94ec982077..6b893e0898 100644 --- a/legal-api/tests/unit/resources/v2/test_business_filings/test_filing_documents.py +++ b/legal-api/tests/unit/resources/v2/test_business_filings/test_filing_documents.py @@ -1990,13 +1990,14 @@ def test_get_receipt(session, client, jwt, requests_mock, mocker): json={'foo': 'bar'}, status_code=HTTPStatus.CREATED) - mocker.patch('business_account.AccountService.get_bearer_token', return_value='service-token') - token = helper_create_jwt(jwt, roles=[STAFF_ROLE], username='username') + service_token = 'service-token-abc' + mocker.patch('business_account.AccountService.get_bearer_token', return_value=service_token) - content, status_code = _get_receipt(business, filing_core, token) + content, status_code = _get_receipt(business, filing_core) assert status_code == HTTPStatus.CREATED assert requests_mock.called_once + assert requests_mock.last_request.headers.get('Authorization') == f'Bearer {service_token}' def test_get_receipt_request_mock(session, client, jwt, requests_mock, mocker): @@ -2026,7 +2027,8 @@ def test_get_receipt_request_mock(session, client, jwt, requests_mock, mocker): json={'foo': 'bar'}, status_code=HTTPStatus.CREATED) - mocker.patch('business_account.AccountService.get_bearer_token', return_value='service-token') + service_token = 'service-token-abc' + mocker.patch('business_account.AccountService.get_bearer_token', return_value=service_token) rv = client.get(f'/api/v2/businesses/{identifier}/filings/{filing.id}/documents/receipt', headers=create_header(jwt, @@ -2036,7 +2038,8 @@ def test_get_receipt_request_mock(session, client, jwt, requests_mock, mocker): ) assert rv.status_code == HTTPStatus.CREATED - assert requests_mock.called_once + assert requests_mock.called + assert requests_mock.last_request.headers.get('Authorization') == f'Bearer {service_token}' def test_get_receipt_forwards_account_linking_key(session, client, jwt, requests_mock, mocker): @@ -2198,46 +2201,6 @@ def test_temp_document_list_for_now(app, mocker, session, client, jwt, monkeypat assert rv_data == expected -def test_receipt_download_uses_service_token(session, client, jwt, mocker): - """Assert receipt download uses the service account token, not the caller's JWT token. - - Reproduces issue #34460: Staff-created filings with waiveFees=true have a payment_token - owned by the Staff account in Pay API. External users were getting 403 because their token - was forwarded to Pay API. The fix uses AccountService.get_bearer_token() instead. - """ - from unittest.mock import MagicMock - from legal_api.resources.v2.business.business_filings import business_documents - - identifier = "BC7654321" - business = factory_business(identifier, entity_type=Business.LegalTypes.BCOMP.value) - - filing_json = copy.deepcopy(FILING_HEADER) - filing_json["filing"]["header"]["name"] = "consentContinuationOut" - filing_json["filing"]["consentContinuationOut"] = copy.deepcopy(CONSENT_CONTINUATION_OUT) - filing_date = datetime.now(UTC) - filing = factory_completed_filing(business, filing_json, filing_date=filing_date, - payment_token="staff-payment-token-999") - - service_token = "service-account-token-abc" - mocker.patch("business_account.AccountService.get_bearer_token", return_value=service_token) - mocker.patch.object(business_documents, "_is_document_available", return_value=True) - - mock_pay_response = MagicMock() - mock_pay_response.status_code = HTTPStatus.CREATED - mock_pay_response.content = b"%PDF-receipt" - mock_post = mocker.patch("requests.post", return_value=mock_pay_response) - - rv = client.get( - f"/api/v2/businesses/{identifier}/filings/{filing.id}/documents/receipt", - headers=create_header(jwt, [STAFF_ROLE], identifier, **{"accept": "application/pdf"}), - ) - - assert rv.status_code == HTTPStatus.CREATED - # Assert Pay API was called with the service token, not the caller's token - call_headers = mock_post.call_args.kwargs["headers"] - assert call_headers["Authorization"] == f"Bearer {service_token}" - - def test_get_static_document_by_file_key_shape(session, client, jwt, mocker): """Assert static documents are served from DRS.""" from unittest.mock import MagicMock, patch