Skip to content

Commit f073bf6

Browse files
authored
Adding Save/Load FGA Resources Details MGMT APIs (#562)
1 parent a7c1112 commit f073bf6

File tree

3 files changed

+126
-0
lines changed

3 files changed

+126
-0
lines changed

descope/management/common.py

+2
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,8 @@ class MgmtV1:
141141
fga_create_relations = "/v1/mgmt/fga/relations"
142142
fga_delete_relations = "/v1/mgmt/fga/relations/delete"
143143
fga_check = "/v1/mgmt/fga/check"
144+
fga_resources_load = "/v1/mgmt/fga/resources/load"
145+
fga_resources_save = "/v1/mgmt/fga/resources/save"
144146

145147
# Project
146148
project_update_name = "/v1/mgmt/project/update/name"

descope/management/fga.py

+27
Original file line numberDiff line numberDiff line change
@@ -138,3 +138,30 @@ def check(
138138
response.json()["tuples"],
139139
)
140140
)
141+
142+
def load_resources_details(self, resource_identifiers: List[dict]) -> List[dict]:
143+
"""
144+
Load details for the given resource identifiers.
145+
Args:
146+
resource_identifiers (List[dict]): list of dicts each containing 'resourceId' and 'resourceType'.
147+
Returns:
148+
List[dict]: list of resources details as returned by the server.
149+
"""
150+
response = self._auth.do_post(
151+
MgmtV1.fga_resources_load,
152+
{"resourceIdentifiers": resource_identifiers},
153+
pswd=self._auth.management_key,
154+
)
155+
return response.json().get("resourcesDetails", [])
156+
157+
def save_resources_details(self, resources_details: List[dict]) -> None:
158+
"""
159+
Save details for the given resources.
160+
Args:
161+
resources_details (List[dict]): list of dicts each containing 'resourceId' and 'resourceType' plus optionally containing metadata fields such as 'displayName'.
162+
"""
163+
self._auth.do_post(
164+
MgmtV1.fga_resources_save,
165+
{"resourcesDetails": resources_details},
166+
pswd=self._auth.management_key,
167+
)

tests/management/test_fga.py

+97
Original file line numberDiff line numberDiff line change
@@ -211,3 +211,100 @@ def test_check(self):
211211
verify=True,
212212
timeout=DEFAULT_TIMEOUT_SECONDS,
213213
)
214+
215+
def test_load_resources_details_success(self):
216+
client = DescopeClient(
217+
self.dummy_project_id,
218+
self.public_key_dict,
219+
False,
220+
self.dummy_management_key,
221+
)
222+
response_body = {
223+
"resourcesDetails": [
224+
{"resourceId": "r1", "resourceType": "type1", "displayName": "Name1"},
225+
{"resourceId": "r2", "resourceType": "type2", "displayName": "Name2"},
226+
]
227+
}
228+
with patch("requests.post") as mock_post:
229+
mock_post.return_value.ok = True
230+
mock_post.return_value.json.return_value = response_body
231+
ids = [
232+
{"resourceId": "r1", "resourceType": "type1"},
233+
{"resourceId": "r2", "resourceType": "type2"},
234+
]
235+
details = client.mgmt.fga.load_resources_details(ids)
236+
self.assertEqual(details, response_body["resourcesDetails"])
237+
mock_post.assert_called_with(
238+
f"{common.DEFAULT_BASE_URL}{MgmtV1.fga_resources_load}",
239+
headers={
240+
**common.default_headers,
241+
"Authorization": f"Bearer {self.dummy_project_id}:{self.dummy_management_key}",
242+
"x-descope-project-id": self.dummy_project_id,
243+
},
244+
params=None,
245+
json={"resourceIdentifiers": ids},
246+
allow_redirects=False,
247+
verify=True,
248+
timeout=DEFAULT_TIMEOUT_SECONDS,
249+
)
250+
251+
def test_load_resources_details_error(self):
252+
client = DescopeClient(
253+
self.dummy_project_id,
254+
self.public_key_dict,
255+
False,
256+
self.dummy_management_key,
257+
)
258+
with patch("requests.post") as mock_post:
259+
mock_post.return_value.ok = False
260+
ids = [{"resourceId": "r1", "resourceType": "type1"}]
261+
self.assertRaises(
262+
AuthException,
263+
client.mgmt.fga.load_resources_details,
264+
ids,
265+
)
266+
267+
def test_save_resources_details_success(self):
268+
client = DescopeClient(
269+
self.dummy_project_id,
270+
self.public_key_dict,
271+
False,
272+
self.dummy_management_key,
273+
)
274+
details = [
275+
{"resourceId": "r1", "resourceType": "type1", "displayName": "Name1"}
276+
]
277+
with patch("requests.post") as mock_post:
278+
mock_post.return_value.ok = True
279+
client.mgmt.fga.save_resources_details(details)
280+
mock_post.assert_called_with(
281+
f"{common.DEFAULT_BASE_URL}{MgmtV1.fga_resources_save}",
282+
headers={
283+
**common.default_headers,
284+
"Authorization": f"Bearer {self.dummy_project_id}:{self.dummy_management_key}",
285+
"x-descope-project-id": self.dummy_project_id,
286+
},
287+
params=None,
288+
json={"resourcesDetails": details},
289+
allow_redirects=False,
290+
verify=True,
291+
timeout=DEFAULT_TIMEOUT_SECONDS,
292+
)
293+
294+
def test_save_resources_details_error(self):
295+
client = DescopeClient(
296+
self.dummy_project_id,
297+
self.public_key_dict,
298+
False,
299+
self.dummy_management_key,
300+
)
301+
details = [
302+
{"resourceId": "r1", "resourceType": "type1", "displayName": "Name1"}
303+
]
304+
with patch("requests.post") as mock_post:
305+
mock_post.return_value.ok = False
306+
self.assertRaises(
307+
AuthException,
308+
client.mgmt.fga.save_resources_details,
309+
details,
310+
)

0 commit comments

Comments
 (0)