Skip to content
38 changes: 36 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
PyMongo Adapter for PyCasbin
====

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

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.

This adapter supports both synchronous and asynchronous PyMongo APIs.

## Installation

```
Expand All @@ -37,6 +39,38 @@ if e.enforce(sub, obj, act):
else:
# deny the request, show an error
pass

# define filter conditions
from casbin_pymongo_adapter import Filter

filter = Filter()
filter.ptype = ["p"]
filter.v0 = ["alice"]

# support MongoDB native query
filter.raw_query = {
"ptype": "p",
"v0": {
"$in": ["alice"]
}
}

# In this case, load only policies with sub value alice
e.load_filtered_policy(filter)
```

## Async Example

```python
from casbin_pymongo_adapter.asynchronous import Adapter
import casbin

adapter = Adapter('mongodb://localhost:27017/', "dbname")
e = casbin.AsyncEnforcer('path/to/model.conf', adapter)

# Note: AsyncEnforcer does not automatically load policies.
# You need to call load_policy() manually.
await e.load_policy()
```


Expand Down
8 changes: 8 additions & 0 deletions casbin_pymongo_adapter/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1,9 @@
from .adapter import Adapter
from ._filter import Filter
from ._rule import CasbinRule

__all__ = [
"Adapter",
"Filter",
"CasbinRule",
]
16 changes: 16 additions & 0 deletions casbin_pymongo_adapter/_filter.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
class Filter:
"""
Filter rule model
"""

ptype = []
v0 = []
v1 = []
v2 = []
v3 = []
v4 = []
v5 = []

# `raw_query` expected dict.
# if set `raw_query`, all other filters are ignored
raw_query = None
39 changes: 38 additions & 1 deletion casbin_pymongo_adapter/adapter.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,29 @@
class Adapter(persist.Adapter):
"""the interface for Casbin adapters."""

def __init__(self, uri, dbname, collection="casbin_rule"):
def __init__(
self,
uri,
dbname,
collection="casbin_rule",
filtered=False,
):
"""Create an adapter for Mongodb

Args:
uri (str): This should be the same requiement as pymongo Client's 'uri' parameter.
See https://pymongo.readthedocs.io/en/stable/api/pymongo/mongo_client.html#pymongo.mongo_client.MongoClient.
dbname (str): Database to store policy.
collection (str, optional): Collection of the choosen database. Defaults to "casbin_rule".
filtered (bool, optional): Whether to use filtered query. Defaults to False.
"""
client = MongoClient(uri)
db = client[dbname]
self._collection = db[collection]
self._filtered = filtered

def is_filtered(self):
return self._filtered

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

persist.load_policy_line(str(rule), model)

def load_filtered_policy(self, model, filter):
"""Load filtered policy rules from mongodb

Args:
model (CasbinRule): CasbinRule object
filter (Filter): Filter rule object
"""
query = {}
if getattr(filter, "raw_query", None) is None:
for attr in ("ptype", "v0", "v1", "v2", "v3", "v4", "v5"):
if len(getattr(filter, attr)) > 0:
value = getattr(filter, attr)
query[attr] = {"$in": value}
else:
query = getattr(filter, "raw_query")

for line in self._collection.find(query):
if "ptype" not in line:
continue
rule = CasbinRule(line["ptype"])
for key, value in line.items():
setattr(rule, key, value)

persist.load_policy_line(str(rule), model)
self._filtered = True

def _save_policy_line(self, ptype, rule):
line = CasbinRule(ptype=ptype)
for index, value in enumerate(rule):
Expand Down
5 changes: 5 additions & 0 deletions casbin_pymongo_adapter/asynchronous/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from .adapter import Adapter

__all__ = [
"Adapter",
]
39 changes: 38 additions & 1 deletion casbin_pymongo_adapter/asynchronous/adapter.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,29 @@
class Adapter(AsyncAdapter):
"""the interface for Casbin adapters."""

def __init__(self, uri, dbname, collection="casbin_rule"):
def __init__(
self,
uri,
dbname,
collection="casbin_rule",
filtered=False,
):
"""Create an adapter for Mongodb

Args:
uri (str): This should be the same requiement as pymongo Client's 'uri' parameter.
See https://pymongo.readthedocs.io/en/stable/api/pymongo/mongo_client.html#pymongo.mongo_client.MongoClient.
dbname (str): Database to store policy.
collection (str, optional): Collection of the choosen database. Defaults to "casbin_rule".
filtered (bool, optional): Whether to use filtered query. Defaults to False.
"""
client = AsyncMongoClient(uri)
db = client[dbname]
self._collection = db[collection]
self._filtered = filtered

def is_filtered(self):
return self._filtered

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

persist.load_policy_line(str(rule), model)

async def load_filtered_policy(self, model, filter):
"""Load filtered policy rules from mongodb

Args:
model (CasbinRule): CasbinRule object
filter (Filter): Filter rule object
"""
query = {}
if getattr(filter, "raw_query", None) is None:
for attr in ("ptype", "v0", "v1", "v2", "v3", "v4", "v5"):
if len(getattr(filter, attr)) > 0:
value = getattr(filter, attr)
query[attr] = {"$in": value}
else:
query = getattr(filter, "raw_query")

async for line in self._collection.find(query):
if "ptype" not in line:
continue
rule = CasbinRule(line["ptype"])
for key, value in line.items():
setattr(rule, key, value)

persist.load_policy_line(str(rule), model)
self._filtered = True

async def _save_policy_line(self, ptype, rule):
line = CasbinRule(ptype=ptype)
for index, value in enumerate(rule):
Expand Down
Empty file added tests/asynchronous/__init__.py
Empty file.
151 changes: 139 additions & 12 deletions tests/asynchronous/test_adapter.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from casbin_pymongo_adapter.asynchronous.adapter import Adapter
from casbin_pymongo_adapter._rule import CasbinRule
from casbin_pymongo_adapter.asynchronous import Adapter
from casbin_pymongo_adapter import Filter
from pymongo import AsyncMongoClient
from unittest import IsolatedAsyncioTestCase
import casbin
Expand Down Expand Up @@ -196,18 +196,145 @@ async def test_remove_filtered_policy(self):
self.assertFalse(e.enforce("alice", "data2", "read"))
self.assertFalse(e.enforce("alice", "data2", "write"))

def test_str(self):
async def test_filtered_policy(self):
"""
test __str__ function
test filtered_policy
"""
rule = CasbinRule(ptype="p", v0="alice", v1="data1", v2="read")
self.assertEqual(rule.__str__(), "p, alice, data1, read")
e = await get_enforcer()
filter = Filter()

filter.ptype = ["p"]
await e.load_filtered_policy(filter)
self.assertTrue(e.enforce("alice", "data1", "read"))
self.assertFalse(e.enforce("alice", "data1", "write"))
self.assertFalse(e.enforce("alice", "data2", "read"))
self.assertFalse(e.enforce("alice", "data2", "write"))
self.assertFalse(e.enforce("bob", "data1", "read"))
self.assertFalse(e.enforce("bob", "data1", "write"))
self.assertFalse(e.enforce("bob", "data2", "read"))
self.assertTrue(e.enforce("bob", "data2", "write"))

filter.ptype = []
filter.v0 = ["alice"]
await e.load_filtered_policy(filter)
self.assertTrue(e.enforce("alice", "data1", "read"))
self.assertFalse(e.enforce("alice", "data1", "write"))
self.assertFalse(e.enforce("alice", "data2", "read"))
self.assertFalse(e.enforce("alice", "data2", "write"))
self.assertFalse(e.enforce("bob", "data1", "read"))
self.assertFalse(e.enforce("bob", "data1", "write"))
self.assertFalse(e.enforce("bob", "data2", "read"))
self.assertFalse(e.enforce("bob", "data2", "write"))
self.assertFalse(e.enforce("data2_admin", "data2", "read"))
self.assertFalse(e.enforce("data2_admin", "data2", "write"))

filter.v0 = ["bob"]
await e.load_filtered_policy(filter)
self.assertFalse(e.enforce("alice", "data1", "read"))
self.assertFalse(e.enforce("alice", "data1", "write"))
self.assertFalse(e.enforce("alice", "data2", "read"))
self.assertFalse(e.enforce("alice", "data2", "write"))
self.assertFalse(e.enforce("bob", "data1", "read"))
self.assertFalse(e.enforce("bob", "data1", "write"))
self.assertFalse(e.enforce("bob", "data2", "read"))
self.assertTrue(e.enforce("bob", "data2", "write"))
self.assertFalse(e.enforce("data2_admin", "data2", "read"))
self.assertFalse(e.enforce("data2_admin", "data2", "write"))

filter.v0 = ["data2_admin"]
await e.load_filtered_policy(filter)
self.assertTrue(e.enforce("data2_admin", "data2", "read"))
self.assertTrue(e.enforce("data2_admin", "data2", "read"))
self.assertFalse(e.enforce("alice", "data1", "read"))
self.assertFalse(e.enforce("alice", "data1", "write"))
self.assertFalse(e.enforce("alice", "data2", "read"))
self.assertFalse(e.enforce("alice", "data2", "write"))
self.assertFalse(e.enforce("bob", "data1", "read"))
self.assertFalse(e.enforce("bob", "data1", "write"))
self.assertFalse(e.enforce("bob", "data2", "read"))
self.assertFalse(e.enforce("bob", "data2", "write"))

filter.v0 = ["alice", "bob"]
await e.load_filtered_policy(filter)
self.assertTrue(e.enforce("alice", "data1", "read"))
self.assertFalse(e.enforce("alice", "data1", "write"))
self.assertFalse(e.enforce("alice", "data2", "read"))
self.assertFalse(e.enforce("alice", "data2", "write"))
self.assertFalse(e.enforce("bob", "data1", "read"))
self.assertFalse(e.enforce("bob", "data1", "write"))
self.assertFalse(e.enforce("bob", "data2", "read"))
self.assertTrue(e.enforce("bob", "data2", "write"))
self.assertFalse(e.enforce("data2_admin", "data2", "read"))
self.assertFalse(e.enforce("data2_admin", "data2", "write"))

filter.v0 = []
filter.v1 = ["data1"]
await e.load_filtered_policy(filter)
self.assertTrue(e.enforce("alice", "data1", "read"))
self.assertFalse(e.enforce("alice", "data1", "write"))
self.assertFalse(e.enforce("alice", "data2", "read"))
self.assertFalse(e.enforce("alice", "data2", "write"))
self.assertFalse(e.enforce("bob", "data1", "read"))
self.assertFalse(e.enforce("bob", "data1", "write"))
self.assertFalse(e.enforce("bob", "data2", "read"))
self.assertFalse(e.enforce("bob", "data2", "write"))
self.assertFalse(e.enforce("data2_admin", "data2", "read"))
self.assertFalse(e.enforce("data2_admin", "data2", "write"))

filter.v1 = ["data2"]
await e.load_filtered_policy(filter)
self.assertFalse(e.enforce("alice", "data1", "read"))
self.assertFalse(e.enforce("alice", "data1", "write"))
self.assertFalse(e.enforce("alice", "data2", "read"))
self.assertFalse(e.enforce("alice", "data2", "write"))
self.assertFalse(e.enforce("bob", "data1", "read"))
self.assertFalse(e.enforce("bob", "data1", "write"))
self.assertFalse(e.enforce("bob", "data2", "read"))
self.assertTrue(e.enforce("bob", "data2", "write"))
self.assertTrue(e.enforce("data2_admin", "data2", "read"))
self.assertTrue(e.enforce("data2_admin", "data2", "write"))

filter.v1 = []
filter.v2 = ["read"]
await e.load_filtered_policy(filter)
self.assertTrue(e.enforce("alice", "data1", "read"))
self.assertFalse(e.enforce("alice", "data1", "write"))
self.assertFalse(e.enforce("alice", "data2", "read"))
self.assertFalse(e.enforce("alice", "data2", "write"))
self.assertFalse(e.enforce("bob", "data1", "read"))
self.assertFalse(e.enforce("bob", "data1", "write"))
self.assertFalse(e.enforce("bob", "data2", "read"))
self.assertFalse(e.enforce("bob", "data2", "write"))
self.assertTrue(e.enforce("data2_admin", "data2", "read"))
self.assertFalse(e.enforce("data2_admin", "data2", "write"))

filter.v2 = ["write"]
await e.load_filtered_policy(filter)
self.assertFalse(e.enforce("alice", "data1", "read"))
self.assertFalse(e.enforce("alice", "data1", "write"))
self.assertFalse(e.enforce("alice", "data2", "read"))
self.assertFalse(e.enforce("alice", "data2", "write"))
self.assertFalse(e.enforce("bob", "data1", "read"))
self.assertFalse(e.enforce("bob", "data1", "write"))
self.assertFalse(e.enforce("bob", "data2", "read"))
self.assertTrue(e.enforce("bob", "data2", "write"))
self.assertFalse(e.enforce("data2_admin", "data2", "read"))
self.assertTrue(e.enforce("data2_admin", "data2", "write"))

def test_dict(self):
async def test_filtered_policy_with_raw_query(self):
"""
test __str__ function
test filtered_policy
"""
rule = CasbinRule(ptype="p", v0="alice", v1="data1", v2="read")
self.assertEqual(
rule.dict(), {"ptype": "p", "v0": "alice", "v1": "data1", "v2": "read"}
)
e = await get_enforcer()
filter = Filter()
filter.raw_query = {"ptype": "p", "v0": {"$in": ["alice", "bob"]}}

await e.load_filtered_policy(filter)
self.assertTrue(e.enforce("alice", "data1", "read"))
self.assertFalse(e.enforce("alice", "data1", "write"))
self.assertFalse(e.enforce("alice", "data2", "read"))
self.assertFalse(e.enforce("alice", "data2", "write"))
self.assertFalse(e.enforce("bob", "data1", "read"))
self.assertFalse(e.enforce("bob", "data1", "write"))
self.assertFalse(e.enforce("bob", "data2", "read"))
self.assertTrue(e.enforce("bob", "data2", "write"))
Loading