Skip to content

Commit 061facd

Browse files
authored
feat: adapt async enforce and batch-enforce APIs to latest format (#104)
1 parent 4a5b310 commit 061facd

File tree

3 files changed

+119
-140
lines changed

3 files changed

+119
-140
lines changed

src/casdoor/async_main.py

Lines changed: 99 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
# limitations under the License.
1414

1515
import base64
16+
import json
1617
from typing import Dict, List, Optional
1718

1819
import aiohttp
@@ -263,73 +264,114 @@ def parse_jwt_token(self, token: str, **kwargs) -> Dict:
263264

264265
async def enforce(
265266
self,
266-
permission_model_name: str,
267-
sub: str,
268-
obj: str,
269-
act: str,
270-
v3: Optional[str] = None,
271-
v4: Optional[str] = None,
272-
v5: Optional[str] = None,
267+
permission_id: str,
268+
model_id: str,
269+
resource_id: str,
270+
enforce_id: str,
271+
owner: str,
272+
casbin_request: Optional[List[str]] = None,
273273
) -> bool:
274274
"""
275-
Send data to Casdoor enforce API
276-
# https://casdoor.org/docs/permission/exposed-casbin-apis#enforce
277-
278-
:param permission_model_name: Name permission model
279-
:param sub: sub from Casbin
280-
:param obj: obj from Casbin
281-
:param act: act from Casbin
282-
:param v3: v3 from Casbin
283-
:param v4: v4 from Casbin
284-
:param v5: v5 from Casbin
275+
Send data to Casdoor enforce API asynchronously
276+
277+
:param permission_id: the permission id (i.e. organization name/permission name)
278+
:param model_id: the model id
279+
:param resource_id: the resource id
280+
:param enforce_id: the enforce id
281+
:param owner: the owner of the permission
282+
:param casbin_request: a list containing the request data (i.e. sub, obj, act)
283+
:return: a boolean value indicating whether the request is allowed
285284
"""
286-
path = "/api/enforce"
287-
params = {
288-
"id": permission_model_name,
289-
"v0": sub,
290-
"v1": obj,
291-
"v2": act,
292-
"v3": v3,
293-
"v4": v4,
294-
"v5": v5,
285+
url = "/api/enforce"
286+
params: Dict[str, str] = {
287+
"permissionId": permission_id,
288+
"modelId": model_id,
289+
"resourceId": resource_id,
290+
"enforceId": enforce_id,
291+
"owner": owner,
295292
}
293+
296294
async with self._session as session:
297-
has_permission = await session.post(path, headers=self.headers, json=params)
298-
if not isinstance(has_permission, bool):
299-
raise ValueError(f"Casdoor response error: {has_permission}")
300-
return has_permission
295+
response = await session.post(
296+
url,
297+
params=params,
298+
data=json.dumps(casbin_request),
299+
auth=aiohttp.BasicAuth(self.client_id, self.client_secret),
300+
headers={"Content-Type": "application/json"},
301+
)
302+
303+
if isinstance(response, dict):
304+
data = response.get("data")
305+
if isinstance(data, list) and len(data) > 0:
306+
has_permission = data[0]
307+
else:
308+
has_permission = response
309+
else:
310+
has_permission = response
301311

302-
async def batch_enforce(self, permission_model_name: str, permission_rules: List[List[str]]) -> List[bool]:
303-
"""
304-
Send data to Casdoor enforce API
305-
306-
:param permission_model_name: Name permission model
307-
:param permission_rules: permission rules to enforce
308-
[][0] -> sub: sub from Casbin
309-
[][1] -> obj: obj from Casbin
310-
[][2] -> act: act from Casbin
311-
[][3] -> v3: v3 from Casbin (optional)
312-
[][4] -> v4: v4 from Casbin (optional)
313-
[][5] -> v5: v5 from Casbin (optional)
314-
"""
315-
path = "/api/batch-enforce"
312+
if not isinstance(has_permission, bool):
313+
error_str = f"Casdoor response error (invalid type {type(has_permission)}):\n{json.dumps(response)}"
314+
raise ValueError(error_str)
316315

317-
def map_rule(rule: List[str], idx) -> Dict:
318-
if len(rule) < 3:
319-
raise ValueError(f"Invalid permission rule[{idx}]: {rule}")
320-
result = {"id": permission_model_name}
321-
for i in range(len(rule)):
322-
result.update({f"v{i}": rule[i]})
323-
return result
316+
return has_permission
324317

325-
params = [map_rule(permission_rules[i], i) for i in range(len(permission_rules))]
318+
async def batch_enforce(
319+
self,
320+
permission_id: str,
321+
model_id: str,
322+
enforce_id: str,
323+
owner: str,
324+
casbin_request: Optional[List[List[str]]] = None,
325+
) -> List[bool]:
326+
"""
327+
Send data to Casdoor batch enforce API asynchronously
328+
329+
:param permission_id: the permission id (i.e. organization name/permission name)
330+
:param model_id: the model id
331+
:param enforce_id: the enforce id
332+
:param owner: the owner of the permission
333+
:param casbin_request: a list of lists containing the request data
334+
:return: a list of boolean values indicating whether each request is allowed
335+
"""
336+
url = "/api/batch-enforce"
337+
params = {
338+
"permissionId": permission_id,
339+
"modelId": model_id,
340+
"enforceId": enforce_id,
341+
"owner": owner,
342+
}
326343

327344
async with self._session as session:
328-
enforce_results = await session.post(path, headers=self.headers, json=params)
329-
if not isinstance(enforce_results, bool):
330-
raise ValueError(f"Casdoor response error:{enforce_results}")
331-
332-
return enforce_results
345+
response = await session.post(
346+
url,
347+
params=params,
348+
data=json.dumps(casbin_request),
349+
auth=aiohttp.BasicAuth(self.client_id, self.client_secret),
350+
headers={"Content-Type": "application/json"},
351+
)
352+
353+
data = response.get("data")
354+
if data is None:
355+
error_str = "Casdoor response error: 'data' field is missing\n" + json.dumps(response)
356+
raise ValueError(error_str)
357+
358+
if not isinstance(data, list):
359+
error_str = f"Casdoor 'data' is not a list (got {type(data)}):\n{json.dumps(response)}"
360+
raise ValueError(error_str)
361+
362+
enforce_results = data[0] if data else []
363+
364+
if (
365+
not isinstance(enforce_results, list)
366+
or len(enforce_results) > 0
367+
and not isinstance(enforce_results[0], bool)
368+
):
369+
error_str = (
370+
f"Casdoor response contains invalid results (got {type(enforce_results)}):\n{json.dumps(response)}"
371+
)
372+
raise ValueError(error_str)
373+
374+
return enforce_results
333375

334376
async def get_users(self) -> Dict:
335377
"""

src/casdoor/main.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -258,7 +258,7 @@ def parse_jwt_token(self, token: str, **kwargs) -> Dict:
258258
certificate = x509.load_pem_x509_certificate(self.certification, default_backend())
259259

260260
return_json = jwt.decode(
261-
token.encode("utf-8"),
261+
token,
262262
certificate.public_key(),
263263
algorithms=self.algorithms,
264264
audience=self.client_id,

src/tests/test_async_oauth.py

Lines changed: 19 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414

15-
from unittest import IsolatedAsyncioTestCase, mock
15+
from unittest import IsolatedAsyncioTestCase
1616

1717
import src.tests.test_util as test_util
1818
from src.casdoor.async_main import AsyncCasdoorSDK
@@ -28,7 +28,7 @@ class TestOAuth(IsolatedAsyncioTestCase):
2828
"""
2929

3030
# server returned authorization code
31-
code = "6d038ac60d4e1f17e742"
31+
code = "21dc0ac806c27e6d7962"
3232

3333
# Casdoor user and password for auth with
3434
# Resource Owner Password Credentials Grant.
@@ -135,92 +135,29 @@ async def test_parse_jwt_token(self):
135135
self.assertIsInstance(decoded_msg, dict)
136136

137137
async def test_enforce(self):
138-
sdk = self.get_sdk()
139-
status = await sdk.enforce("built-in/permission-built-in", "admin", "a", "ac")
140-
self.assertIsInstance(status, bool)
141-
142-
def mocked_enforce_requests_post(*args, **kwargs):
143-
class MockResponse:
144-
def __init__(
145-
self,
146-
json_data,
147-
status_code=200,
148-
):
149-
self.json_data = json_data
150-
self.status_code = status_code
151-
152-
def json(self):
153-
return self.json_data
154-
155-
result = True
156-
for i in range(0, 5):
157-
if kwargs.get("json").get(f"v{i}") != f"v{i}":
158-
result = False
159-
160-
return MockResponse(result)
161-
162-
@mock.patch("aiohttp.ClientSession.post", side_effect=mocked_enforce_requests_post)
163-
async def test_enforce_parmas(self, mock_post):
164138
sdk = self.get_sdk()
165139
status = await sdk.enforce(
166-
"built-in/permission-built-in",
167-
"v0",
168-
"v1",
169-
"v2",
170-
v3="v3",
171-
v4="v4",
172-
v5="v5",
140+
permission_id="built-in/permission-built-in",
141+
model_id="",
142+
resource_id="",
143+
enforce_id="",
144+
owner="",
145+
casbin_request=["alice", "data1", "read"],
173146
)
174-
self.assertEqual(status, True)
175-
176-
def mocked_batch_enforce_requests_post(*args, **kwargs):
177-
class MockResponse:
178-
def __init__(
179-
self,
180-
json_data,
181-
status_code=200,
182-
):
183-
self.json_data = json_data
184-
self.status_code = status_code
185-
186-
def json(self):
187-
return self.json_data
188-
189-
json = kwargs.get("json")
190-
result = [True for i in range(0, len(json))]
191-
for k in range(0, len(json)):
192-
for i in range(0, len(json[k]) - 1):
193-
if json[k].get(f"v{i}") != f"v{i}":
194-
result[k] = False
195-
196-
return MockResponse(result)
197-
198-
@mock.patch(
199-
"aiohttp.ClientSession.post",
200-
side_effect=mocked_batch_enforce_requests_post,
201-
)
202-
def test_batch_enforce(self, mock_post):
147+
self.assertIsInstance(status, bool)
148+
149+
async def test_batch_enforce(self):
203150
sdk = self.get_sdk()
204-
status = sdk.batch_enforce(
205-
"built-in/permission-built-in",
206-
[
207-
["v0", "v1", "v2", "v3", "v4", "v5"],
208-
["v0", "v1", "v2", "v3", "v4", "v1"],
209-
],
151+
status = await sdk.batch_enforce(
152+
permission_id="built-in/permission-built-in",
153+
model_id="",
154+
enforce_id="",
155+
owner="",
156+
casbin_request=[["alice", "data1", "read"], ["bob", "data2", "write"]],
210157
)
211158
self.assertEqual(len(status), 2)
212-
self.assertEqual(status[0], True)
213-
self.assertEqual(status[1], False)
214-
215-
@mock.patch(
216-
"aiohttp.ClientSession.post",
217-
side_effect=mocked_batch_enforce_requests_post,
218-
)
219-
def test_batch_enforce_raise(self, mock_post):
220-
sdk = self.get_sdk()
221-
with self.assertRaises(ValueError) as context:
222-
sdk.batch_enforce("built-in/permission-built-in", [["v0", "v1"]])
223-
self.assertEqual("Invalid permission rule[0]: ['v0', 'v1']", str(context.exception))
159+
self.assertIsInstance(status[0], bool)
160+
self.assertIsInstance(status[1], bool)
224161

225162
async def test_get_users(self):
226163
sdk = self.get_sdk()

0 commit comments

Comments
 (0)