Skip to content

Commit 15574c7

Browse files
committed
feat: validate store_id and authorization_model_id are in a valid format
1 parent 493f256 commit 15574c7

8 files changed

Lines changed: 133 additions & 23 deletions

File tree

.openapi-generator/FILES

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,8 @@ openfga_sdk/models/write_authorization_model_request.py
144144
openfga_sdk/models/write_authorization_model_response.py
145145
openfga_sdk/models/write_request.py
146146
openfga_sdk/rest.py
147+
openfga_sdk/test/validation.py
148+
openfga_sdk/validation.py
147149
requirements.txt
148150
setup.cfg
149151
setup.py

openfga_sdk/client/configuration.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
"""
1313

1414
from openfga_sdk.configuration import Configuration
15+
from openfga_sdk.exceptions import FgaValidationException
16+
from openfga_sdk.validation import is_well_formed_ulid_string
1517

1618

1719
class ClientConfiguration(Configuration):
@@ -30,6 +32,13 @@ def __init__(
3032
super().__init__(api_scheme, api_host, store_id, credentials, retry_params)
3133
self._authorization_model_id = authorization_model_id
3234

35+
def is_valid(self):
36+
super().is_valid()
37+
38+
if self.authorization_model_id is not None and self.authorization_model_id != "" and is_well_formed_ulid_string(self.authorization_model_id) is False:
39+
raise FgaValidationException(
40+
"authorization_model_id ('%s') is not in a valid ulid format" % self.authorization_model_id)
41+
3342
@property
3443
def authorization_model_id(self):
3544
return self._authorization_model_id

openfga_sdk/client/openfga_client.py

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
from openfga_sdk.client.write_transaction import WriteTransaction
2525
from openfga_sdk.client.configuration import ClientConfiguration
2626
from openfga_sdk.client.read_changes_body import ReadChangesBody
27+
from openfga_sdk.exceptions import FgaValidationException
2728
from openfga_sdk.models.assertion import Assertion
2829
from openfga_sdk.models.check_request import CheckRequest
2930
from openfga_sdk.models.contextual_tuple_keys import ContextualTupleKeys
@@ -35,6 +36,7 @@
3536
from openfga_sdk.models.write_assertions_request import WriteAssertionsRequest
3637
from openfga_sdk.models.write_authorization_model_request import WriteAuthorizationModelRequest
3738
from openfga_sdk.models.write_request import WriteRequest
39+
from openfga_sdk.validation import is_well_formed_ulid_string
3840

3941
import asyncio
4042
import uuid
@@ -118,15 +120,20 @@ async def __aexit__(self, exc_type, exc_value, traceback):
118120
async def close(self):
119121
await self._api.close()
120122

121-
def _get_authorization_model_id(self, options):
123+
def _get_authorization_model_id(self, options: object) -> str | None:
122124
"""
123125
Return the authorization model ID if specified in the options.
124126
Otherwise return the authorization model ID stored in the client's configuration
125127
"""
128+
authorization_model_id = self._client_configuration.authorization_model_id
126129
if options is not None and "authorization_model_id" in options:
127-
return options["authorization_model_id"]
128-
else:
129-
return self._client_configuration.authorization_model_id
130+
authorization_model_id = options["authorization_model_id"]
131+
if authorization_model_id is None or authorization_model_id == "":
132+
return None
133+
if is_well_formed_ulid_string(authorization_model_id) is False:
134+
raise FgaValidationException(
135+
"authorization_model_id ('%s') is not in a valid ulid format" % authorization_model_id)
136+
return authorization_model_id
130137

131138
def set_store_id(self, value):
132139
"""

openfga_sdk/configuration.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
from six.moves import http_client as httplib
2222
from urllib.parse import urlparse
2323
from openfga_sdk.exceptions import FgaValidationException, ApiValueError
24+
from openfga_sdk.validation import is_well_formed_ulid_string
2425

2526

2627
JSON_SCHEMA_VALIDATION_KEYWORDS = {
@@ -519,6 +520,11 @@ def is_valid(self):
519520
if (parsed_url.query != ''):
520521
raise ApiValueError(
521522
'api_host `{}` is not expected to have query specified'.format(self.api_scheme))
523+
524+
if self.store_id is not None and self.store_id != "" and is_well_formed_ulid_string(self.store_id) is False:
525+
raise FgaValidationException(
526+
"store_id ('%s') is not in a valid ulid format" % self.store_id)
527+
522528
if self._credentials is not None:
523529
self._credentials.validate_credentials_config()
524530

openfga_sdk/validation.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# coding: utf-8
2+
3+
"""
4+
Python SDK for OpenFGA
5+
6+
API version: 0.1
7+
Website: https://openfga.dev
8+
Documentation: https://openfga.dev/docs
9+
Support: https://discord.gg/8naAwJfWN6
10+
License: [Apache-2.0](https://github.com/openfga/python-sdk/blob/main/LICENSE)
11+
12+
NOTE: This file was auto generated by OpenAPI Generator (https://openapi-generator.tech). DO NOT EDIT.
13+
"""
14+
15+
import re
16+
17+
18+
def is_well_formed_ulid_string(ulid):
19+
regex = re.compile("^[0-7][0-9A-HJKMNP-TV-Z]{25}$")
20+
if not isinstance(ulid, str):
21+
return False
22+
match = regex.match(ulid)
23+
if match is None:
24+
return False
25+
return True

test/test_open_fga_api.py

Lines changed: 29 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@
6767
from openfga_sdk.models.write_authorization_model_response import WriteAuthorizationModelResponse
6868
from openfga_sdk.models.write_request import WriteRequest
6969

70-
store_id = 'd12345abc'
70+
store_id = '01H0H015178Y2V4CX10C2KGHF4'
7171
request_id = 'x1y2z3'
7272

7373
# Helper function to construct mock response
@@ -134,7 +134,7 @@ async def test_check(self, mock_request):
134134
# Make sure the API was called with the right data
135135
mock_request.assert_called_once_with(
136136
'POST',
137-
'http://api.fga.example/stores/d12345abc/check',
137+
'http://api.fga.example/stores/01H0H015178Y2V4CX10C2KGHF4/check',
138138
headers=ANY,
139139
query_params=[],
140140
post_params=[],
@@ -197,7 +197,7 @@ async def test_delete_store(self, mock_request):
197197
await api_instance.delete_store()
198198
mock_request.assert_called_once_with(
199199
'DELETE',
200-
'http://api.fga.example/stores/d12345abc',
200+
'http://api.fga.example/stores/01H0H015178Y2V4CX10C2KGHF4',
201201
headers=ANY,
202202
query_params=[],
203203
body=None,
@@ -239,7 +239,7 @@ async def test_expand(self, mock_request):
239239
self.assertEqual(api_response, expected_response)
240240
mock_request.assert_called_once_with(
241241
'POST',
242-
'http://api.fga.example/stores/d12345abc/expand',
242+
'http://api.fga.example/stores/01H0H015178Y2V4CX10C2KGHF4/expand',
243243
headers=ANY,
244244
query_params=[],
245245
post_params=[],
@@ -257,7 +257,7 @@ async def test_get_store(self, mock_request):
257257
Get a store # noqa: E501
258258
"""
259259
response_body = '''{
260-
"id": "d12345abc",
260+
"id": "01H0H015178Y2V4CX10C2KGHF4",
261261
"name": "test_store",
262262
"created_at": "2022-07-25T20:45:10.485Z",
263263
"updated_at": "2022-07-25T20:45:10.485Z"
@@ -271,11 +271,11 @@ async def test_get_store(self, mock_request):
271271
# Get a store
272272
api_response = await api_instance.get_store()
273273
self.assertIsInstance(api_response, GetStoreResponse)
274-
self.assertEqual(api_response.id, 'd12345abc')
274+
self.assertEqual(api_response.id, '01H0H015178Y2V4CX10C2KGHF4')
275275
self.assertEqual(api_response.name, 'test_store')
276276
mock_request.assert_called_once_with(
277277
'GET',
278-
'http://api.fga.example/stores/d12345abc',
278+
'http://api.fga.example/stores/01H0H015178Y2V4CX10C2KGHF4',
279279
headers=ANY,
280280
query_params=[],
281281
_preload_content=ANY,
@@ -313,7 +313,7 @@ async def test_list_objects(self, mock_request):
313313
self.assertEqual(api_response.objects, ['document:abcd1234'])
314314
mock_request.assert_called_once_with(
315315
'POST',
316-
'http://api.fga.example/stores/d12345abc/list-objects',
316+
'http://api.fga.example/stores/01H0H015178Y2V4CX10C2KGHF4/list-objects',
317317
headers=ANY,
318318
query_params=[],
319319
post_params=[],
@@ -436,7 +436,7 @@ async def test_read(self, mock_request):
436436
self.assertEqual(api_response, expected_data)
437437
mock_request.assert_called_once_with(
438438
'POST',
439-
'http://api.fga.example/stores/d12345abc/read',
439+
'http://api.fga.example/stores/01H0H015178Y2V4CX10C2KGHF4/read',
440440
headers=ANY,
441441
query_params=[],
442442
post_params=[],
@@ -488,7 +488,7 @@ async def test_read_assertions(self, mock_request):
488488
self.assertEqual(api_response.assertions, [assertion])
489489
mock_request.assert_called_once_with(
490490
'GET',
491-
'http://api.fga.example/stores/d12345abc/assertions/01G5JAVJ41T49E9TT3SKVS7X1J',
491+
'http://api.fga.example/stores/01H0H015178Y2V4CX10C2KGHF4/assertions/01G5JAVJ41T49E9TT3SKVS7X1J',
492492
headers=ANY,
493493
query_params=[],
494494
_preload_content=ANY,
@@ -573,7 +573,7 @@ async def test_read_authorization_model(self, mock_request):
573573
self.assertEqual(api_response.authorization_model, authorization_model)
574574
mock_request.assert_called_once_with(
575575
'GET',
576-
'http://api.fga.example/stores/d12345abc/authorization-models/01G5JAVJ41T49E9TT3SKVS7X1J',
576+
'http://api.fga.example/stores/01H0H015178Y2V4CX10C2KGHF4/authorization-models/01G5JAVJ41T49E9TT3SKVS7X1J',
577577
headers=ANY,
578578
query_params=[],
579579
_preload_content=ANY,
@@ -628,7 +628,7 @@ async def test_read_changes(self, mock_request):
628628
self.assertEqual(api_response, read_changes)
629629
mock_request.assert_called_once_with(
630630
'GET',
631-
'http://api.fga.example/stores/d12345abc/changes',
631+
'http://api.fga.example/stores/01H0H015178Y2V4CX10C2KGHF4/changes',
632632
headers=ANY,
633633
query_params=[('type', 'document'), ('page_size', 1),
634634
('continuation_token', 'abcdefg')],
@@ -670,7 +670,7 @@ async def test_write(self, mock_request):
670670
)
671671
mock_request.assert_called_once_with(
672672
'POST',
673-
'http://api.fga.example/stores/d12345abc/write',
673+
'http://api.fga.example/stores/01H0H015178Y2V4CX10C2KGHF4/write',
674674
headers=ANY,
675675
query_params=[],
676676
post_params=[],
@@ -714,7 +714,7 @@ async def test_write_delete(self, mock_request):
714714
)
715715
mock_request.assert_called_once_with(
716716
'POST',
717-
'http://api.fga.example/stores/d12345abc/write',
717+
'http://api.fga.example/stores/01H0H015178Y2V4CX10C2KGHF4/write',
718718
headers=ANY,
719719
query_params=[],
720720
post_params=[],
@@ -759,7 +759,7 @@ async def test_write_assertions(self, mock_request):
759759
)
760760
mock_request.assert_called_once_with(
761761
'PUT',
762-
'http://api.fga.example/stores/d12345abc/assertions/xyz0123',
762+
'http://api.fga.example/stores/01H0H015178Y2V4CX10C2KGHF4/assertions/xyz0123',
763763
headers=ANY,
764764
query_params=[],
765765
post_params=[],
@@ -819,7 +819,7 @@ async def test_write_authorization_model(self, mock_request):
819819
self.assertEqual(api_response, expected_response)
820820
mock_request.assert_called_once_with(
821821
'POST',
822-
'http://api.fga.example/stores/d12345abc/authorization-models',
822+
'http://api.fga.example/stores/01H0H015178Y2V4CX10C2KGHF4/authorization-models',
823823
headers=ANY,
824824
query_params=[],
825825
post_params=[],
@@ -906,6 +906,17 @@ def test_configuration_has_query(self):
906906
)
907907
self.assertRaises(ApiValueError, configuration.is_valid)
908908

909+
def test_configuration_store_id_invalid(self):
910+
"""
911+
Test whether ApiValueError is raised if host has query
912+
"""
913+
configuration = openfga_sdk.Configuration(
914+
api_host='localhost',
915+
api_scheme='http',
916+
store_id="abcd"
917+
)
918+
self.assertRaises(FgaValidationException, configuration.is_valid)
919+
909920
async def test_bad_configuration_read_authorization_model(self):
910921
"""
911922
Test whether FgaValidationException is raised for API (reading authorization models)
@@ -1166,7 +1177,7 @@ async def test_check_api_token(self, mock_request):
11661177
{'Accept': 'application/json', 'Content-Type': 'application/json', 'User-Agent': 'openfga-sdk python/0.1.1', 'Authorization': 'Bearer TOKEN1'})
11671178
mock_request.assert_called_once_with(
11681179
'POST',
1169-
'http://api.fga.example/stores/d12345abc/check',
1180+
'http://api.fga.example/stores/01H0H015178Y2V4CX10C2KGHF4/check',
11701181
headers=expectedHeader,
11711182
query_params=[],
11721183
post_params=[],
@@ -1209,7 +1220,7 @@ async def test_check_custom_header(self, mock_request):
12091220
{'Accept': 'application/json', 'Content-Type': 'application/json', 'User-Agent': 'openfga-sdk python/0.1.1', 'Custom Header': 'custom value'})
12101221
mock_request.assert_called_once_with(
12111222
'POST',
1212-
'http://api.fga.example/stores/d12345abc/check',
1223+
'http://api.fga.example/stores/01H0H015178Y2V4CX10C2KGHF4/check',
12131224
headers=expectedHeader,
12141225
query_params=[],
12151226
post_params=[],

test/test_openfga_client.py

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
from openfga_sdk.client.read_changes_body import ReadChangesBody
3131
from openfga_sdk.client.single_write_response import SingleWriteResponse
3232
from openfga_sdk.client.write_transaction import WriteTransaction
33-
from openfga_sdk.exceptions import ValidationException
33+
from openfga_sdk.exceptions import ValidationException, FgaValidationException
3434
from openfga_sdk.models.assertion import Assertion
3535
from openfga_sdk.models.authorization_model import AuthorizationModel
3636
from openfga_sdk.models.check_response import CheckResponse
@@ -2006,3 +2006,26 @@ async def test_update_auth_model(self, mock_request):
20062006
_preload_content=ANY,
20072007
_request_timeout=None
20082008
)
2009+
2010+
def test_configuration_store_id_invalid(self):
2011+
"""
2012+
Test whether ApiValueError is raised if host has query
2013+
"""
2014+
configuration = ClientConfiguration(
2015+
api_host='localhost',
2016+
api_scheme='http',
2017+
store_id="abcd"
2018+
)
2019+
self.assertRaises(FgaValidationException, configuration.is_valid)
2020+
2021+
def test_configuration_authorization_model_id_invalid(self):
2022+
"""
2023+
Test whether ApiValueError is raised if host has query
2024+
"""
2025+
configuration = ClientConfiguration(
2026+
api_host='localhost',
2027+
api_scheme='http',
2028+
store_id="01H15K9J85050XTEDPVM8DJM78",
2029+
authorization_model_id="abcd"
2030+
)
2031+
self.assertRaises(FgaValidationException, configuration.is_valid)

test/test_validation.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import unittest
2+
3+
from openfga_sdk.validation import is_well_formed_ulid_string
4+
5+
6+
class TestValidation(unittest.TestCase):
7+
"""Test for validation"""
8+
9+
def setUp(self):
10+
pass
11+
12+
def tearDown(self):
13+
pass
14+
15+
def test_is_well_formed_ulid_string_valid_ulids(self):
16+
self.assertEqual(is_well_formed_ulid_string("01H0GVCS1HCQM6SJRJ4A026FZ9"), True, "Should be True")
17+
self.assertEqual(is_well_formed_ulid_string("01H0GVD9ACPFKGMWJV0Y93ZM7H"), True, "Should be True")
18+
self.assertEqual(is_well_formed_ulid_string("01H0GVDH0FRZ4WAFED6T9KZYZR"), True, "Should be True")
19+
self.assertEqual(is_well_formed_ulid_string("01H0GVDSW72AZ8QV3R0HJ91QBX"), True, "Should be True")
20+
21+
def test_is_well_formed_ulid_string_invalid_ulids(self):
22+
self.assertEqual(is_well_formed_ulid_string("abc"), False, "Should be False")
23+
self.assertEqual(is_well_formed_ulid_string(123), False, "Should be False")
24+
self.assertEqual(is_well_formed_ulid_string(None), False, "Should be False")
25+
self.assertEqual(is_well_formed_ulid_string("01H0GVDSW72AZ8QV3R0HJ91QBXa"), False, "Should be False")
26+
self.assertEqual(is_well_formed_ulid_string("b523ad13-8adb-4803-a6db-013ac50197ca"), False, "Should be False")
27+
self.assertEqual(is_well_formed_ulid_string("9240BFC0-DA00-457B-A328-FC370A598D60"), False, "Should be False")

0 commit comments

Comments
 (0)