Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit b4ae18a

Browse files
authored
33654 - account linking key forwarding for auth and pay (#4682)
* 33654 - account linking key forwarding for auth and pay * sonarqube feedback * updates for test backwards compatibility for existing mocks / clean up * lint / update test mocks / revert request context account linking header work around * clean up * sonarqube duplication feedback / lint * PR Feedback
1 parent 3f8ffa6 commit b4ae18a

13 files changed

Lines changed: 245 additions & 89 deletions

File tree

legal-api/src/legal_api/resources/v2/business/business_filings/business_documents.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
from legal_api.resources.v2.business.bp import bp
3737
from legal_api.services import MinioService, authorized
3838
from legal_api.services import doc_service as client_doc_service
39+
from legal_api.services.request_context import add_account_linking_key_header
3940
from legal_api.utils.auth import jwt
4041
from legal_api.utils.util import cors_preflight
4142

@@ -232,6 +233,7 @@ def _get_receipt(business: Business, filing: Filing, token):
232233
effective_date = LegislationDatetime.format_as_report_string(filing.storage.effective_date)
233234

234235
headers = {"Authorization": "Bearer " + token}
236+
add_account_linking_key_header(headers)
235237

236238
corp_name = _get_corp_name(business, filing.storage)
237239

legal-api/src/legal_api/resources/v2/business/business_filings/business_filings.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@
7272
from legal_api.services.event_publisher import publish_to_queue
7373
from legal_api.services.filings import validate
7474
from legal_api.services.permissions import PermissionService
75+
from legal_api.services.request_context import add_account_linking_key_header
7576
from legal_api.services.utils import get_str
7677
from legal_api.utils.auth import jwt
7778

@@ -271,6 +272,7 @@ def patch_filings(identifier, filing_id=None):
271272
payment_svc_url = "{}/{}".format(current_app.config.get("PAYMENT_SVC_URL"), filing.payment_token)
272273
token = jwt.get_token_auth_header()
273274
headers = {"Authorization": "Bearer " + token}
275+
add_account_linking_key_header(headers)
274276
rv = requests.delete(url=payment_svc_url, headers=headers, timeout=20.0)
275277
if rv.status_code in (HTTPStatus.OK, HTTPStatus.ACCEPTED):
276278
filing.reset_filing_to_draft()
@@ -387,6 +389,7 @@ def get_payment_update(filing_dict: dict):
387389
"Authorization": f"Bearer {jwt.get_token_auth_header()}",
388390
"Content-Type": "application/json"
389391
}
392+
add_account_linking_key_header(headers)
390393
payment_svc_url = current_app.config.get("PAYMENT_SVC_URL")
391394

392395
if payment_token := filing_dict.get("filing", {}).get("header", {}).get("paymentToken"):
@@ -1057,6 +1060,7 @@ def create_invoice(business: Business, # noqa: PLR0912, PLR0915
10571060
token = user_jwt.get_token_auth_header()
10581061
headers = {"Authorization": "Bearer " + token,
10591062
"Content-Type": "application/json"}
1063+
add_account_linking_key_header(headers)
10601064
current_app.logger.debug(f"Pay api call - url: {payment_svc_url}, payload: {payload}")
10611065
rv = requests.post(url=payment_svc_url,
10621066
json=payload,

legal-api/src/legal_api/resources/v2/business/business_tasks.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
from business_common.utils.legislation_datetime import LegislationDatetime
2929
from business_model.models import Business, Filing
3030
from legal_api.services import check_warnings, namex
31+
from legal_api.services.request_context import add_account_linking_key_header
3132
from legal_api.services.warnings.business.business_checks import BusinessWarningCodes, WarningType
3233
from legal_api.utils.auth import jwt
3334

@@ -130,6 +131,7 @@ def construct_task_list(business: Business): # noqa: PLR0915
130131
"Authorization": f"Bearer {jwt.get_token_auth_header()}",
131132
"Content-Type": "application/json"
132133
}
134+
add_account_linking_key_header(headers)
133135
pay_response = requests.get(
134136
url=f'{current_app.config.get("PAYMENT_SVC_URL")}/{filing.payment_token}',
135137
headers=headers

legal-api/src/legal_api/services/authz.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333
are_digital_credentials_allowed,
3434
get_digital_credentials_preconditions,
3535
)
36-
from legal_api.services.request_context import get_request_context
36+
from legal_api.services.request_context import add_account_linking_key_header, get_request_context
3737
from legal_api.services.warnings.business.business_checks import WarningType
3838

3939
SYSTEM_ROLE = "system"
@@ -86,6 +86,7 @@ def _call_auth_api(path: str, token: str) -> Response:
8686
auth_url += path
8787

8888
headers = {"Authorization": "Bearer " + token}
89+
add_account_linking_key_header(headers)
8990
try:
9091
http = Session()
9192
retries = Retry(total=5,

legal-api/src/legal_api/services/request_context.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
class RequestContext:
2424
account_id: str | None = None
2525
user: User | None = None
26+
account_linking_key: str | None = None
2627

2728

2829
def build_from_flask() -> RequestContext:
@@ -33,13 +34,16 @@ def build_from_flask() -> RequestContext:
3334
# Account header (configurable)
3435
account_id = request.headers.get("Account-Id", None)
3536

37+
account_linking_key = request.headers.get("Account-Linking-Key", None)
38+
3639
# Token info and user
3740
token_info = getattr(g, "jwt_oidc_token_info", None)
3841
user = User.get_or_create_user_by_jwt(token_info) if token_info else None
3942

4043
return RequestContext(
4144
account_id=account_id,
4245
user=user,
46+
account_linking_key=account_linking_key,
4347
)
4448

4549

@@ -52,3 +56,12 @@ def get_request_context() -> RequestContext:
5256
rc = build_from_flask()
5357
g.request_context = rc
5458
return rc
59+
60+
61+
def add_account_linking_key_header(headers: dict) -> None:
62+
"""Forward the incoming Account-Linking-Key header to an outgoing auth-api/pay-api call, if present."""
63+
if not has_request_context():
64+
return
65+
66+
if account_linking_key := request.headers.get("Account-Linking-Key"):
67+
headers["Account-Linking-Key"] = account_linking_key

legal-api/tests/unit/core/test_filing_ledger.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,8 +75,8 @@ def test_simple_ledger_search(app, session, client, jwt, monkeypatch, mock_drs_s
7575
token = helper_create_jwt(jwt, roles=[STAFF_ROLE], username='testuser')
7676
headers = {'Authorization': 'Bearer ' + token, 'Account-Id': 1}
7777

78-
def mock_auth(one, two): # pylint: disable=unused-argument; mocks of library methods
79-
return headers[one]
78+
def mock_auth(key, default=None):
79+
return headers.get(key, default)
8080

8181
# test
8282
with app.test_request_context():

legal-api/tests/unit/resources/v2/test_business_filings/test_filing_documents.py

Lines changed: 46 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,11 @@
7171
}
7272

7373

74+
def mock_auth(headers: dict):
75+
"""Return a stub for flask.request.headers.get."""
76+
return lambda key, default=None: headers.get(key, default)
77+
78+
7479
def basic_test_helper():
7580
identifier = 'CP7654321'
7681
business = factory_business(identifier)
@@ -1637,12 +1642,9 @@ def test_document_list_for_various_filing_states(app, session, mocker, client, j
16371642

16381643
account_id: str = '1'
16391644
headers=create_header(jwt, [STAFF_ROLE], identifier, account_id=account_id)
1640-
def mock_auth(one, two): # pylint: disable=unused-argument; mocks of library methods
1641-
return headers[one]
1642-
16431645
# test
16441646
with app.test_request_context():
1645-
monkeypatch.setattr('flask.request.headers.get', mock_auth)
1647+
monkeypatch.setattr('flask.request.headers.get', mock_auth(headers))
16461648
account_products_mock = []
16471649
with requests_mock.Mocker() as m:
16481650
mock_url = f"{app.config['AUTH_SVC_URL']}/orgs/{account_id}/products?include_hidden=true"
@@ -1765,11 +1767,8 @@ def test_continuation_out_uploaded_documents(app, session, client, jwt, monkeypa
17651767
account_id = '1'
17661768
headers = create_header(jwt, [STAFF_ROLE], identifier, account_id=account_id)
17671769

1768-
def mock_auth(one, two): # pylint: disable=unused-argument; mocks of library methods
1769-
return headers[one]
1770-
17711770
with app.test_request_context():
1772-
monkeypatch.setattr('flask.request.headers.get', mock_auth)
1771+
monkeypatch.setattr('flask.request.headers.get', mock_auth(headers))
17731772
with requests_mock.Mocker() as m:
17741773
mock_url = f"{app.config['AUTH_SVC_URL']}/orgs/{account_id}/products?include_hidden=true"
17751774
m.get(mock_url, json=[], status_code=HTTPStatus.OK)
@@ -1834,11 +1833,8 @@ def test_continuation_out_uploaded_documents_returned_for_non_staff(non_staff_ro
18341833
account_id = '1'
18351834
headers = create_header(jwt, [non_staff_role], identifier, account_id=account_id)
18361835

1837-
def mock_auth(one, two): # pylint: disable=unused-argument; mocks of library methods
1838-
return headers[one]
1839-
18401836
with app.test_request_context():
1841-
monkeypatch.setattr('flask.request.headers.get', mock_auth)
1837+
monkeypatch.setattr('flask.request.headers.get', mock_auth(headers))
18421838
with requests_mock.Mocker() as m:
18431839
m.get(f"{app.config.get('AUTH_SVC_URL')}/entities/{identifier}/authorizations",
18441840
json={'roles': ['view']}, status_code=HTTPStatus.OK)
@@ -1947,12 +1943,9 @@ def test_temp_document_list_for_various_filing_states(app, mocker, session, clie
19471943
filing.save()
19481944
account_id: str = '1'
19491945
headers=create_header(jwt, [STAFF_ROLE], temp_identifier, account_id=account_id)
1950-
def mock_auth(one, two): # pylint: disable=unused-argument; mocks of library methods
1951-
return headers[one]
1952-
19531946
# test
19541947
with app.test_request_context():
1955-
monkeypatch.setattr('flask.request.headers.get', mock_auth)
1948+
monkeypatch.setattr('flask.request.headers.get', mock_auth(headers))
19561949
account_products_mock = []
19571950
with requests_mock.Mocker() as m:
19581951
mock_url = f"{app.config['AUTH_SVC_URL']}/orgs/{account_id}/products?include_hidden=true"
@@ -2043,6 +2036,42 @@ def test_get_receipt_request_mock(session, client, jwt, requests_mock):
20432036
assert requests_mock.called_once
20442037

20452038

2039+
def test_get_receipt_forwards_account_linking_key(session, client, jwt, requests_mock):
2040+
"""Assert that the receipt call forwards the Account-Linking-Key header when present on the request."""
2041+
identifier = 'CP7654321'
2042+
business = factory_business(identifier)
2043+
filing_name = 'incorporationApplication'
2044+
payment_id = '12345'
2045+
2046+
filing_json = copy.deepcopy(FILING_HEADER)
2047+
filing_json['filing']['header']['name'] = filing_name
2048+
filing_json['filing'][filing_name] = INCORPORATION
2049+
filing_json['filing'].pop('business')
2050+
2051+
filing_date = datetime.now(UTC)
2052+
filing = factory_filing(business, filing_json, filing_date=filing_date)
2053+
filing.skip_status_listener = True
2054+
filing._status = 'PAID'
2055+
filing._payment_token = payment_id
2056+
filing._payment_completion_date = filing_date
2057+
filing.save()
2058+
2059+
pay_mock = requests_mock.post(f"{current_app.config.get('PAYMENT_SVC_URL')}/{payment_id}/receipts",
2060+
json={'foo': 'bar'},
2061+
status_code=HTTPStatus.CREATED)
2062+
2063+
rv = client.get(f'/api/v2/businesses/{identifier}/filings/{filing.id}/documents/receipt',
2064+
headers=create_header(jwt,
2065+
[STAFF_ROLE],
2066+
identifier,
2067+
**{'accept': 'application/pdf',
2068+
'Account-Linking-Key': 'test-linking-key'})
2069+
)
2070+
2071+
assert rv.status_code == HTTPStatus.CREATED
2072+
assert pay_mock.last_request.headers.get('Account-Linking-Key') == 'test-linking-key'
2073+
2074+
20462075
def test_get_receipt_no_receipt_ca(session, client, jwt, requests_mock):
20472076
"""Assert that a receipt is generated."""
20482077
from legal_api.resources.v2.business.business_filings.business_documents import _get_receipt
@@ -2132,12 +2161,9 @@ def test_temp_document_list_for_now(app, mocker, session, client, jwt, monkeypat
21322161
filing.save()
21332162
account_id: str = '1'
21342163
headers=create_header(jwt, [STAFF_ROLE], temp_identifier, account_id=account_id)
2135-
def mock_auth(one, two): # pylint: disable=unused-argument; mocks of library methods
2136-
return headers[one]
2137-
21382164
# test
21392165
with app.test_request_context():
2140-
monkeypatch.setattr('flask.request.headers.get', mock_auth)
2166+
monkeypatch.setattr('flask.request.headers.get', mock_auth(headers))
21412167
account_products_mock = []
21422168
with requests_mock.Mocker() as m:
21432169
mock_url = f"{app.config['AUTH_SVC_URL']}/orgs/{account_id}/products?include_hidden=true"

legal-api/tests/unit/resources/v2/test_business_filings/test_filings.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1809,6 +1809,34 @@ def test_coa(session, requests_mock, client, jwt, test_name, legal_type, identif
18091809
assert 'futureEffectiveDate' not in rv.json['filing']['header']
18101810

18111811

1812+
def test_create_invoice_forwards_account_linking_key(session, requests_mock, client, jwt):
1813+
"""Assert that create_invoice forwards the Account-Linking-Key header when present on the request."""
1814+
identifier = 'CP1234567'
1815+
coa = copy.deepcopy(FILING_HEADER)
1816+
coa['filing']['header']['name'] = 'changeOfAddress'
1817+
coa['filing']['changeOfAddress'] = CHANGE_OF_ADDRESS
1818+
coa['filing']['changeOfAddress']['offices']['registeredOffice']['deliveryAddress']['addressCountry'] = 'CA'
1819+
coa['filing']['changeOfAddress']['offices']['registeredOffice']['mailingAddress']['addressCountry'] = 'CA'
1820+
coa['filing']['business']['identifier'] = identifier
1821+
1822+
b = factory_business(identifier, (datetime.now(UTC) - datedelta.YEAR), None, Business.LegalTypes.COOP.value)
1823+
factory_business_mailing_address(b)
1824+
1825+
pay_mock = requests_mock.post(current_app.config.get('PAYMENT_SVC_URL'),
1826+
json={'id': 21322,
1827+
'statusCode': 'COMPLETED',
1828+
'isPaymentActionRequired': False},
1829+
status_code=HTTPStatus.CREATED)
1830+
rv = client.post(f'/api/v2/businesses/{identifier}/filings',
1831+
json=coa,
1832+
headers=create_header(jwt, [STAFF_ROLE], identifier,
1833+
**{'Account-Linking-Key': 'test-linking-key'})
1834+
)
1835+
1836+
assert rv.status_code == HTTPStatus.CREATED
1837+
assert pay_mock.last_request.headers.get('Account-Linking-Key') == 'test-linking-key'
1838+
1839+
18121840
def test_rules_memorandum_in_sr(session, mocker, requests_mock, client, jwt, ):
18131841
"""Assert if both rules update in sr, and rules file key is provided"""
18141842
mocker.patch('legal_api.services.filings.validations.alteration.validate_pdf',

0 commit comments

Comments
 (0)