Skip to content

Commit 354e24d

Browse files
authored
feat: dynamic token issuer (#186)
feat(python): dynamic token issuer
1 parent 172cd26 commit 354e24d

File tree

2 files changed

+221
-1
lines changed

2 files changed

+221
-1
lines changed

openfga_sdk/oauth2.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ async def _obtain_token(self, client):
7070
"""
7171
configuration = self._credentials.configuration
7272

73-
token_url = f"https://{configuration.api_issuer}/oauth/token"
73+
token_url = self._credentials._parse_issuer(configuration.api_issuer)
7474

7575
post_params = {
7676
"client_id": configuration.client_id,

test/oauth2_test.py

Lines changed: 220 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -274,3 +274,223 @@ async def test_get_authentication_retries_5xx_responses(self, mock_request):
274274
self.assertEqual(auth_header, {"Authorization": "Bearer AABBCCDD"})
275275

276276
await rest_client.close()
277+
278+
@patch.object(rest.RESTClientObject, "request")
279+
async def test_get_authentication_keep_full_url(self, mock_request):
280+
"""
281+
Fully qualified issuer URLs should not get manipulated.
282+
"""
283+
response_body = """
284+
{
285+
"expires_in": 120,
286+
"access_token": "AABBCCDD"
287+
}
288+
"""
289+
mock_request.return_value = mock_response(response_body, 200)
290+
291+
credentials = Credentials(
292+
method="client_credentials",
293+
configuration=CredentialConfiguration(
294+
client_id="myclientid",
295+
client_secret="mysecret",
296+
api_issuer="https://issuer.fga.example/something",
297+
api_audience="myaudience",
298+
),
299+
)
300+
rest_client = rest.RESTClientObject(Configuration())
301+
current_time = datetime.now()
302+
client = OAuth2Client(credentials)
303+
auth_header = await client.get_authentication_header(rest_client)
304+
self.assertEqual(auth_header, {"Authorization": "Bearer AABBCCDD"})
305+
self.assertEqual(client._access_token, "AABBCCDD")
306+
self.assertGreaterEqual(
307+
client._access_expiry_time, current_time + timedelta(seconds=120)
308+
)
309+
expected_header = urllib3.response.HTTPHeaderDict(
310+
{
311+
"Accept": "application/json",
312+
"Content-Type": "application/x-www-form-urlencoded",
313+
"User-Agent": "openfga-sdk (python) 0.9.3",
314+
}
315+
)
316+
mock_request.assert_called_once_with(
317+
method="POST",
318+
url="https://issuer.fga.example/something",
319+
headers=expected_header,
320+
query_params=None,
321+
body=None,
322+
_preload_content=True,
323+
_request_timeout=None,
324+
post_params={
325+
"client_id": "myclientid",
326+
"client_secret": "mysecret",
327+
"audience": "myaudience",
328+
"grant_type": "client_credentials",
329+
},
330+
)
331+
await rest_client.close()
332+
333+
@patch.object(rest.RESTClientObject, "request")
334+
async def test_get_authentication_add_scheme(self, mock_request):
335+
"""
336+
Issuer URLs without scheme should get scheme prefix added.
337+
"""
338+
response_body = """
339+
{
340+
"expires_in": 120,
341+
"access_token": "AABBCCDD"
342+
}
343+
"""
344+
mock_request.return_value = mock_response(response_body, 200)
345+
346+
credentials = Credentials(
347+
method="client_credentials",
348+
configuration=CredentialConfiguration(
349+
client_id="myclientid",
350+
client_secret="mysecret",
351+
api_issuer="issuer.fga.example/something",
352+
api_audience="myaudience",
353+
),
354+
)
355+
rest_client = rest.RESTClientObject(Configuration())
356+
current_time = datetime.now()
357+
client = OAuth2Client(credentials)
358+
auth_header = await client.get_authentication_header(rest_client)
359+
self.assertEqual(auth_header, {"Authorization": "Bearer AABBCCDD"})
360+
self.assertEqual(client._access_token, "AABBCCDD")
361+
self.assertGreaterEqual(
362+
client._access_expiry_time, current_time + timedelta(seconds=120)
363+
)
364+
expected_header = urllib3.response.HTTPHeaderDict(
365+
{
366+
"Accept": "application/json",
367+
"Content-Type": "application/x-www-form-urlencoded",
368+
"User-Agent": "openfga-sdk (python) 0.9.3",
369+
}
370+
)
371+
mock_request.assert_called_once_with(
372+
method="POST",
373+
url="https://issuer.fga.example/something",
374+
headers=expected_header,
375+
query_params=None,
376+
body=None,
377+
_preload_content=True,
378+
_request_timeout=None,
379+
post_params={
380+
"client_id": "myclientid",
381+
"client_secret": "mysecret",
382+
"audience": "myaudience",
383+
"grant_type": "client_credentials",
384+
},
385+
)
386+
await rest_client.close()
387+
388+
@patch.object(rest.RESTClientObject, "request")
389+
async def test_get_authentication_add_path(self, mock_request):
390+
"""
391+
Issuer URLs without scheme should get scheme prefix added.
392+
"""
393+
response_body = """
394+
{
395+
"expires_in": 120,
396+
"access_token": "AABBCCDD"
397+
}
398+
"""
399+
mock_request.return_value = mock_response(response_body, 200)
400+
401+
credentials = Credentials(
402+
method="client_credentials",
403+
configuration=CredentialConfiguration(
404+
client_id="myclientid",
405+
client_secret="mysecret",
406+
api_issuer="https://issuer.fga.example",
407+
api_audience="myaudience",
408+
),
409+
)
410+
rest_client = rest.RESTClientObject(Configuration())
411+
current_time = datetime.now()
412+
client = OAuth2Client(credentials)
413+
auth_header = await client.get_authentication_header(rest_client)
414+
self.assertEqual(auth_header, {"Authorization": "Bearer AABBCCDD"})
415+
self.assertEqual(client._access_token, "AABBCCDD")
416+
self.assertGreaterEqual(
417+
client._access_expiry_time, current_time + timedelta(seconds=120)
418+
)
419+
expected_header = urllib3.response.HTTPHeaderDict(
420+
{
421+
"Accept": "application/json",
422+
"Content-Type": "application/x-www-form-urlencoded",
423+
"User-Agent": "openfga-sdk (python) 0.9.3",
424+
}
425+
)
426+
mock_request.assert_called_once_with(
427+
method="POST",
428+
url="https://issuer.fga.example/oauth/token",
429+
headers=expected_header,
430+
query_params=None,
431+
body=None,
432+
_preload_content=True,
433+
_request_timeout=None,
434+
post_params={
435+
"client_id": "myclientid",
436+
"client_secret": "mysecret",
437+
"audience": "myaudience",
438+
"grant_type": "client_credentials",
439+
},
440+
)
441+
await rest_client.close()
442+
443+
@patch.object(rest.RESTClientObject, "request")
444+
async def test_get_authentication_add_scheme_and_path(self, mock_request):
445+
"""
446+
Issuer URLs without scheme should get scheme prefix added.
447+
"""
448+
response_body = """
449+
{
450+
"expires_in": 120,
451+
"access_token": "AABBCCDD"
452+
}
453+
"""
454+
mock_request.return_value = mock_response(response_body, 200)
455+
456+
credentials = Credentials(
457+
method="client_credentials",
458+
configuration=CredentialConfiguration(
459+
client_id="myclientid",
460+
client_secret="mysecret",
461+
api_issuer="issuer.fga.example",
462+
api_audience="myaudience",
463+
),
464+
)
465+
rest_client = rest.RESTClientObject(Configuration())
466+
current_time = datetime.now()
467+
client = OAuth2Client(credentials)
468+
auth_header = await client.get_authentication_header(rest_client)
469+
self.assertEqual(auth_header, {"Authorization": "Bearer AABBCCDD"})
470+
self.assertEqual(client._access_token, "AABBCCDD")
471+
self.assertGreaterEqual(
472+
client._access_expiry_time, current_time + timedelta(seconds=120)
473+
)
474+
expected_header = urllib3.response.HTTPHeaderDict(
475+
{
476+
"Accept": "application/json",
477+
"Content-Type": "application/x-www-form-urlencoded",
478+
"User-Agent": "openfga-sdk (python) 0.9.3",
479+
}
480+
)
481+
mock_request.assert_called_once_with(
482+
method="POST",
483+
url="https://issuer.fga.example/oauth/token",
484+
headers=expected_header,
485+
query_params=None,
486+
body=None,
487+
_preload_content=True,
488+
_request_timeout=None,
489+
post_params={
490+
"client_id": "myclientid",
491+
"client_secret": "mysecret",
492+
"audience": "myaudience",
493+
"grant_type": "client_credentials",
494+
},
495+
)
496+
await rest_client.close()

0 commit comments

Comments
 (0)