Skip to content

Commit 0531ff3

Browse files
fix: add auth headers and body to multitenancy token request
Signed-off-by: Yuki I <[email protected]>
1 parent 9567f85 commit 0531ff3

File tree

2 files changed

+180
-12
lines changed

2 files changed

+180
-12
lines changed

oidc-controller/api/core/acapy/config.py

Lines changed: 54 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -21,16 +21,50 @@ class MultiTenantAcapy:
2121
@cache
2222
def get_wallet_token(self):
2323
logger.debug(">>> get_wallet_token")
24-
resp_raw = requests.post(
25-
settings.ACAPY_ADMIN_URL + f"/multitenancy/wallet/{self.wallet_id}/token",
24+
25+
# Check if admin API key is configured
26+
admin_api_key_configured = (
27+
settings.ST_ACAPY_ADMIN_API_KEY_NAME and settings.ST_ACAPY_ADMIN_API_KEY
2628
)
27-
assert (
28-
resp_raw.status_code == 200
29-
), f"{resp_raw.status_code}::{resp_raw.content}"
30-
resp = json.loads(resp_raw.content)
31-
wallet_token = resp["token"]
32-
logger.debug("<<< get_wallet_token")
3329

30+
headers = {}
31+
32+
if admin_api_key_configured:
33+
logger.debug("Admin API key is configured, adding to request headers")
34+
headers[settings.ST_ACAPY_ADMIN_API_KEY_NAME] = (
35+
settings.ST_ACAPY_ADMIN_API_KEY
36+
)
37+
else:
38+
logger.debug(
39+
"No admin API key configured, proceeding without authentication headers"
40+
)
41+
42+
payload = {"wallet_key": self.wallet_key}
43+
44+
try:
45+
resp_raw = requests.post(
46+
settings.ACAPY_ADMIN_URL
47+
+ f"/multitenancy/wallet/{self.wallet_id}/token",
48+
headers=headers,
49+
json=payload,
50+
)
51+
52+
if resp_raw.status_code != 200:
53+
error_detail = resp_raw.content.decode()
54+
logger.error(
55+
f"Failed to get wallet token. Status: {resp_raw.status_code}, Detail: {error_detail}"
56+
)
57+
# Raising Exception to be caught by the except block below or propagated
58+
raise Exception(f"{resp_raw.status_code}::{error_detail}")
59+
60+
resp = json.loads(resp_raw.content)
61+
wallet_token = resp["token"]
62+
63+
except Exception as e:
64+
logger.error(f"Exception occurred while getting wallet token: {str(e)}")
65+
raise
66+
67+
logger.debug("<<< get_wallet_token")
3468
return wallet_token
3569

3670
def get_headers(self) -> dict[str, str]:
@@ -39,4 +73,15 @@ def get_headers(self) -> dict[str, str]:
3973

4074
class SingleTenantAcapy:
4175
def get_headers(self) -> dict[str, str]:
42-
return {settings.ST_ACAPY_ADMIN_API_KEY_NAME: settings.ST_ACAPY_ADMIN_API_KEY}
76+
# Check if admin API key is configured
77+
admin_api_key_configured = (
78+
settings.ST_ACAPY_ADMIN_API_KEY_NAME and settings.ST_ACAPY_ADMIN_API_KEY
79+
)
80+
81+
if admin_api_key_configured:
82+
return {
83+
settings.ST_ACAPY_ADMIN_API_KEY_NAME: settings.ST_ACAPY_ADMIN_API_KEY
84+
}
85+
else:
86+
logger.debug("No admin API key configured for single tenant agent")
87+
return {}

oidc-controller/api/core/acapy/tests/test_config.py

Lines changed: 126 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,16 @@ async def test_single_tenant_has_expected_headers():
1313
assert headers == {"name": "key"}
1414

1515

16+
@pytest.mark.asyncio
17+
@mock.patch.object(settings, "ST_ACAPY_ADMIN_API_KEY_NAME", "name")
18+
@mock.patch.object(settings, "ST_ACAPY_ADMIN_API_KEY", None)
19+
async def test_single_tenant_empty_headers_not_configured():
20+
# Test behavior when API key is missing
21+
acapy = SingleTenantAcapy()
22+
headers = acapy.get_headers()
23+
assert headers == {}
24+
25+
1626
@pytest.mark.asyncio
1727
async def test_multi_tenant_get_headers_returns_bearer_token_auth(requests_mock):
1828
acapy = MultiTenantAcapy()
@@ -45,7 +55,120 @@ async def test_multi_tenant_throws_assertion_error_for_non_200_response(requests
4555
)
4656
acapy = MultiTenantAcapy()
4757
acapy.wallet_id = "wallet_id"
48-
try:
58+
59+
# Clear cache to ensure test isolation
60+
acapy.get_wallet_token.cache_clear()
61+
62+
# The implementation raises Exception, not AssertionError
63+
with pytest.raises(Exception) as exc_info:
4964
acapy.get_wallet_token()
50-
except AssertionError as e:
51-
assert e is not None
65+
66+
assert "400" in str(exc_info.value)
67+
68+
69+
@pytest.mark.asyncio
70+
async def test_multi_tenant_get_wallet_token_includes_auth_headers_and_body(
71+
requests_mock,
72+
):
73+
# Verify headers and body payload
74+
wallet_id = "wallet_id"
75+
wallet_key = "wallet_key"
76+
admin_key = "admin_key"
77+
admin_header = "x-api-key"
78+
79+
# Mock settings for the duration of this test
80+
with mock.patch.object(
81+
settings, "MT_ACAPY_WALLET_ID", wallet_id
82+
), mock.patch.object(
83+
settings, "MT_ACAPY_WALLET_KEY", wallet_key
84+
), mock.patch.object(
85+
settings, "ST_ACAPY_ADMIN_API_KEY", admin_key
86+
), mock.patch.object(
87+
settings, "ST_ACAPY_ADMIN_API_KEY_NAME", admin_header
88+
):
89+
90+
acapy = MultiTenantAcapy()
91+
# Ensure we use the values we expect (class init reads settings once)
92+
acapy.wallet_id = wallet_id
93+
acapy.wallet_key = wallet_key
94+
# Ensure we bypass cache from any previous tests
95+
acapy.get_wallet_token.cache_clear()
96+
97+
requests_mock.post(
98+
settings.ACAPY_ADMIN_URL + f"/multitenancy/wallet/{wallet_id}/token",
99+
json={"token": "token"},
100+
status_code=200,
101+
)
102+
103+
token = acapy.get_wallet_token()
104+
assert token == "token"
105+
106+
# Verify request details
107+
last_request = requests_mock.last_request
108+
assert last_request.headers[admin_header] == admin_key
109+
assert last_request.json() == {"wallet_key": wallet_key}
110+
111+
112+
@pytest.mark.asyncio
113+
async def test_multi_tenant_get_wallet_token_no_auth_headers_when_not_configured(
114+
requests_mock,
115+
):
116+
# Test insecure mode behavior
117+
wallet_id = "wallet_id"
118+
wallet_key = "wallet_key"
119+
120+
# Mock settings with None for admin key
121+
with mock.patch.object(
122+
settings, "MT_ACAPY_WALLET_ID", wallet_id
123+
), mock.patch.object(
124+
settings, "MT_ACAPY_WALLET_KEY", wallet_key
125+
), mock.patch.object(
126+
settings, "ST_ACAPY_ADMIN_API_KEY", None
127+
), mock.patch.object(
128+
settings, "ST_ACAPY_ADMIN_API_KEY_NAME", "x-api-key"
129+
):
130+
131+
acapy = MultiTenantAcapy()
132+
acapy.wallet_id = wallet_id
133+
acapy.wallet_key = wallet_key
134+
acapy.get_wallet_token.cache_clear()
135+
136+
requests_mock.post(
137+
settings.ACAPY_ADMIN_URL + f"/multitenancy/wallet/{wallet_id}/token",
138+
json={"token": "token"},
139+
status_code=200,
140+
)
141+
142+
token = acapy.get_wallet_token()
143+
assert token == "token"
144+
145+
# Verify request details
146+
last_request = requests_mock.last_request
147+
# Headers might contain Content-Type, but should not contain the api key
148+
assert "x-api-key" not in last_request.headers
149+
assert last_request.json() == {"wallet_key": wallet_key}
150+
151+
152+
@pytest.mark.asyncio
153+
async def test_multi_tenant_throws_exception_for_non_200_response(requests_mock):
154+
wallet_id = "wallet_id"
155+
wallet_key = "wallet_key"
156+
157+
with mock.patch.object(settings, "MT_ACAPY_WALLET_ID", wallet_id):
158+
acapy = MultiTenantAcapy()
159+
acapy.wallet_id = wallet_id
160+
acapy.wallet_key = wallet_key
161+
acapy.get_wallet_token.cache_clear()
162+
163+
requests_mock.post(
164+
settings.ACAPY_ADMIN_URL + f"/multitenancy/wallet/{wallet_id}/token",
165+
json={"error": "unauthorized"},
166+
status_code=401, # Use 401 for a more realistic test
167+
)
168+
169+
# Check for generic Exception, as the code now raises Exception(f"{code}::{detail}")
170+
with pytest.raises(Exception) as excinfo:
171+
acapy.get_wallet_token()
172+
173+
assert "401" in str(excinfo.value)
174+
assert "unauthorized" in str(excinfo.value)

0 commit comments

Comments
 (0)