Skip to content

Commit 875263c

Browse files
authored
feat: support sync & async load_filtered_policy (#5)
1 parent f8064c6 commit 875263c

9 files changed

Lines changed: 424 additions & 17 deletions

File tree

README.md

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,18 @@
11
PyMongo Adapter for PyCasbin
22
====
33

4-
[![Build Status](https://www.travis-ci.org/officialpycasbin/pymongo-adapter.svg?branch=master)](https://www.travis-ci.org/officialpycasbin/pymongo-adapter)
4+
[![build Status](https://github.com/officialpycasbin/pymongo-adapter/actions/workflows/main.yml/badge.svg)](https://github.com/officialpycasbin/pymongo-adapter/actions/workflows/main.yml)
55
[![Coverage Status](https://coveralls.io/repos/github/officialpycasbin/pymongo-adapter/badge.svg)](https://coveralls.io/github/officialpycasbin/pymongo-adapter)
66
[![Version](https://img.shields.io/pypi/v/casbin_pymongo_adapter.svg)](https://pypi.org/project/casbin_pymongo_adapter/)
77
[![PyPI - Wheel](https://img.shields.io/pypi/wheel/casbin_pymongo_adapter.svg)](https://pypi.org/project/casbin_pymongo_adapter/)
88
[![Pyversions](https://img.shields.io/pypi/pyversions/casbin_pymongo_adapter.svg)](https://pypi.org/project/casbin_pymongo_adapter/)
9-
[![Download](https://img.shields.io/pypi/dm/casbin_pymongo_adapter.svg)](https://pypi.org/project/casbin_pymongo_adapter/)
9+
[![Download](https://static.pepy.tech/badge/casbin_pymongo_adapter)](https://pypi.org/project/casbin_pymongo_adapter/)
1010
[![License](https://img.shields.io/pypi/l/casbin_pymongo_adapter.svg)](https://pypi.org/project/casbin_pymongo_adapter/)
1111

1212
PyMongo Adapter is the [PyMongo](https://pypi.org/project/pymongo/) adapter for [PyCasbin](https://github.com/casbin/pycasbin). With this library, Casbin can load policy from MongoDB or save policy to it.
1313

14+
This adapter supports both synchronous and asynchronous PyMongo APIs.
15+
1416
## Installation
1517

1618
```
@@ -37,6 +39,38 @@ if e.enforce(sub, obj, act):
3739
else:
3840
# deny the request, show an error
3941
pass
42+
43+
# define filter conditions
44+
from casbin_pymongo_adapter import Filter
45+
46+
filter = Filter()
47+
filter.ptype = ["p"]
48+
filter.v0 = ["alice"]
49+
50+
# support MongoDB native query
51+
filter.raw_query = {
52+
"ptype": "p",
53+
"v0": {
54+
"$in": ["alice"]
55+
}
56+
}
57+
58+
# In this case, load only policies with sub value alice
59+
e.load_filtered_policy(filter)
60+
```
61+
62+
## Async Example
63+
64+
```python
65+
from casbin_pymongo_adapter.asynchronous import Adapter
66+
import casbin
67+
68+
adapter = Adapter('mongodb://localhost:27017/', "dbname")
69+
e = casbin.AsyncEnforcer('path/to/model.conf', adapter)
70+
71+
# Note: AsyncEnforcer does not automatically load policies.
72+
# You need to call load_policy() manually.
73+
await e.load_policy()
4074
```
4175

4276

casbin_pymongo_adapter/__init__.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,9 @@
11
from .adapter import Adapter
2+
from ._filter import Filter
3+
from ._rule import CasbinRule
4+
5+
__all__ = [
6+
"Adapter",
7+
"Filter",
8+
"CasbinRule",
9+
]

casbin_pymongo_adapter/_filter.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
class Filter:
2+
"""
3+
Filter rule model
4+
"""
5+
6+
ptype = []
7+
v0 = []
8+
v1 = []
9+
v2 = []
10+
v3 = []
11+
v4 = []
12+
v5 = []
13+
14+
# `raw_query` expected dict.
15+
# if set `raw_query`, all other filters are ignored
16+
raw_query = None

casbin_pymongo_adapter/adapter.py

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,18 +7,29 @@
77
class Adapter(persist.Adapter):
88
"""the interface for Casbin adapters."""
99

10-
def __init__(self, uri, dbname, collection="casbin_rule"):
10+
def __init__(
11+
self,
12+
uri,
13+
dbname,
14+
collection="casbin_rule",
15+
filtered=False,
16+
):
1117
"""Create an adapter for Mongodb
1218
1319
Args:
1420
uri (str): This should be the same requiement as pymongo Client's 'uri' parameter.
1521
See https://pymongo.readthedocs.io/en/stable/api/pymongo/mongo_client.html#pymongo.mongo_client.MongoClient.
1622
dbname (str): Database to store policy.
1723
collection (str, optional): Collection of the choosen database. Defaults to "casbin_rule".
24+
filtered (bool, optional): Whether to use filtered query. Defaults to False.
1825
"""
1926
client = MongoClient(uri)
2027
db = client[dbname]
2128
self._collection = db[collection]
29+
self._filtered = filtered
30+
31+
def is_filtered(self):
32+
return self._filtered
2233

2334
def load_policy(self, model):
2435
"""Implementing add Interface for casbin. Load all policy rules from mongodb
@@ -36,6 +47,32 @@ def load_policy(self, model):
3647

3748
persist.load_policy_line(str(rule), model)
3849

50+
def load_filtered_policy(self, model, filter):
51+
"""Load filtered policy rules from mongodb
52+
53+
Args:
54+
model (CasbinRule): CasbinRule object
55+
filter (Filter): Filter rule object
56+
"""
57+
query = {}
58+
if getattr(filter, "raw_query", None) is None:
59+
for attr in ("ptype", "v0", "v1", "v2", "v3", "v4", "v5"):
60+
if len(getattr(filter, attr)) > 0:
61+
value = getattr(filter, attr)
62+
query[attr] = {"$in": value}
63+
else:
64+
query = getattr(filter, "raw_query")
65+
66+
for line in self._collection.find(query):
67+
if "ptype" not in line:
68+
continue
69+
rule = CasbinRule(line["ptype"])
70+
for key, value in line.items():
71+
setattr(rule, key, value)
72+
73+
persist.load_policy_line(str(rule), model)
74+
self._filtered = True
75+
3976
def _save_policy_line(self, ptype, rule):
4077
line = CasbinRule(ptype=ptype)
4178
for index, value in enumerate(rule):
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
from .adapter import Adapter
2+
3+
__all__ = [
4+
"Adapter",
5+
]

casbin_pymongo_adapter/asynchronous/adapter.py

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,18 +8,29 @@
88
class Adapter(AsyncAdapter):
99
"""the interface for Casbin adapters."""
1010

11-
def __init__(self, uri, dbname, collection="casbin_rule"):
11+
def __init__(
12+
self,
13+
uri,
14+
dbname,
15+
collection="casbin_rule",
16+
filtered=False,
17+
):
1218
"""Create an adapter for Mongodb
1319
1420
Args:
1521
uri (str): This should be the same requiement as pymongo Client's 'uri' parameter.
1622
See https://pymongo.readthedocs.io/en/stable/api/pymongo/mongo_client.html#pymongo.mongo_client.MongoClient.
1723
dbname (str): Database to store policy.
1824
collection (str, optional): Collection of the choosen database. Defaults to "casbin_rule".
25+
filtered (bool, optional): Whether to use filtered query. Defaults to False.
1926
"""
2027
client = AsyncMongoClient(uri)
2128
db = client[dbname]
2229
self._collection = db[collection]
30+
self._filtered = filtered
31+
32+
def is_filtered(self):
33+
return self._filtered
2334

2435
async def load_policy(self, model):
2536
"""Implementing add Interface for casbin. Load all policy rules from mongodb
@@ -37,6 +48,32 @@ async def load_policy(self, model):
3748

3849
persist.load_policy_line(str(rule), model)
3950

51+
async def load_filtered_policy(self, model, filter):
52+
"""Load filtered policy rules from mongodb
53+
54+
Args:
55+
model (CasbinRule): CasbinRule object
56+
filter (Filter): Filter rule object
57+
"""
58+
query = {}
59+
if getattr(filter, "raw_query", None) is None:
60+
for attr in ("ptype", "v0", "v1", "v2", "v3", "v4", "v5"):
61+
if len(getattr(filter, attr)) > 0:
62+
value = getattr(filter, attr)
63+
query[attr] = {"$in": value}
64+
else:
65+
query = getattr(filter, "raw_query")
66+
67+
async for line in self._collection.find(query):
68+
if "ptype" not in line:
69+
continue
70+
rule = CasbinRule(line["ptype"])
71+
for key, value in line.items():
72+
setattr(rule, key, value)
73+
74+
persist.load_policy_line(str(rule), model)
75+
self._filtered = True
76+
4077
async def _save_policy_line(self, ptype, rule):
4178
line = CasbinRule(ptype=ptype)
4279
for index, value in enumerate(rule):

tests/asynchronous/__init__.py

Whitespace-only changes.

tests/asynchronous/test_adapter.py

Lines changed: 139 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
from casbin_pymongo_adapter.asynchronous.adapter import Adapter
2-
from casbin_pymongo_adapter._rule import CasbinRule
1+
from casbin_pymongo_adapter.asynchronous import Adapter
2+
from casbin_pymongo_adapter import Filter
33
from pymongo import AsyncMongoClient
44
from unittest import IsolatedAsyncioTestCase
55
import casbin
@@ -196,18 +196,145 @@ async def test_remove_filtered_policy(self):
196196
self.assertFalse(e.enforce("alice", "data2", "read"))
197197
self.assertFalse(e.enforce("alice", "data2", "write"))
198198

199-
def test_str(self):
199+
async def test_filtered_policy(self):
200200
"""
201-
test __str__ function
201+
test filtered_policy
202202
"""
203-
rule = CasbinRule(ptype="p", v0="alice", v1="data1", v2="read")
204-
self.assertEqual(rule.__str__(), "p, alice, data1, read")
203+
e = await get_enforcer()
204+
filter = Filter()
205+
206+
filter.ptype = ["p"]
207+
await e.load_filtered_policy(filter)
208+
self.assertTrue(e.enforce("alice", "data1", "read"))
209+
self.assertFalse(e.enforce("alice", "data1", "write"))
210+
self.assertFalse(e.enforce("alice", "data2", "read"))
211+
self.assertFalse(e.enforce("alice", "data2", "write"))
212+
self.assertFalse(e.enforce("bob", "data1", "read"))
213+
self.assertFalse(e.enforce("bob", "data1", "write"))
214+
self.assertFalse(e.enforce("bob", "data2", "read"))
215+
self.assertTrue(e.enforce("bob", "data2", "write"))
216+
217+
filter.ptype = []
218+
filter.v0 = ["alice"]
219+
await e.load_filtered_policy(filter)
220+
self.assertTrue(e.enforce("alice", "data1", "read"))
221+
self.assertFalse(e.enforce("alice", "data1", "write"))
222+
self.assertFalse(e.enforce("alice", "data2", "read"))
223+
self.assertFalse(e.enforce("alice", "data2", "write"))
224+
self.assertFalse(e.enforce("bob", "data1", "read"))
225+
self.assertFalse(e.enforce("bob", "data1", "write"))
226+
self.assertFalse(e.enforce("bob", "data2", "read"))
227+
self.assertFalse(e.enforce("bob", "data2", "write"))
228+
self.assertFalse(e.enforce("data2_admin", "data2", "read"))
229+
self.assertFalse(e.enforce("data2_admin", "data2", "write"))
230+
231+
filter.v0 = ["bob"]
232+
await e.load_filtered_policy(filter)
233+
self.assertFalse(e.enforce("alice", "data1", "read"))
234+
self.assertFalse(e.enforce("alice", "data1", "write"))
235+
self.assertFalse(e.enforce("alice", "data2", "read"))
236+
self.assertFalse(e.enforce("alice", "data2", "write"))
237+
self.assertFalse(e.enforce("bob", "data1", "read"))
238+
self.assertFalse(e.enforce("bob", "data1", "write"))
239+
self.assertFalse(e.enforce("bob", "data2", "read"))
240+
self.assertTrue(e.enforce("bob", "data2", "write"))
241+
self.assertFalse(e.enforce("data2_admin", "data2", "read"))
242+
self.assertFalse(e.enforce("data2_admin", "data2", "write"))
243+
244+
filter.v0 = ["data2_admin"]
245+
await e.load_filtered_policy(filter)
246+
self.assertTrue(e.enforce("data2_admin", "data2", "read"))
247+
self.assertTrue(e.enforce("data2_admin", "data2", "read"))
248+
self.assertFalse(e.enforce("alice", "data1", "read"))
249+
self.assertFalse(e.enforce("alice", "data1", "write"))
250+
self.assertFalse(e.enforce("alice", "data2", "read"))
251+
self.assertFalse(e.enforce("alice", "data2", "write"))
252+
self.assertFalse(e.enforce("bob", "data1", "read"))
253+
self.assertFalse(e.enforce("bob", "data1", "write"))
254+
self.assertFalse(e.enforce("bob", "data2", "read"))
255+
self.assertFalse(e.enforce("bob", "data2", "write"))
256+
257+
filter.v0 = ["alice", "bob"]
258+
await e.load_filtered_policy(filter)
259+
self.assertTrue(e.enforce("alice", "data1", "read"))
260+
self.assertFalse(e.enforce("alice", "data1", "write"))
261+
self.assertFalse(e.enforce("alice", "data2", "read"))
262+
self.assertFalse(e.enforce("alice", "data2", "write"))
263+
self.assertFalse(e.enforce("bob", "data1", "read"))
264+
self.assertFalse(e.enforce("bob", "data1", "write"))
265+
self.assertFalse(e.enforce("bob", "data2", "read"))
266+
self.assertTrue(e.enforce("bob", "data2", "write"))
267+
self.assertFalse(e.enforce("data2_admin", "data2", "read"))
268+
self.assertFalse(e.enforce("data2_admin", "data2", "write"))
269+
270+
filter.v0 = []
271+
filter.v1 = ["data1"]
272+
await e.load_filtered_policy(filter)
273+
self.assertTrue(e.enforce("alice", "data1", "read"))
274+
self.assertFalse(e.enforce("alice", "data1", "write"))
275+
self.assertFalse(e.enforce("alice", "data2", "read"))
276+
self.assertFalse(e.enforce("alice", "data2", "write"))
277+
self.assertFalse(e.enforce("bob", "data1", "read"))
278+
self.assertFalse(e.enforce("bob", "data1", "write"))
279+
self.assertFalse(e.enforce("bob", "data2", "read"))
280+
self.assertFalse(e.enforce("bob", "data2", "write"))
281+
self.assertFalse(e.enforce("data2_admin", "data2", "read"))
282+
self.assertFalse(e.enforce("data2_admin", "data2", "write"))
283+
284+
filter.v1 = ["data2"]
285+
await e.load_filtered_policy(filter)
286+
self.assertFalse(e.enforce("alice", "data1", "read"))
287+
self.assertFalse(e.enforce("alice", "data1", "write"))
288+
self.assertFalse(e.enforce("alice", "data2", "read"))
289+
self.assertFalse(e.enforce("alice", "data2", "write"))
290+
self.assertFalse(e.enforce("bob", "data1", "read"))
291+
self.assertFalse(e.enforce("bob", "data1", "write"))
292+
self.assertFalse(e.enforce("bob", "data2", "read"))
293+
self.assertTrue(e.enforce("bob", "data2", "write"))
294+
self.assertTrue(e.enforce("data2_admin", "data2", "read"))
295+
self.assertTrue(e.enforce("data2_admin", "data2", "write"))
296+
297+
filter.v1 = []
298+
filter.v2 = ["read"]
299+
await e.load_filtered_policy(filter)
300+
self.assertTrue(e.enforce("alice", "data1", "read"))
301+
self.assertFalse(e.enforce("alice", "data1", "write"))
302+
self.assertFalse(e.enforce("alice", "data2", "read"))
303+
self.assertFalse(e.enforce("alice", "data2", "write"))
304+
self.assertFalse(e.enforce("bob", "data1", "read"))
305+
self.assertFalse(e.enforce("bob", "data1", "write"))
306+
self.assertFalse(e.enforce("bob", "data2", "read"))
307+
self.assertFalse(e.enforce("bob", "data2", "write"))
308+
self.assertTrue(e.enforce("data2_admin", "data2", "read"))
309+
self.assertFalse(e.enforce("data2_admin", "data2", "write"))
310+
311+
filter.v2 = ["write"]
312+
await e.load_filtered_policy(filter)
313+
self.assertFalse(e.enforce("alice", "data1", "read"))
314+
self.assertFalse(e.enforce("alice", "data1", "write"))
315+
self.assertFalse(e.enforce("alice", "data2", "read"))
316+
self.assertFalse(e.enforce("alice", "data2", "write"))
317+
self.assertFalse(e.enforce("bob", "data1", "read"))
318+
self.assertFalse(e.enforce("bob", "data1", "write"))
319+
self.assertFalse(e.enforce("bob", "data2", "read"))
320+
self.assertTrue(e.enforce("bob", "data2", "write"))
321+
self.assertFalse(e.enforce("data2_admin", "data2", "read"))
322+
self.assertTrue(e.enforce("data2_admin", "data2", "write"))
205323

206-
def test_dict(self):
324+
async def test_filtered_policy_with_raw_query(self):
207325
"""
208-
test __str__ function
326+
test filtered_policy
209327
"""
210-
rule = CasbinRule(ptype="p", v0="alice", v1="data1", v2="read")
211-
self.assertEqual(
212-
rule.dict(), {"ptype": "p", "v0": "alice", "v1": "data1", "v2": "read"}
213-
)
328+
e = await get_enforcer()
329+
filter = Filter()
330+
filter.raw_query = {"ptype": "p", "v0": {"$in": ["alice", "bob"]}}
331+
332+
await e.load_filtered_policy(filter)
333+
self.assertTrue(e.enforce("alice", "data1", "read"))
334+
self.assertFalse(e.enforce("alice", "data1", "write"))
335+
self.assertFalse(e.enforce("alice", "data2", "read"))
336+
self.assertFalse(e.enforce("alice", "data2", "write"))
337+
self.assertFalse(e.enforce("bob", "data1", "read"))
338+
self.assertFalse(e.enforce("bob", "data1", "write"))
339+
self.assertFalse(e.enforce("bob", "data2", "read"))
340+
self.assertTrue(e.enforce("bob", "data2", "write"))

0 commit comments

Comments
 (0)