Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add interface stubs for async adapters #335

Merged
merged 7 commits into from
Dec 28, 2023
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -232,7 +232,7 @@ https://casbin.org/docs/role-managers

If your code use `async` / `await` and is heavily dependent on I/O operations, you can adopt Async Enforcer!

1. Create an async engine and new a Casbin AsyncEnforcer with a model file and an async Pycasbin adapter:
1. Create an async engine and new a Casbin AsyncEnforcer with a model file and an async Pycasbin adapter (AsyncAdapter subclass):

```python
import asyncio
Expand Down Expand Up @@ -266,6 +266,8 @@ async def get_enforcer():

Note: you can see all supported adapters in [Adapters | Casbin](https://casbin.org/docs/adapters).

Built-in async adapters are available in `casbin.persist.adapters.asyncio`.

2. Add an enforcement hook into your code right before the access happens:

```python
Expand Down
5 changes: 2 additions & 3 deletions casbin/async_internal_enforcer.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,7 @@

from casbin.core_enforcer import CoreEnforcer
from casbin.model import Model, FunctionMap
from casbin.persist import Adapter
from casbin.persist.adapters.async_file_adapter import AsyncFileAdapter
from casbin.persist.adapters.asyncio import AsyncFileAdapter, AsyncAdapter


class AsyncInternalEnforcer(CoreEnforcer):
Expand All @@ -32,7 +31,7 @@ def init_with_file(self, model_path, policy_path):
def init_with_model_and_adapter(self, m, adapter=None):
"""initializes an enforcer with a model and a database adapter."""

if not isinstance(m, Model) or adapter is not None and not isinstance(adapter, Adapter):
if not isinstance(m, Model) or adapter is not None and not isinstance(adapter, AsyncAdapter):
raise RuntimeError("Invalid parameters for enforcer.")

self.adapter = adapter
Expand Down
2 changes: 1 addition & 1 deletion casbin/persist/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,5 @@

from .adapter import *
from .adapter_filtered import *
from .batch_adapter import *
from .adapters import *
from .batch_adapter import *
7 changes: 6 additions & 1 deletion casbin/persist/adapters/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,10 @@
# limitations under the License.

from .file_adapter import FileAdapter
from .adapter_filtered import FilteredAdapter
from .filtered_file_adapter import FilteredFileAdapter
from .update_adapter import UpdateAdapter

# alias import for backwards compatibility
FilteredAdapter = FilteredFileAdapter

__all__ = ["FileAdapter", "FilteredFileAdapter", "FilteredAdapter", "UpdateAdapter"]
109 changes: 3 additions & 106 deletions casbin/persist/adapters/adapter_filtered.py
Original file line number Diff line number Diff line change
@@ -1,107 +1,4 @@
# Copyright 2021 The casbin Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from .filtered_file_adapter import Filter
from .filtered_file_adapter import FilteredFileAdapter as FilteredAdapter

from casbin import persist
from .file_adapter import FileAdapter
import os


class Filter:
# P,G are string []
P = []
G = []


class FilteredAdapter(FileAdapter, persist.FilteredAdapter):
filtered = False
_file_path = ""
filter = Filter()
# new_filtered_adapte is the constructor for FilteredAdapter.
def __init__(self, file_path):
self.filtered = True
self._file_path = file_path

def load_policy(self, model):
if not os.path.isfile(self._file_path):
raise RuntimeError("invalid file path, file path cannot be empty")
self.filtered = False
self._load_policy_file(model)

# load_filtered_policy loads only policy rules that match the filter.
def load_filtered_policy(self, model, filter):
if filter == None:
return self.load_policy(model)

if not os.path.isfile(self._file_path):
raise RuntimeError("invalid file path, file path cannot be empty")

try:
filter_value = [filter.__dict__["P"]] + [filter.__dict__["G"]]
except:
raise RuntimeError("invalid filter type")

self.load_filtered_policy_file(model, filter_value, persist.load_policy_line)
self.filtered = True

def load_filtered_policy_file(self, model, filter, hanlder):
with open(self._file_path, "rb") as file:
while True:
line = file.readline()
line = line.decode().strip()
if line == "\n":
continue
if not line:
break
if filter_line(line, filter):
continue

hanlder(line, model)

# is_filtered returns true if the loaded policy has been filtered.
def is_filtered(self):
return self.filtered

def save_policy(self, model):
if self.filtered:
raise RuntimeError("cannot save a filtered policy")

self._save_policy_file(model)


def filter_line(line, filter):
if filter == None:
return False

p = line.split(",")
if len(p) == 0:
return True
filter_slice = []

if p[0].strip() == "p":
filter_slice = filter[0]
elif p[0].strip() == "g":
filter_slice = filter[1]
return filter_words(p, filter_slice)


def filter_words(line, filter):
if len(line) < len(filter) + 1:
return True
skip_line = False
for i, v in enumerate(filter):
if len(v) > 0 and (v.strip() != line[i + 1].strip()):
skip_line = True
break

return skip_line
__all__ = ["Filter", "FilteredAdapter"]
13 changes: 13 additions & 0 deletions casbin/persist/adapters/asyncio/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
from .adapter import AsyncAdapter
from .adapter_filtered import AsyncFilteredAdapter
from .batch_adapter import AsyncBatchAdapter
from .file_adapter import AsyncFileAdapter
from .update_adapter import AsyncUpdateAdapter

__all__ = [
"AsyncAdapter",
"AsyncFilteredAdapter",
"AsyncBatchAdapter",
"AsyncFileAdapter",
"AsyncUpdateAdapter",
]
32 changes: 32 additions & 0 deletions casbin/persist/adapters/asyncio/adapter.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
from abc import ABCMeta, abstractmethod


class AsyncAdapter(metaclass=ABCMeta):
"""The interface for async Casbin adapters."""

@abstractmethod
async def load_policy(self, model):
"""loads all policy rules from the storage."""
pass

@abstractmethod
async def save_policy(self, model):
"""saves all policy rules to the storage."""
pass

@abstractmethod
async def add_policy(self, sec, ptype, rule):
"""adds a policy rule to the storage."""
pass

@abstractmethod
async def remove_policy(self, sec, ptype, rule):
"""removes a policy rule from the storage."""
pass

@abstractmethod
async def remove_filtered_policy(self, sec, ptype, field_index, *field_values):
"""removes policy rules that match the filter from the storage.
This is part of the Auto-Save feature.
"""
pass
17 changes: 17 additions & 0 deletions casbin/persist/adapters/asyncio/adapter_filtered.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
from abc import ABCMeta, abstractmethod


class AsyncFilteredAdapter(metaclass=ABCMeta):
"""AsyncFilteredAdapter is the interface for async Casbin adapters supporting filtered policies."""

@abstractmethod
async def is_filtered(self):
"""IsFiltered returns true if the loaded policy has been filtered
Marks if the loaded policy is filtered or not
"""
pass

@abstractmethod
async def load_filtered_policy(self, model, filter):
"""Loads policy rules that match the filter from the storage."""
pass
15 changes: 15 additions & 0 deletions casbin/persist/adapters/asyncio/batch_adapter.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
from abc import ABCMeta, abstractmethod


class AsyncBatchAdapter(metaclass=ABCMeta):
"""AsyncBatchAdapter is the interface for async Casbin adapters with multiple add and remove policy functions."""

@abstractmethod
async def add_policies(self, sec, ptype, rules):
"""AddPolicies adds policy rules to the storage."""
pass

@abstractmethod
async def remove_policies(self, sec, ptype, rules):
"""RemovePolicies removes policy rules from the storage."""
pass
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,13 @@
# See the License for the specific language governing permissions and
# limitations under the License.

from casbin import persist
import os

from ...adapter import load_policy_line
from .adapter import AsyncAdapter

class AsyncFileAdapter(persist.Adapter):

class AsyncFileAdapter(AsyncAdapter):
"""the async file adapter for Casbin.
It can load policy from file or save policy to file.
"""
Expand All @@ -42,7 +44,7 @@ def _load_policy_file(self, model):
with open(self._file_path, "rb") as file:
line = file.readline()
while line:
persist.load_policy_line(line.decode().strip(), model)
load_policy_line(line.decode().strip(), model)
line = file.readline()

def _save_policy_file(self, model):
Expand Down
27 changes: 27 additions & 0 deletions casbin/persist/adapters/asyncio/update_adapter.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
from abc import ABCMeta, abstractmethod


class AsyncUpdateAdapter(metaclass=ABCMeta):
"""AsyncUpdateAdapter is the interface for async Casbin adapters with add update policy function."""

@abstractmethod
async def update_policy(self, sec, ptype, old_rule, new_policy):
"""
update_policy updates a policy rule from storage.
This is part of the Auto-Save feature.
"""
pass

@abstractmethod
async def update_policies(self, sec, ptype, old_rules, new_rules):
"""
UpdatePolicies updates some policy rules to storage, like db, redis.
"""
pass

@abstractmethod
async def update_filtered_policies(self, sec, ptype, new_rules, field_index, *field_values):
"""
update_filtered_policies deletes old rules and adds new rules.
"""
pass
7 changes: 4 additions & 3 deletions casbin/persist/adapters/file_adapter.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,12 @@
# See the License for the specific language governing permissions and
# limitations under the License.

from casbin import persist
import os

from ..adapter import Adapter, load_policy_line

class FileAdapter(persist.Adapter):

class FileAdapter(Adapter):
"""the file adapter for Casbin.
It can load policy from file or save policy to file.
"""
Expand All @@ -42,7 +43,7 @@ def _load_policy_file(self, model):
with open(self._file_path, "rb") as file:
line = file.readline()
while line:
persist.load_policy_line(line.decode().strip(), model)
load_policy_line(line.decode().strip(), model)
line = file.readline()

def _save_policy_file(self, model):
Expand Down
Loading
Loading